Q:什么时候会触发垃圾回收?
R:新生代对象越来越多,都快满的时候就会触发垃圾回收。
Q:被那些变量引用的对象是不能回收的?
R:JVM中使用了一种可达性分析算法来判定哪些对象是可以被回收的,哪些是不可被回收的。
在JVM规范中,局部变量就是可以作为GC Roots的,也就是只要一个对象被局部变量引用了,那么就说明他有一个GC Roots,此时就不能被回收了。
静态变量也可以看做是一种GC Roots,此时只要一个对象被GC Roots引用了,就不会回收他。
总结:只要你的对象被方法的局部变量、类的静态变量引用了,就不会回收他们。
Q:Java中对象不同的引用类型?
Java引用类型:强引用,软引用,弱引用,虚引用
强引用:一个变量引用一个对象,只要是强引用的类型,那么垃圾回收的时候绝对不会去回收这个对象的。
软引用:实例对象A用一个软引用类型B的对象给包裹起来了,正常情况下垃圾回收不会回收软引用对象,但是进行垃圾回收之后,发现内存空间还是不够存放新对象,内存快溢出了,此时就会把这些软引用对象给回收掉。
弱引用:弱引用和没引用类似,发生垃圾回收,就会把这个对象回收掉。
Q:finalize()方法的作用?
如果有个对象要被垃圾回收了,假如这个对象重写了object类中的finalize()方法,那么他会首先尝试调用一下他的finalize()方法,看这个对象是否给了某个GC Roots变量。如果重新引用了那么他就不会被垃圾回收了。
复制算法:针对新生代的垃圾回收算法,他叫做复制算法。把新生代的内存划分成两块内存区域,然后只使用其中一块内存,待那块内存快满的时候,把里面的存活对象一次性转移到另外一块区域,保证没有内存碎片,接着一次性回收原来那块内存区域的垃圾对象。再次空出来一块内存区域。两块内存区域就这么重复着循环使用。
复制算法缺点:始终只有一半内存可用,对内存的使用效率太低了。
复制算法的优化:Eden区和Survivor区
新生代的内存区域划分成三块:1个Eden区,2个Survivor区。其中Eden区占内存空间的80%,两个Survivor区各占10%。
每次使用Eden区和一块Survivor区。第一次Eden区快满了,触发垃圾回收,Eden区的存活对象都一次性转移到Survivor区,然后清空Eden区。
第二次Eden区快满了,触发垃圾回收,Eden区和上次Minor GC存活在Survivor区的存活对象都一次性转移到另外一块Survivor区。然后清空Eden区和上次Minor GC存活在Survivor区。
保证始终有一块Survivor区是空着的,就这样一直循环使用着三块内存。
最大的好处就是只有10%的内存空置了,剩下的90%都利用上了。
进入老年代判断标准
多次躲过GC进入:JVM参数“-XX:MaxTenuringThreshold”来设置,默认是15岁。:也就是说新生代每躲过一次GC被转移到一块Survivor区域中都会增长一岁。15岁的时候就会被转移到老年代中去。
动态年龄判断法:当前GC后转移到Survivor的对象中,一批对象内存超过Survivor区域内存的50%,大于这个年纪的对象全部放入老年代。
年龄1+年龄2+年龄n 的对象内存>50%Survivor区内存 然后年龄大于n的对象全部放入老年代
大对象直接进入:JVM参数“-XX:PretenureSizeThreshold”,可以把他的值设置成字节数,1048576=1MB
也就是创建大于这个大小的对象,直接进入老年代。
Q:Minor GC后的对象太多无法放入Survivor区怎么办?
R:需要把这些对象直接转移到老年代去
老年代空间分配担保规则
在执行任何一次Minor GC之前,JVM会先检查一下老年代可用的可用内存空间,是否大于新生代所有对象的总大小。
如果老年代空间小于新生代的全部对象大小,就会看JVM参数“-XX:-HandlePromotionFailure”的参数是否设置了,
设置了这个参数,判断老年代内存和之前每次Minor GC后进去老年代的对象的平均大小。
a.步骤判断失败了;
b.JVM参数“-XX:-HandlePromotionFailure”,没设置
两者满足就会直接触发Full GC,对老年代进行回收,然后再进行Minor GC。
a.步骤判断失败了;b.JVM参数“-XX:-HandlePromotionFailure”设置了;会出现几种可能:
第一种:Minor GC过后,存活对象大小,是小于Survivor区域大小,直接进去Survivor区域
第二种:Minor GC过后,存活对象大小,是大于Survivor区域大小,但是小于老年代可用内存大小,直接进去老年代
第三种:Minor GC过后,存活对象大小,是大于Survivor区域大小,也大于老年代可用内存大小,就会发生HandlePromotionFailure,这时候触发一次Full GC,
Full GC就是对老年代进行垃圾回收,同时一般也对新生代进行垃圾回收。
如果Full GC过后,老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么此时就会导致所谓的“OOM”内存溢出了
老年代垃圾回收算法:标记整理算法
老年代触发垃圾回收的时机
一是在Minor GC之前,一通检查发现很可能Minor GC 之后要进入老年代的对象太多了,老年代放不下,要提前触发Full GC 然后再带着进行Minor GC。
二是在Minor GC之后,发现剩余对象太多,放入老年代都放不下了。
避免频繁Full GC,要确保Survivor区有足够内存可以容纳每次Minor GC后的对象。JVM参数“-XX:-SurvivorRatio=8”这个参数默认Eden区占80%,可以调整Eden区占比,增加Survivor区的内存。避免动态年龄判断规则直接把他们升入老年代。
垃圾回收器:
Serial和Serial Old 垃圾回收器:分别用来回收新生代和老年代的垃圾对象
工作原理:就是单线程运行,垃圾回收的时候会停止我们自己写的系统的其他工作线程,让我们系统直接卡死不动,然后让他们垃圾回收,这个现在一般写后台的Java系统几乎不用。
ParNew和CMS垃圾回收器:ParNew现在一般都是用在新生代的垃圾回收器,CMS是用在老年代的垃圾回收器,他们都是多线程并发机制,性能更好,现在一般是线上生产系统的标配组合。
G1垃圾回收器:统一收集新生代和老年代。采用更加优秀的算法和设计机制。
“Stop the World”:ParNew垃圾回收器回收新生代时,是使用复制算法。需要Java系统暂停创建对象,进入“Stop the World”状态,然后垃圾回收线程调用垃圾回收器ParNew执行垃圾回收算法复制算法,把Eden区和Survivor1区的对象复制到Survivor2区,然后尽快一次性回收掉Eden区和Survivor1区中的所有垃圾对象。回收完毕就会恢复我们写的Java系统的工作线程的运行。
“Stop the World”就会造成系统停顿,Minor GC需要多久,系统的工作线程在这个时间段内不在运行,不能处理请求。
Full GC 的时间会更长,两者都会造成系统停顿,频繁卡顿会造成用户体验极差。
我们现在常用的新生代垃圾回收器是ParNew,他针对服务器一般都是多核CPU做了优化,支持多线程垃圾回收,可以大幅度提升回收性能,缩短回收时间。