笔记:深入理解JVM 第3章 垃圾回收器与内存分配策略
1、对象是否已死
(1). 引用计数法:无法回收相互引用的对象,故JVM没有采用
例子:
public class ReferenceObj { public ReferenceObj refObj; } public static void main(String[] args){ ReferenceObj obj1 = new ReferenceObj(); ReferenceObj obj2 = new ReferenceObj(); obj1.refObj = obj2; obj2.refObj = obj1; obj1 = null; obj2 = null; }
以上例子使用引用计数法无法回收,但是JVM使用的不是,JVM可回收。
(2). 可达性分析算法:
通过一系列“GC root” 作为起始点,从这些节点开始往下搜索,搜索经过的路径成为引用链。若对象不与引用链相连接,则该对象不可用,可被回收。
GC root的对象有:
a. 虚拟机栈中引用的对象
b. 方法区中类静态属性引用的对象
c. 方法区中常量引用的对象
d. 本地方法栈中JNI引用的对象
(3).引用的类型
强引用:引用存在则不会回收
软引用:内存不足,则回收
弱引用:不管内存是否足够,必定回收
虚引用:无法通过虚引用获得对象,只为在回收之前获得系统通知
(4).finalize 方法可使得对象在回收之前复活,但是不建议使用
(5). 方法区(持久代)回收
方法区中无用的常量、无用的类会被回收。判断无用的类的准则,,同时满足:
a.该类的所有实例已经回收
b.该类的ClassLoader已经回收
c.该类的Class 对象不再被引用。
JVM 通过设置 -Xnoclaagc 控制。
在大量使用反射、动态代理、CGLib等框架,动态生成JSP、OSGI等频繁定义ClassLoader场景下都需要JVM具备卸载类的功能,保证方法区不溢出。
2、垃圾回收算法
(1).标记-清除算法
最简单的算法。
不足:效率低;内存产生大量碎片。
(2). 复制算法 ( 新生代)
两块内存,每次使用一块,回收时候将对象从块A移到块B,再清理块A。
优点:简单、高效,无碎片。
缺点:内存使用率低。
新生代一般用的是复制算法。因为新生代中对象98%是“朝生夕死”,所以两块内存不需要1:1,而是使用1块比较大的Eden和2块比较小的Survivor。回收时,将Eden和Survivor A中还存活的数据移动到Survivor B,再清理Eden 和 Survivor A;再次回收时候,将Eden和Survivor B中还存活的数据移动到Survivor A,再清理Eden 和 Survivor B。JVM 默认Eden 和 Survivor的比例是8:1, 即Eden: Survivor A: Survivor B = 8 :0.5:0.5 。当回收时候 Survivor 不够用时候, 对象将被移动到老生代。
(3). 标记-整理算法 (老生代)
先标记,再让所有存活的对象向一端移动,然后直接清除端边界一外的内存。
(4). 分代收集算法
也就是新生代用 “复制" 算法,老生代用 “标记-整理” 或者 "标记-清除" 算法。
3、HotSpot 的JVM 算法的实现
4、垃圾收集器
(1). Serial 收集器 (新生代)
基于复制算法。
只使用单线程,在垃圾收集时候,必须暂停其他所有线程,直到其收集结束 (stop the world)。Serial Collector 是Client模式下,新生代的默认收集器。
(2). ParNew 收集器 (Parallel New 收集器) (新生代)
基于复制算法。
是Serial Collector 的多线程版本,也必须暂停其他所有线程。 ParNew Collector 是Server模式下,新生代的默认收集器。使用 -XX: UseParNewGC 打开。
当老生代使用 -XX:+UseConcMarkSweepGC 时候,新生代必须使用 Serial Collector 或 ParNew Collector ,默认是 ParNew Collector 。
默认开启的线城数目与CPU核数目一致。
(3). Parallel Scavenge 收集器 (新生代)
基于复制算法。
与ParNew Collector 相比,可以控制最大垃圾收集停顿时间-XX:MaxGCPauseMillis 和 吞吐量-XX: GCTimeRadio,-XX:
最大垃圾收集停顿时间-XX:MaxGCPauseMillis=500:停顿时间越短,则垃圾回收越频繁。
吞吐量-XX:GCTimeRadio=99:吞吐量 = (用户线程使用CPU时间) / (用户线程使用CPU时间 + GC线程使用CPU时间)
自适应调节策略 -XX:+UseAdaptiveSizePolicy: 开启后系统会使用当前系统的运行情况收集性能监控信息,动态调整停顿时间和吞吐量。
(4). Serial Old 收集器 (老生代)
基于标记-整理算法。
Serial 收集器的老生代版本。三个方面用途:
a. Client 模式下用
b. Server 模式下老版本的JVM 与Parallel Scavenge 搭配使用
c. 作为老生代CMS的后备方案,失败的时候使用。
(5). Parallel Old 收集器
基于“标记-整理”算法。
Parallel Scavenge 的老生代版本。一般用于 与新生代的 Parallel Scavenge 相搭配,整个系统吞吐量优先。
(6). CMS 收集器
基于“标记-清除”
Concurrent Mark Sweep ,以获取最短时间停顿为目标的收集器。互联网站和B/S系统的服务端,一般都用CMS,以保证服务的响应速度,停顿时间短,给用户带来较好地体验。
包括4个步骤:初始标记、并发标记、初始标记、并发清除。
优点:并发收集、低停顿。
缺点:
a. 对CPU资源敏感,即占用大量CPU资源,不会导致用户线程停顿但是会变慢;
b. 无法处理浮动垃圾,即在清理阶段产生的垃圾,这些垃圾必须在下一次GC时候回收,所以还必须预留部分空间,设置-XX:CMSInitiatingOccupancyFraction 来提前触发 (默认 92% 触发);
c. 收集结束会产生空间碎片,设置+UseCMSCompactAtFullCollection 整理空间碎片。
(7) G1 收集器 (新生代、老生代)
Garbage First ,未来可能替换CMS。特点:
a. 并发与并行
b. 分代收集
c.空间整合,使用“标记-整理”算法,不会产生空间碎片
d.可预测的停顿
现在还没有广泛运用。
5、配置参数总结
-XX:+UseSerialGC 使用 Serial + Serial Old 收集器
-XX:+UseParNewGC 使用ParNew + Serial Old 收集器
-XX:+UseConcMarkSweepGC 使用ParNew + CMS + Serial Old (失败后的预备方法)
-XX:+UseParallGC 使用 Parallel Scavenge + Serial Old
-XX:+UseParallelOldGC 使用 Parallel Scavenge + Parallel Old
-XX:SurvivorRatio=8 新生代中Eden 与 Survivor 的比例,默认8
-XX:PretenureSizeThreshold=1024 大于此参数的对象直接分配在老生代
-XX:MaxTenuringThreshold=5 新生代中对象年龄超过此值,移动到老生代
-XX:+UseAdaptiveSizePolicy 动态调整Java 堆中各区域大小 以及进入老生代的年龄
-XX:+HandlePromotionFailure 是否允许分配担保失败
-XX:ParallelGCThreads=10 GC并行线程个数
-XX:GCTimeRatio=98 GC吞吐量,仅在Parallel Scavenge 有效
-XX:MaxGCPauseMillis=500 GC最大停顿时间,仅在Parallel Scavenge 有效
-XX:CMSInitiatingOccupancyFraction=70 CMS在使用70%触发, 默认68%
-XX:+UseCMSCompactAtFullCollection CMS后进行内存整理
-XX:CMSFullGcsBeforeCompaction=5 CMS 5次后进行碎片整理
6. 理解GC日志
[GC :GC 没有发生Stop-The-World停顿
[Full GC :GC 发生了Stop-The_World
[Full GC(System) : GC 由System.gc() 调用
[DefNew : 新生代使用了默认的Serial 收集器 Default New Generation
[Tenured: 老生代 Tenured Generation
[Perm: 持久带 Perm Generation
[ParNew: 新生代使用ParNew收集器 Parallel New Generation
[PSYoungGen: 新生代使用Parallel Scavenge 收集器
3324K ->152K (3712K) : GC前为3324K -> GC后为152k (总容量为3712K)
[Times: user=0.01, sys=0.00 , real=0.02secs] : 用户态消耗的CPU时间、内核态消耗的CPU时间、墙钟时间。墙钟时间包括CPU时间与非运算的等待消耗,如等待磁盘IO、等待线程阻塞。
7、内存分配与回收策略
(1)、对象优先在Eden分配
大多数情况下,对象在新生代的Eden区分配,当Eden区没有足够的空间进行分配适合,虚拟机会进行一次 Minor GC。
配置:
-Xmx20M -Xms20M -Xmn10M -verbose:gc -XX:+PrintGCDetails -XX:SurvivorRatio=8
代码:
public class MinorGCTest { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocate1, allocate2, allocate3, allocate4; allocate1 = new byte[2*_1MB]; allocate2 = new byte[3*_1MB]; allocate3 = new byte[3*_1MB]; allocate4 = new byte[4*_1MB]; } }
输出:
[GC [PSYoungGen: 5792K->600K(9216K)] 5792K->5720K(19456K), 0.0022215 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 600K->0K(9216K)] [ParOldGen: 5120K->5589K(10240K)] 5720K->5589K(19456K) [PSPermGen: 2508K->2507K(21504K)], 0.0091615 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 9216K, used 6727K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 82% used [0x00000000ff600000,0x00000000ffc91c28,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen total 10240K, used 5589K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 54% used [0x00000000fec00000,0x00000000ff1754e0,0x00000000ff600000)
PSPermGen total 21504K, used 2517K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
object space 21504K, 11% used [0x00000000f9a00000,0x00000000f9c75520,0x00000000faf00000)
(2)、大对象直接进入老生代
可通过 -XX:PretenureSizeThreshold 设置。写程序应该避免大对象。
配置:
-Xmx20M -Xms20M -Xmn10M -verbose:gc -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728
代码:
public class PretenureSizeThresholdTest { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocate1; allocate1 = new byte[8*_1MB]; } }
输出:
Heap
PSYoungGen total 9216K, used 835K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 10% used [0x00000000ff600000,0x00000000ff6d0fc0,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400010,0x00000000ff600000)
PSPermGen total 21504K, used 2515K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
object space 21504K, 11% used [0x00000000f9a00000,0x00000000f9c74cd0,0x00000000faf00000)
(3)、长期存活的对象移动到老生代
通过-XX:MaxTenuringThreshold 设定。
配置
-Xmx20M -Xms20M -Xmn10M -verbose:gc -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
代码:
private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocate1, allocate2, allocate3, allocate4; allocate1 = new byte[ _1MB/4]; allocate2 = new byte[4*_1MB]; allocate3 = null; allocate3 = new byte[4*_1MB]; }
输出:
[GC[ParNew: 5024K->758K(9216K), 0.0021874 secs] 5024K->4856K(19456K), 0.0022406 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 9216K, used 5346K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
eden space 8192K, 56% used [0x00000000f9a00000, 0x00000000f9e7af60, 0x00000000fa200000)
from space 1024K, 74% used [0x00000000fa300000, 0x00000000fa3bdaa0, 0x00000000fa400000)
to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
concurrent mark-sweep generation total 10240K, used 4098K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
concurrent-mark-sweep perm gen total 21248K, used 2519K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
(4)、动态对象年龄判定
为了能更好地适应不同程序的内存情况,虚拟机并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才进入老生代。如果在Survivor 空间中相同年龄所有对象大小的总和大于Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老生代,无需等到MaxTenuringThreshold 。
(5)、空间分配担保
在发生Minor GC 之前, 虚拟机会先检查老生代最大可用的连续空间是否大于新生代所有对象总空间,如果成立,则Minor GC可用确保是安全的。在JDK 1.6_24 之后,只要大于,则进行Minor GC, 否则则进行Full GC。
8. 新生代GC 和 老生代GC 各自特点
新生代GC:Minor GC,因为新生代的对象大多数朝生夕灭,所以非常频繁,回收速度也很快。
老生代GC:Major GC/Full GC, 其速度比 Minor GC 慢10倍以上。