第三章 垃圾收集器和内存分配策略 《深入理解java虚拟机》

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/xuchuanliang11/article/details/102467588

判断对象是否存活

判断对象存活的两种方法:引用计数法和可达性分析;引用计数法无法解决互相循环引用问题;

jvm中使用可达性分析法来判断对象是否存活,可达性分析就是通过一系列GC Roots对象作为起点,通过GC Roots向下搜索,所走过的路程称为引用链,如果一个对象到GC Roots没有任何引用链相连,则认为该对象不可达。

java语言中可作为GC Roots的对象包括:1.虚拟机栈(栈帧中的本地变量表)中引用的对象;2.方法区中类静态变量的引用;3.方法区中常量引用的对象;4.本地方法栈中JNI(一般说的native方法)引用的对象。

java1.2之后引用分为四类:1.强引用(不会被垃圾收集器回收);2.软引用(当即将发生OutOfMemeoryError之前会回收);3.弱引用(在下一次产生垃圾收集时会被回收);4.虚引用(无实际意义)

java中对象要被回收之前会被经过两次标记过程,第一次标记会将对象放在一个F-queue队列中,并且执行该对象的finalize()方法,如果第二次垃圾收集时依然不可达,则会被回收。注意finalize()方法只会被执行一次,如果在该方法体中对象成功将自身与GC Roots相连,则能够拯救自己【一般很少用】

垃圾收集算法

标记清除算法:首先标记需要被清除的对象(即通过可达性分析算出死亡的对象),然后再清除被标记的对象。缺点:1.效率低;2.内存碎片化严重,当需要分配较大的对象时,如果内存中没有足够的连续空间,则会提前触发一次垃圾收集动作。

复制算法:为了解决标记清除算法的内存碎片化产生的,将内存分成大小相同的两块,每次只使用其中一块,当一块内存满了后,将这块的存货对象复制到另一块内存中,并清空当前内存。缺点:内存利用率低。一般新生代使用这种算法收集垃圾,因为对象的存活率低,回收效果比较明显

实际上在jvm的堆内存中,管理新生代一般都使用复制算法,因为新生代中内存回收率高,一般将新生代内存分成一个Eden区域和两个Survivor区域,比例是8:1:1,当发生垃圾回收时,将Eden区域和一块Survivor区域中存活的对象复制到另一个Survivor区域,并清空前面的Eden和Survivor区域。

为什么使用一个Eden区域和两个Survivor区域:

1.为什么要有Survivor区域,是因为如果没有Survivor区域,那么每当发生一次Minor GC时都会将Eden区域中所有存活的对象复制到老年代区域中,老年代很快被填满后,将会触发Full GC,在进行Full GC时会导致Stop the world ,并且影响性能,Survivor的存在意义就是减少送到老年代的对象,减少Full GC的发生,Survivor的预筛选机制保证了只有经历了16次Minor GC的对象才会被送到老年代。

2.为什么需要两个Survivor区域,因为新建的对象分配到Eden区域,当经历过一次Minor GC后,Eden区域中被存活的对象被送到Survivor space S0中,并且清空Eden区域;当在发生一次Minor GC时,将Eden区域和Survivor space S0中存活的对象复制到Survivor space S1中,并且清空Eden和S0区域,当对象经历过16次Minor GC后,被送到老年代,这样保证了总有一块Survivor区域是空白,保证了复制后内存的连续性。具体参见本人的博客:https://blog.csdn.net/xuchuanliang11/article/details/100978439

标记整理算法:在回收的时候移动对象,这种方法是用于对象回收率低的情况,但是效率不高,一般老年代使用这种算法。

分代收集算法:实际上就是将内存区域划分成老年代和新生代,分别根据对象的存活特点,使用不同的垃圾收集算法进行收集。

hotspot垃圾收集器

并行垃圾收集器:指多条垃圾收集器线程并行工作,但是用户线程仍然处于等待状态。

并发垃圾收集器:指用户线程和垃圾收集器同时运行。

Serial收集器:是一块单线程的新生代垃圾收集器,在进行垃圾收集时,会停掉所有的用户线程,直到垃圾收集结束。

ParNew收集器:相当于是一款多线程的Serial收集器,也是新生代收集器,主要作用是能和CMS进行配合使用。

Parallel Scavenge收集器:与ParNew收集器类似,都是一个多线程的新生代收集器,但是这款收集器的关注点再吞吐量,CMS等收集器的关注点再用户线程的停顿时间,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),停顿时间短有利于与用户交互的程序,吞吐量高有利于在后台运行的程序。也被称为吞吐量优先收集器。

Serial Old收集器:Serial的老年代版本

Parallel Old收集器:Parallel Scavenge的老年代版本

CMS(Concurrent Mark Sweep)收集器:以获取最短停顿时间为目标的老年代垃圾收集器。适合B/S架构的java应用,重视服务的响应速度。

CMS垃圾回收分为四个阶段:初始标记、并发标记、重新标记、并发清除四个阶段,其中初始标记和重新标记需要触发Stop the world。

缺点:1.CMS默认回收线程数是(CPU数量+3)/4,也就是当CPU数量小于4时,CPU需要分出一半运算能力来执行垃圾收集器,对用户程序影响较大;2.CMS无法处理浮动垃圾,也就是在并发清除的时候,用户线程依然在执行,会产生垃圾,这种垃圾称为浮动垃圾,浮动垃圾需要等到下一次垃圾收集时处理;3.是基于标记-清除算法,会在老年代中产生内存碎片,达到一定程度后会触发Full GC。

G1垃圾收集器:面向服务端的一款垃圾收集器。特点:1.并行和并发;2.分代手机;3.空间整合(基于标记整理算法);4.可预测的停顿(可以建立一个可预测的停顿时间模型)。G1是将内存划分成多个相同的独立内存区间(Region)。

步骤:初始标记、并发标记、最终标记、筛选回收

内存分配与回收策略

新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为大部分java对象存在朝生夕死的情况,所以Minor GC一般执行比较快,也比较频繁。

老年代GC(Full GC/Major GC):指发生在老年代的垃圾收集动作,出现一次Full GC一般伴随着一次Minor GC,Full GC一般比Minor GC慢10倍以上。

对象被送入老年代的情况:1.一般当对象年龄达到15,也就是熬过了15次Minor GC之后,会被送入老年代,通过                         -XX:MaxTenuringThreshold指定送到老年代的年龄;2.一般比较大的对象也可直接送到老年代,避免Eden和Survivor区域内存赋值带来的开销,通过-XX:PretenureSizeThreshold设置判断是否是大对象的大小;3.动态对象年龄判断,如果在Survivor中相同年龄的所有对象大小综合大于Survivor内存的一半,那么大于或者等于这个年龄的对象会被送到老年代中。

命令:-XX:+PrintGCDetail在垃圾收集时打印日志;-Xmn10m:限制新生代的内存空间是10m;-XX:Survivor=8:标识Edent区域和一块Survivor区域的空间比例是8:1;-XX:PretenureSizeThreshold=1024:设置当大于这个参数时,对象被直接分配到老年代;-XX:MaxTenuringThreshold=15:设置当新生代年龄大于该值时,会被送入老年代

猜你喜欢

转载自blog.csdn.net/xuchuanliang11/article/details/102467588