GC
判断对象已死
方法一:通过引用计数器法
当被引用一次,计数器+1;当引用失效,计数器-1;当计数器为0,表示对象不能再被使用。
方法二:可达性分析
GC Roots当GC roots对象不能到达对象时,此对象不可用
可作为GC Roots的引用点:
- JavaStack中的引用的对象。
- 方法区中静态引用指向的对象。
- 方法区中常量引用指向的对象。
- Native方法中JNI引用的对象。
引用 reference
定义:存储的数值为另一块内存的起始地址。
引用分类:强引用、软引用、弱引用、虚引用
Strong Reference:普通的对象,当有引用存在时,不会被回收
Sofe Reference:在第二次GC时,会被回收。
Weak Reference:在下一次GC时,会被回收。
Phantom Reference:不会影响对象的生存时间,仅仅在对象被回收时收到系统消息。
可达性分析
在可达性分析后,判断为不可用时,对象不会立即GC。若重写了finalize()可以拯救一次。当GC时,若对象重写了finalize()方法,对象会加入到F-Queue队列,等待一个优先级较低的线程Finalizer去执行,仅会执行还未调用过finalize()方法的对象的finalize()方法。当在finalize方法中,重新与引用链上的任何一个对象建立关系,将会移出该队列,获得最后一次机会。不建议使用finalize()方法。
回收方法区
回收方法区,也就是回收永久代,卸载类和回收废弃常量。
卸载类
要求比较苛刻:
1.已无该对象的实例
2.加载该对象的classload已回收
3.java.lang.Class对象没有在任何地方被引用,即无法通过反射访问该类。
回收废弃常量
没有该常量的引用就行。
垃圾收集算法
标记-清除
分为标记和清除两个阶段
先标记已被回收的对象,再统一清除对象
缺点:堆中出现碎片、两个阶段效率低
优点:简单
复制
以堆的新生代为例。
1.对象首先分配到Eden(大对象可以直接分配到老年代)。
2.当Eden满了,MIN GC Eden和Survivor1,将存活的对象复制到Survivor2中。
3.多次MIN GC后(一般为15次,可设置),仍然存活就移到老年代。
4.当Survivor内存不够用时不够存放存活的对象,由老年代担保,直接进入老年代。
标记-整理
标记过程与标记-清除一致
清除过程是将存活的对象都向一端移动,清除端边界以外的对象。
分代收集算法
1、新生代:复制
2、老年代:标记-整理、标记-清除
HotSpot中的算法实现
存在的问题:GC Root需确保一致性,导致GC时需要停止其它所有线程(Stop the World)。如何通过并行或者并发的方式进行GC就是提出的问题。
将引用存放的位置存入OopMap,GC通过扫描OopMap进行回收。并且只在安全点和安全区域进行存放操作。
安全点:就是需要花费比较长时间执行的一条指令。比如方法调用、循环跳转、异常跳转等。通过抢先式中断或者主动式中断。
抢先式中断指GC时,中断所有线程,查询是否到达安全点,若未到达,恢复线程继续执行,直到安全点;
主动式中断,是所有线程主动轮询是否需要进入安全点,轮询标志的地方和安全点是重合的。
一般采用后者。
安全区域:指sleep或者blocked状态。在这一段时间内,引用关系不会改变。当GC还未完成时,将要离开安全区域,则会停在安全区域,直到GC完成。
垃圾收集器:
Young generation:Serial(单线程)、ParNew(多线程)、Paraller Scavenge(多线程,可控吞吐量和停等时间)、GI
Old generation:CMS(初始标记STW、并发标记、重新标记STW、并发清除)、Serial Old(MSC,单线程)、Parallel Old(多线程)、GI(初始标记STW、并发标记、最终标记、筛选回收)
注:STW=stop the world
复制停止:
Serial:单线程;在单CPU下,由于减少了线程之间切换的开销,所以速度可观。一般用于Client下
ParNew:多线程;大部分使用这个,因为CMS仅能与Serial与ParNew搭配使用
Paraller Scanvenge:多线程;自适应调节;吞吐量与停等时间正相关,减少新生代大小,停等时间减少,但吞吐量也减少。
标记-整理:
Serial Old:单线程;与Paraller Scavenge搭配,或作为CMS后备方案
Paraller Old:多线程;与Paraller Scanvenge搭配
G1:多线程,可独立管理整个GC堆;Region,局部之间使用复制停止算法;很复杂,回收部分Region
标记-清除:
CMS:
多线程、并发;
初始标记,仅仅标记可达的对象;并发标记,并行标记消亡的对象;重新标记,修正并发标记由于用户程序导致的对对象的修正;并发清除;
由于存在浮动垃圾(并发标记时生成的新的垃圾),所以不能像其他收集器一样在老年代几乎满的时候进行收集,需要预留一部分;当这一预留的一部分,不能满足程序要求(也就是放浮动垃圾),就会出现concurrent mode failure,之后将启用Serial Old。
由于是标记清除,需要在FULL GC时候进行碎片整理,可设置。
一些指令
-Xms 初始堆大小,示例-Xms250M
-Xmx 堆的最大值,一般与-Xms分配一样,可避免回收后需要缩小堆大小至Xms的值
-XX:NewSize=n,设置年轻代大小 -Xmn 年轻代的大小
-XX:MaxPermSize=n:设置持久代大小
-XX:NewRatio=n,设年轻代和老年代的比值
-XX:SurvivorRation=3,表示Eden:一个Survivor=3:1,存在两个Survivor,所以一个Survivor占1/5;
-Xss 为jvm启动的每个线程分配的内存大小,默认JDK1.4中是256K,JDK1.5+中是1M
调优总结
年轻代大小选择
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
年老代大小选择
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
并发垃圾收集信息
持久代并发收集次数
传统GC信息
花在年轻代和年老代回收上的时间比例
减少年轻代和年老代花费的时间,一般会提高应用的效率
吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
较小堆引起的碎片问题
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩