GC常见垃圾回收算法,JVM分代模型
如何判断是垃圾?引用计数器和Root可达性算法
如何进行清除?标记清除、复制、标记整理
堆分代模型?Eden,Surevivor,Tenuring
一个对象从创建到消亡的过程?
对象什么时候进入老年代?
一、GC(Garbage Collector)
GC tuning:GC调优
tuning:调整,调优
reference:引用
1、Garbage,什么是垃圾?
没有引用指向的任何对象,都是Garbage
2、判断什么是垃圾的两种算法(也就是怎么找到垃圾):
(1)reference count(引用计数器):
有一个对象引用该变量,在对象头+1,有多个就加多个,没引用了就-1,减到0,说明没有引用了,判定为垃圾;
reference count不能解决的问题是循环引用问题,比如对象A引用对象B,对象B引用对象C,对象C又引用对象A;
相互引用的时候没有任何第三方对象引用这两个对象,不能判断为垃圾,不会被回收,实际上是需要被回收的:
(2)root searching(可达性算法):
在编程过程中哪些对象或者变量会被定义成root?
JVM Stack:虚拟机栈里面的线程栈里面的变量;
Native Stack:c/c++实现的那些native方法里面使用到的变量,会被定义为root对象
静态变量:static修饰的对象
常量池里面的对象:比如Class对象
一般是一个程序启动后马上要用到的对象;
3、找到垃圾之后,如何进行清除?
三种算法:(优缺点是指这种算法的优缺点)
(1)Mark Sweep:标记清除,
特点:
- 从Root对象开始,遍历两次,一次进行标记,一次进行清除;
- 算法简单;执行效率不稳定,垃圾较多,而存活对象少的时候,效率低,内存空间碎片化会更严重;
(2)为解决Mark Sweep算法的缺陷,提出:Semispace Copying:拷贝(半区复制算法)
实现:
- 将内存一份为二,从Root对象开始,将有用的对象移动(复制)到一边,移动完成后清除另一边需要被回收的内存;当垃圾较多,存活对象较少时,只需要将存活对象移动到一块很小的区域,就能进行垃圾清除,效率高,同时也能解决内存空间碎片化的问题;
- 只扫描一次,但移动复制对象,需要调整对象的引用,会产生对象引用指针移动的开销,同时将内存一分为二,也会造成内存减半的后果,空间浪费;
- 如果存活对象比较多的情况下不适合;
- 如果存活对象比较多,内存又减半会导致内存不足,需要老年代进行分配担保(当Eden进行回收后往Surivor区进行复制,Surivor区发现内存不足,对象直接进入老年代)
(3)Mark Compact:标记整理
实现及特点:
- 扫描2次,从Root开始将有用的对象压缩到内存的头部,如果前面有垃圾进行填充
- 需要移动对象,效率偏低;
- 不会产生内存碎片,更方便对象分配,不会产生内存减半;
总结:标记清除和标记整理的算法
- 是否移动对象:移动则回收的时候更复杂,不移动则内存再分配时会更复杂
- 是否停顿(或者说停顿时间更短):移动的时候停顿时间更长,不移动的时候停顿时间短,甚至可以不停顿;
- 吞吐量:从吞吐量的角度考虑,移动对象吞吐量会更高;
- 也就是说,根据业务,如果在意用户体验,不要停顿太长时间的情况下,考虑使用不移动对象的垃圾回收算法,也就是标记-清除
- 如果对吞吐量要求高的场景,使用移动对象的垃圾回收算法,也就是复制或者标记整理的垃圾回收算法会更划算;
二、JVM内存分代模型
1、堆内存的逻辑分区
G1是逻辑分代,但物理内存不分代,除此之外,都是逻辑分代,物理内存也分代
Eden区是new出来的对象真正存放的区域,而S0和S1是经过回收会还存活的对象存在区域
GC算法的选择上,新生代活着的对象比较少适合Copying算法的垃圾回收器,而老年代 ,存活的对象比较多,适合Mark Sweep(MS)和Mark Compact(MC)这两种算法实现的垃圾回收器;
查询年轻代和老年代之间的空间比值:NewRatio=2,意思是年轻代和老年代的比值是1:2:
java查询参数小细节:java -XX:PrintFlagsFianl -version
以-开头的标准参数
以-X开头是非标准参数,
以-XX开头,不稳定参数,有些版本支持,有些版本可能不支持,也可能不是这个命令
2、一个对象从创建到消亡的过程:
先尝试栈上分配,如果满了,在Eden区分配,当出现GC时,会往S0区域移动,多次回收是指在S0和S1上来回移动,移动次数可以通过参数配置,经过多次回收还存活,移动到老年代;
MinorGC/YGC: 年轻代垃圾回收,年轻代空间耗尽,无法再分配空间时,触发该GC;
Major/FullGC: 老年代和年轻代都空间耗尽,无法再分配空间时,老年代和年轻代都会触发GC;
3、对象尝试在栈上分配
(1)什么样的内容会在栈上分配?
- 线程私有小对象:对象比较小,而且是线程私有的,没有线程共享
- 无逃逸:出了线程无其他线程知道这个对象的存在
- 支持标量替换:对象只有少量属性,完全可以使用这些属性来替换对象;
(2)当线程栈上分配空间不足了,会进行线程本地分配(Thread Local Allocation Buffer,简称TLAB)
- 每个线程会在Eden区有一块小区域是该线程独享的,用于分配小对象,仅1%;
- 多线程的时候不用竞争Eden区域就可以申请的空间,可以提升对象分配的效率;
4、对象什么时候进入老年代?以下两种情况会进入老年代
(1)通过一个参数设置:XX:MaxTenuringThreshold指定回收次数,也就是当进行GC时,对象在S0和S1之间进行copying,超过该参数设置的值,就会触发对象进入老年代;
该参数,在
Paralle Scavenge中默认是15
CMS:6
G1:15
(2)动态年龄,是指将S0里面的存活对象全部拷贝到S1的时候,如果发现全部对象的大小超过了S1的空间的50%,则会触发动态年龄淘汰机制,就是把年龄最大的那些对象放入老年代,而不管他的copying次数是否超过设置的阈值;