一、深入java虚拟机—JVM视频课程
深入JVM(Java Virtual Machine)
java内存模型
java内存模型概览
在运行时数据区之中内存的分配一共有五个模块:
1、堆内存Heap:保存真正的程序的数据的部分。
2、栈Stack:保存堆内存的地址、基本数据、方法的执行;
3、方法区:保存所有的方法的具体操作,该区域属于共享;
4、程序计数器:这是一块很小的内存,小到几乎可以忽略的地步,只是做一个程序执行顺序的记录,只是为了标记下一步要执行的代码的顺序号;
5、本地方法栈:该栈之中所保存的都是操作系统的原生函数;
堆和方法区共享、栈、程序计数器、本地方法栈独享
StackOverFlowError和OutOfMemoryError:
如果请求的栈的深度过大,虚拟机可能会抛出StackOverFlowError,
如果虚拟机的实现中允许虚拟机栈动态扩展,当内存不足以扩展栈的时候,会抛出OutOfMemoryError。
总结:
1、造成StackOverFlowError和OutOfMemoryError的原因:
2、在JVM的栈内存中保存有栈帧的概念,所有的栈内存采用后进先出的数据结构进行存储。
Java对象访问模式:
JVM默认的运行模式:
1.HotSpot虚拟机采用了混合模式的模式来运行;
运行的形式使用的是server模式,该模式占用内存大,启动的速度慢,但是处理的效率最高。
第二章JVM内存模型与垃圾收集
JVM垃圾回收主要是指堆内存空间,那么在每一次执行GC的时候需要区分那些内存需要回收那些不需要回收,所以为了整体的回收处理方便,JVM将对内存分为如下的几个组成部分,需要考虑jdk的版本:
2.1java堆内存模型
新生代:那些刚刚创建的对象,刚刚创建的对象有可能会存在许多的垃圾对象,那么这些对象应该是被优先回收的。
老年代:老不死的那类对象,经过了很多次清理之后,该对象依然有用;
永久代:intern()方法进入池的对象实际上就在永久代中,永久代不会被回收,因为相当于一个bug存在,所以在jdk1.8之后将其更换为元空间(电脑的直接内存)。
2.2对象创建与垃圾回收流程
垃圾回收两种形式:自动调用、手工调用(runtime.getRuntime().gc()
1、当程序之中需要产生新的实例化对象(反射实例化、new对象、clone对象),为新的对象申请空间;
2、新对象要申请的对象空间默认都是在伊甸园区(新生)进行开辟,所以首先需要判断伊甸园区是否有空余的内存空间,如果有空余的内存空间直接在伊甸园区开辟新的堆内存空间,此时不会发生有GC处理;
3、如果新对象无法在伊甸园空间申请新的空间,那么就表示现在的伊甸园区的内存空间不足,不足就需要将那些无用的新对象进行回收(Minor GC),当回收完成后要继续判断该空间是否还有空余的空间容纳下新的对象,如果可以容纳,则开辟新空间,保存新对象。
4、如果此时伊甸园区即使执行了Minor GC后依然发现没有可以回收的对象,那么这个时候将判断存活区是否有空间(存活区一般与伊甸区的比率1:1:8)如果存活区有空余空间,则将这些活跃的伊甸区的部分对象直接保存给存活区,就相当于伊甸园区可以腾出部分空间(这个空间非常小)来供新对象进行使用。
5、如果此时存活区依然满了(空间不足),则继续向老年代进行内存空间申请,如果老年代有空余的空间则将存活区中的活跃对象保存在老年代,而后存活区得到了空间释放,伊甸园区也得到了空间释放,则对象空间申请成功。,
6、如果老年代也是满的,那么这个时候会执行FULL GC(完全GC/Major GC)进行老年代的内存释放,如果可以释放则进行对象的保存,如果释放不成功则表示已经没有可用的内存空间了,那么就会抛出OOM异常。
*GC的触发条件:Minor GC、FULL GC(Major GC)
Minor GC:发生在年轻代内存空间,当年轻代内存空间不足时会进行触发,释放年轻代中不活跃对象。
Major GC:发生在老年代内存空间,当老年代的空间不足时会自动触发Full GC,如果触发之后,内存空间依然不足,则会产生OOM错误提示。
2.3java堆内存调整参数
GC虽然可以进行内存空间的释放,但频繁的GC肯定会影响系统性能,那么如何才可以不频繁的发生GC呢?内存越大GC发生的频率就越低,系统的性能就越高。
JAVA内存调整策略(内存优化策略)
如果取消掉伸缩区的概念,让初始化的内存就是最大的可用内存空间,这样就可以实现JVM的性能调整避免了重复的内存的控制操作,可以让整个的代码的执行速度上升。-Xms -Xmx
面试题:如何对JVM进行调优:
1、在堆内存之中存在一个伸缩区的概念,默认情况下最大可用内存为整体内存的四分之一,默认使用的内存为整体内存的六十四分之一,这个时候只需要避免伸缩区的频繁变更,就可以提升程序的性能
2、可以在程序执行的时候-Xms设置初始化内存, -Xmx设置最大可用内存、将这两个内容设置为一样空间大小,就可以提升JVM的运行性能。
2、4年轻代
年轻代:年轻代主要分为两个区域:伊甸区、存活区
所有新创建的对象都会存活在伊甸区,但是伊甸区的保存的空间一定是最大的,毕竟产生新对象的几率是很高的,在伊甸园区里面由于对象经常可能只是临时创建,所以其拥有一个Minor GC的操作处理,而经过多次的Minor GC后依然被保留下来的对象,就应该认为该对象不应该被回收,则将此对象保存到存活区中。
存活区分为两类:主要负责对象的晋级,向老年代晋级。并且这两个存活区有一块是专门进行对象回收的,所以有一块内存空间总是空的。由于伊甸园区保存的对象数据较多,所以默认的比率为8:1:1.
年轻代GC实现算法-复制算法Copying
算法:复制采用的方式为从根集合扫描出存活的对象,并将找到的存活对象复制到一块新的完全未使用的空间中。
在经过GC之前一定会先发生一次全对象的扫描处理操作,通过扫描才可以知道哪些对象是垃圾空间。
年轻代优化算法:
在实际运行中,由于Eden区总是会保存大量的新生对象,所以HotSpot虚拟机为了可以加快此空间的内存分配,而使用了Bump-The-Pointer和TLAB(Thread-Local-Allocation-Buffers)两种技术。
BTP算法(记录栈中最后一个对象后面的剩余空间)可以提高内存的分配速度,但并不适合多线程的操作情况。
TLAB算法将伊甸园区分为多个数据块,每个数据块分别使用BTP技术进行对象保存于内存分配。
在java内存中,所有的堆内存是线程共享的区域。
年轻代内存调整参数:
-Mmn 设置年轻代堆内存大小,默认为物理内存的1/64.
-Xss设置每个线程栈的大小,JDK1.5之后默认为每个线程分配1M的栈大小,减少此数值可以产生更多的线程对象,但是不能无限生成。
-XX:SurvivorRatio设置伊甸区与存活区空间的大小比例8:1:1,不建议修改。
2、5老年代调整
老年代空间的主要目的是用于存储由Eden区发送过来的对象,一般经历过好几次Minor GC还会保存下来的对象,才会被复制到老年代,一般老年代的内存空间大小会设置的比年轻代大,这样可以存放更多的对象,同时在老年代中执行GC的次数也相对较少,当老年代内存不足时会自动执行Full GC。
算法:标记-清除(Mark-Sweep)采用的方式为从根集合开始扫描,对存活区的对象进行标记,标记完毕后、再扫描整个空间中未标记的对象,并进行回收。
优缺点:在空间中存活对象较多的情况下较为高效,但由于该算法为直接回收不存活对象所占用的内存,因此会造成内存碎片。
算法:标记-压缩Mark-Compact
标记阶段与标记清除算法相同,但是在清除阶段有所不同。在回收不存活对象所占用的内存空间后,会将其他所有存活对象都往左端的空间进行移动,并更新引用其对象指针。
优缺点:在标记-清除Mark-Compact的基础上还需要进行对象移动,成本相对高,好处则是不产生内存碎片。
-XX:PretenureSizeTnreshold控制直接进入老年代的对象大小,大于这个值的对象会直接分配在老年代中,不经过年轻代。
2.6 永久代(JDK1.8被废除)
Jdk1.8之前的一个bug性的存在,其核心的本质在于:该区域中的对象不会被回收。方法区就是永久代。HotSpot虚拟机中存在有永久代的概念,但是BEA和IBM的虚拟机是不包含永久代概念的,oracle将hotspot永久代取消了。
2.7元空间(永久代的替代品)MetaSpace的本质是本机的物理内存,其作用和永久代相同,但是元空间和永久代最大的区别,元空间用的是物理内存(受到本机的物理内存的限制),而永久代是JVM的内存空间,本身受到JVM的限制。
-XX:MetaspaceSize 设置元空间的初始大小
-XX:MaxMetaspaceSize设置元空间的最大内存默认没有限制,受到本机物理内存限制
面试题:请问是否知道什么叫OOM?怎么会出现?
OutOfMemoryError指的是内存溢出问题,内存的溢出需要考虑以下几种情况:
java堆内存溢出Java heap space:往往出现在Full GC失败之后。
永久代PermGen space:一个方法中出现的内存溢出。
元空间MetaSpace:分配的物理内存不足,或者数据量高于物理内存。
第三章 垃圾收集策略
JVM会自己选择合适的垃圾收集策略,而用户也可以自己来设置自己所需要的垃圾收集策略,但是就个人而言采用默认的垃圾收集处理策略。
垃圾的收集一定要分两个空间考虑:年轻代、老年代。
老年代的内存空间》年轻代的内存空间,所以老年代的对象每一次执行GC都会消耗更多的时间。
可用GC方式:
新生代可用GC策略:
串行GC(Serial Copying)
并行回收GC(Parallel Scavenge)
并行GC(ParNew)
老年代可用GC策略:
串行GC(Serial MSC)
并行huishouGC(Parallel MSC)
并发GC(CMS)
同一种垃圾的收集策略,有可能会根据触发内存代的不同有不同的效果,所以先来看各个内存的操作特点。
新生代—串行GC
算法:复制Copy
过程:扫描出新生代中存活的对象;
Minor GC将存活的对象复制到做为To Sapce的S0/S1区;
之前做为To Space/From Space的S0/S1区对换角色;
经历过几次Minor GC任然存活的对象,放入老年代。
年轻代并行GC Parallel Scavenge
算法:负责Copying清理算法;
操作步骤:在扫描和复制时均采用多线程方式处理,并行回收GC为空间较大的年轻代回收提供许多优化。
优势:在多CPU的机器上其GC耗时会比串行方式短,适合多CPU、对暂停时间的要求较短的应用。
也就是说一个GC的处理操作,需要有多个线程共同完成,一个线程负责内存的扫描(扫描出所有不用的内存对象,)而另外一个线程负责对象的复制操作。
并行回收只是处理年轻代的,而并行GC需要与老年代的GC结合。
年轻代并行GC(ParNew)
算法:复制Copying清理算法
操作步骤:并行GC必须结合老年代”CMS GC”一起使用。因为在年轻代如果发生了“Minor GC”时,老年代也需要使用“CMS GC” 同时处理(并行回收GC并不会做这些)。
CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。
老年代串行GC(Serial MSC)
算法:标记-清除-压缩(Mark-Sweep-Compact)
操作步骤:
扫描老年代中还存活的对象,并且对这些对象进行标记;
遍历整个老年代内存空间,回收所有未标记的对象内存;
将所有存活对象都集中在一端,而后将所有回收的对象的内存空间变为一块连续的内存空间。
优缺点:串行执行的过程中为单线程,需要暂停应用并耗时较长。
所有的串行GC处理都只是单线程处理,那么在进行处理的时候都必须暂停操作。
老年代并行GC(Parallel Mark Sweep 、Parallel Compacting)
算法:标记-压缩(Mark-Compact)
操作步骤:
将老年代内存空间按照线程个数划分为若干个子区域;
多个线程并行对各自的子区域内的存活对象进行标记;
多个线程并行清除所有未标记的对象;
多个线程并行将多个存活对象整理在一起,并将所有被回收的对象空间整合为一体。
优缺点:多个线程同时进行垃圾回收可以缩短应用暂停的时间,但是由于老年代的空间一般较大,所以在扫描和标记存活对象上需要花费较长时间。
老年代并行GC (Concurrent Mark-Sweep GC、CMS GC)
算法:标记-清除(Mark-Sweep)
操作步骤:
初始标记(STWInitial Mark):虚拟机暂停正在执行的任务(STW),由根对象扫描出所有的关联对象,并做标记。此过程只会导致短暂的JVM暂停。
并发标记:(Concurrent Marking)恢复所有暂停的线程对象,并且对之前标记过的对象进行扫描,取得所有跟标记对象有关联的对象。
并发预清理(Concurrent Precleaning):查找所有在并发标记阶段新进入老年代的对象(一些对象可能从新生代晋升到老年代,或者有一些对象被分配到老年代),通过重新扫描,减少下一阶段的工作。
重新标记(STW Remark):此阶段会暂停JVM,对在”并发标记“阶段被改变引用或新创建的对象进行标记;
并发清理(Concurrent Sweeping):恢复所有的暂停的应用线程,对所有未标记的垃圾对象进行清理,并且会尽量将已回收的对象的空间重新拼凑为一个整体。在此阶段收集器线程和应用程序线程并发执行。
并发重置(Concurrent Reset):重置CMS收集器的数据结构,等待下一次垃圾回收。
优缺点:只有在第一次和重新标记阶段才会暂停整个应用,这样对应用程序所带来的影响非常小。缺点是并发标记与回收线程会与应用线程争抢CPU资源,并且容易产生内存碎片。
第四章 G1收集器
G1收集器简介:
G1收集器Garbage ·First是从JDK1.7u4版本之后正式引入到java中的垃圾收集器,此类垃圾收集器主要应用在多CPU以及大内存的服务器环境下,这样可以极大的减少垃圾收集的停顿时间,以提升服务器的操作性能。引入此收集器的主要目的是为了将来的某一时间内可以替换掉CMS收集器。
G1的实现方案相当于现在将所有的子内存区域合并在一起,也不进行任何的区分,这样就相当于所有的内存的区域都可以按照统一的方式规划处理。而G1最大的特点就是避免了全内存扫描。
G1的回收策略:
在整个G1进行标记和清理的时候是按照区域完成的,这样不影响其他区域的执行,除此之外,适用的形式和之前的CMS都是非常类似的操作方式。
G1相关处理参数:
如果要使用G1收集器,则必须有用户自己来进行参数指定,有如下的可用参数,
第五章 Java引用类型
引用类型概述:
强引用(Strong Reference):即使进行了多次的GC回收,即使JVM的内存不够用了,即使JVM最终不得已抛出了OOM错误,那么该引用继续抢占。
软引用(Soft Reference):当内存空间不足时,可以回收此内存空间;如果充足则不回收。一般可以用在缓存处理操作开发;
弱引用(Weak Reference):不管内存是否够用,只要一出现GC处理,则立即回收。
幽灵引用(Phantom Reference):和没有引用是一样的。
软引用与强引用区别:
软引用中保存的内存如果在内存富裕的时候会继续保留,内存不足会作为第一批的丢弃者进行垃圾空间的释放。在开发中可以利用软引用实现高速缓存组件。
引用队列:
引用对象里面保存的就是一个要准备被回收的对象对的信息。
幽灵引用直接把要保存的内容保存在了引用队列之中了。