如何判断这个对象是存活的还是无用的
1):引用计数器
引用计数器定义:
定义:给每个对象分配一个计算器,当有引用指向这个对象时,计数器加1,当指向该对象的引用失效时,计数器减一。最后如果该对象的计算器为0时,java垃圾回收器会认为该对象是可回收的。
很多人认为的是:给对象添加一个引用计数器,每当有一个地方引用它时候,计数器值就+1,当引用失效的时候计数器就-1;所以得出结论:当这个对象的计数器为0的时候,就会被GC掉
上面这种说法有误,通过代码验证:
VM Args:-XX:+PrintGCDetails
public class ObjectGC { //写一些变量,占用一些内存,方便GC日志的查看 private static final int _1M = 1024*1024 ; private byte[] byteSize = new byte[10*_1M] ; public void testGC(){ A a = new A(); B b = new B(); a.b = b ; b.a = a ; //即使下面进行a = null和b = null,但是A类对象仍然被B类对象中的字段引用着, // 尽管现在A类和B类独享都已经访问不到了,但是引用计数却都不为0. a = null ; b = null ; System.gc(); } public static void main(String[] args) { new ObjectGC().testGC(); } } class A{ public B b; } class B{ public A a; }
从运行结果中可以清楚看到GC日志中包含“496K->0K”,意味着虚拟机并没有因为这两个对象互相引用就不回收它们,这也从侧面说明虚拟机并不是通过引用计数器算法来判断对象是否存活的。
2):可达性分析算法
这个算法的基本思路就是通过一系列的称为:GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference chain);
当一个对象到GC Roots没有任何引用链相连,那么就说明这个对象是不可用的
上图中的objectE,objectF,objectH虽然相互有关联,但是它们到GC Roots是不可达的,所以它们就会被判定为可回收的对象
垃圾回收算法
年轻代:
Eden:存放新生成的对象Suvisor:“两个幸存区。用来存放每次垃圾回收后存活的对象”
年轻代是由Eden space和两个suvisor组成的,在初始阶段,新创建的对象会分配给Eden区(如果创建的对象非常大,那么对象会直接进去老年代),两个Suvisor区是空的:
随着对象往Eden区进行填充,Eden区满了的时候,就会触young GC------ Minor GC
在这阶段会使用垃圾回收的算法---复制算法(复制算法会将存活的对象复制到from suvisor区域,然后已经无用的对象被回收)
注意:年轻代中的Eden和suvisor比例可以调整 -Xms -Xmx
调整年轻代大小的参数:-Xmn 或者 -XX:NewSize(新生代大小)和-XX:NewMaxSize
年轻代由主要由三部分组成“Eden”,from Suvisor , To Suvisor
-XX:SurvivorRadio来调整Eden与Eden Space和Survivor Space大小
随着minorGC的不断进行,会反复重复上面的过程:
这样每次对象还存活,两个suvisor区域中的对象,只要经过复制过程,年龄就会加1;
当对象的年龄不断的增长,达到一个默认值“15”( -XX:MaxTenuringThreshold)的时候,对象就会进入老年代了;
(在发生minor GC的时候。JVM都会去检查每次晋升到老年代的对象大小是否已经大于老年代剩余的空间,如果大于那么就会出现FULL GC)
指定老年代大小:
我们上面说了一个参数:-Xmx 是整个堆的大小; 那么老年代的堆大小= -Xmx - Xmn 当然,老年代的大小也可以通过:-XX:OldSize来指定 或者通过调整新生代和老年代的比例:-XX:NewRatio (比如这个值是4,那么就是新生代占对内存的1/5,老年代占用对内存的4/5)
JVM会持续这样的操作,只要年龄达到了哪个触发点,就会把年轻代的对象复制到老年代里面;那么随着时间的推移,老年代的对象会越来越多,最终老年代的空间区域也会不够,就会出现老年的GC-------Major GC(标记清除或者标记-整理)