算法过程:每个对象有一个引用计数器,当对象被引用一次计数器就加一,引用失效就减一,对于计数器为0的对象表示为垃圾对象,可以被GC回收。
缺点:无法解决循环引用的问题,例如:A引用了B,B引用了A,但是A和B都没有被其他对象引用,这样就会导致内存泄漏,无法被回收。
算法过程:
通过一系列被称为GC Roots的对象作为起点开始搜索,所经过的路径被称为引用链,当一个对象没有跟任何一个引用链相连接的时候,表示从GC Roots对象到这个对象不可达,意味着这是一个垃圾对象可以被回收。
适用场景:Java虚拟机是采用这种算法对垃圾进行回收,解决了循环引用的问题。
可以作为GC Roots对象有:虚拟机栈,静态成员,常量,本地方法栈引用的对象。
算法过程:首先标记出需要回收的对象,标记完成后统一回收。
缺点:主要有两个缺点,一个是标记和清除两个过程的效率都不算高(据资料显示)。另一个是空间问题,标记清除后,会产生不连续的内存碎片,当需要分配大对象时,无法找到足够连续的内存,导致分配失败提前触发GC。
算法过程:将内存分为两部分,每次只使用其中一块内存。回收时将存活的对象复制到另一块区域,之后将已使用的内存区域一次性全部清理掉。
优点:解决了空间碎片的问题,存活对象少时,提升了回收效率。
缺点:一个是内存使用率缩小了,因为永远有一块空闲的内存备用。另一个是当存活对象较多时复制效率低下。
算法过程:标记整理的算法过程跟标记清除的标记过程是一样的,但标记后是将存活的对象都想某一端移动,然后清理边界以外的内存。
优点:解决了内存碎片的问题。
概念:JVM内存区域是分代的,分为:年轻代、老年代、永久代(元数据区)和两个幸存区。根据不同的算法特点,在不同分代中使用不同的算法进行垃圾回收。
年轻代、幸存区:由于Java对象在很多场景下,都是朝生夕死的,所以在这几个区间适合使用复制算法。
老年代、永久代:永久代或者元数据区用于存储一些类信息、常量池等数据,老年代一般也是存活比较久的对象,因此适合用标记清除和标记整理算法。
老年代收集器,需要配合Serial或者ParNew使用,一般是与ParNew使用。CMS全称Concurrent Mark-Sweep。CMS出现的目标是为了降低延迟,减少回收停顿时间。适合对延迟、停顿时间敏感的应用使用。通过 -XX:+UseConcMarkSweepGC开启。
CMS-initial-mark(STW):通过可达性分析算法,从GC Roots对象开始扫描能够直接关联到的对象,并做标记,需要STW,但是一般会很快完成。
CMS-concurrent-mark:从初始标记的基础上,继续向下搜索并做标记,这个阶段应用线程和GC线程并发执行,不会造成停顿。
CMS-concurrent-preclean:查找并发标记阶段中,新进入老年代的对象,包括晋升到老年代和直接在老年代分配的对象,目的是减少下一个阶段重新标记的时间。
CMS-concurrent-abortable-preclean:可中断的预清理,这个阶段做的事情跟并发预清理的事情一样,目的是为了减少下一个阶段STW的时间,这个阶段有几个个条件控制何时结束。
A、-XX:CMSScheduleRemarkEdenSizeThreshold=2,该阶段在Eden区占用超过2M时启动。
B、-XX:CMSMaxAbortablePrecleanTime=5000,设置一个最大时间最大执行5秒钟。
C、-XX:CMSScheduleRemarkEdenPenetration=50,Eden区使用率超过50就停止该阶段进入remark阶段。
D、-XX:+CMSScavengeBeforeRemark,控制remark阶段前进行一次minor gc,来提高remark的效率,减少时间。
CMS-remark(STW):重新标记,处理上几次以来可能引用关系发生变化的部分,并重新进行标记,这个阶段会停止应用线程
CMS-concurrent-sweep:真正的清理阶段,将以上几个步骤标记的无法访问的对象进行并发清理,并将清理的空间回收到空闲列表中
CMS-concurrent-reset:调整堆大小,重置一些内部的数据结构,为下一次回收做准备
A、无法处理浮动垃圾,因为这是一款并发的收集器,程序运行和收集的同时都会产生垃圾,所以回收时不能向其他收集器一样等到满了的时候再收集,通过-XX:CMSInitiatingOccupancyFraction=65设置触发的百分比,留出一定的空间给并发收集的时候使用,当CMS运行期间无法满足程序的需要,则会出现,此时会临时使用Serial Old作为临时方案进行一次标记整理的回收,这样就会使得出现较为长期的停顿,所以-XX:CMSInitiatingOccupancyFraction=65不能设置太高。
B、由于是使用标记清除算法的收集器,因此会产生内存碎片,为了解决这个问题,垃圾收集器提供了一个-XX:+UseCMSCompactAtFullCollection的开关参数,表示再一次FGC时进行一次内存整理的过程,这个过程是无法并发的,会拉长停顿时间,收集器还提供另一个参数,-XX:CMSFullGCsBeforeCompaction=5表示FGC几次后进行一次内存整理。
优化目标:减少STW停顿时间
优化手段:
A、降低触发CMS回收的百分比,给并发收集时留出一定的空间,避免Concurrent Mode Failure,通过-XX:CMSInitiatingOccupancyFraction=65配置
B、开启内存压缩,避免碎片问题无法分配大内存对象,通过-XX:+UseCMSCompactAtFullCollection和-XX:CMSFullGCsBeforeCompaction=5,具体数值可以按需调配。
C、减少CMS-remark(STW)阶段的时间,通过-XX:+CMSScavengeBeforeRemark、-XX:CMSMaxAbortablePrecleanTime=5000、-XX:CMSScheduleRemarkEdenPenetration=50进行微调。
参数 | 说明 |
---|---|
-Xloggc:/dev/shm/xxx_gc.log | 设置GC日志路径 |
-XX:+PrintGCDateStamps | 打印GC时间 |
-XX:+PrintGCDetails | 打印GC详细信息 |
-XX:+PrintGCApplicationStoppedTime | 打印应用停顿时间 |
-XX:+PrintGCApplicationConcurrentTime | 打印应用当前时间 |
-XX:+PrintSafepointStatistics | 打印安全点统计信息 |
-XX:+UnlockDiagnosticVMOptions | 解锁诊断型参数 |
-XX:-DisplayVMOutput | 禁用控制台输出VM信息,例如安全点日志 |
-XX:+LogVMOutput | 启用日志输出VM日志 |
-XX:LogFile=/dev/shm/safepoint.log | 设置VM日志输出路径 |
-XX:CMSScheduleRemarkEdenPenetration=20 | 设置Eden区使用率超过20%就进入remark阶段 |
-XX:CMSMaxAbortablePrecleanTime=1500 | 设置可中断预清理阶段最大执行时间 |
-XX:+CMSScavengeBeforeRemark | 设置期望进入remark阶段前进行一次MinorGC |
-XX:+PrintReferenceGC | 打印引用处理信息 |
-XX:+UseConcMarkSweepGC | 使用CMS收集器 |
-XX:+UseParNewGC | 使用ParNew收集器 |
-XX:+CMSParallelRemarkEnabled | remark阶段采用并行多线程模式 |
-XX:+UseCMSCompactAtFullCollection | 开启压缩整理功能 |
-XX:CMSFullGCsBeforeCompaction=0 | 设置CMS多少次后进行一次压缩 |
-XX:+CMSClassUnloadingEnabled | 开启回收永久代 |
-XX:+UseCMSInitiatingOccupancyOnly | 设置CMS回收时机一直按照Fraction配置进行 |
-XX:CMSInitiatingOccupancyFraction=60 | 设置老年代内存使用率超过60时开始CMS回收 |
-XX:+AlwaysPreTouch | 启动时申请所需内存,避免系统调用延迟 |
以下是一次完整的CMS回收日志,需要配置以下参数
2019-07-17T21:54:50.216+0800: 190007.616: [GC [1 CMS-initial-mark: 1258440K(2097152K)] 1294890K(3984640K), 0.0365050 secs] [Times: user=0.04 sys=0.01, real=0.04 secs]
2019-07-17T21:54:50.253+0800: 190007.654: Total time for which application threads were stopped: 0.0455330 seconds
2019-07-17T21:54:50.254+0800: 190007.654: [CMS-concurren
t-mark-start]
2019-07-17T21:54:50.413+0800: 190007.813: [CMS-concurrent-mark: 0.159/0.159 secs] [Times: user=0.91 sys=0.02, real=0.16 secs]
2019-07-17T21:54:50.413+0800: 190007.814: [CMS-concurrent-preclean-start]
2019-07-17T21:54:50.413+0800: 190007.814: [Preclean SoftReferences, 0.0005560 secs]2019-07-17T21:54:50.414+0800: 190007.814: [Preclean WeakReferences, 0.0011690 secs]2019-07-17T21:54:50.415+0800: 190007.815: [Preclean FinalReferences, 0.0000350 secs]2019-07-17T21:54:50.415+0800: 190007.815: [Preclean PhantomReferences, 0.0000070 secs]2019-07-17T21:54:50.425+0800: 190007.825: [CMS-concurrent-preclean: 0.011/0.011 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
2019-07-17T21:54:50.425+0800: 190007.826: [CMS-concurrent-abortable-preclean-start]
CMS: abort preclean due to time 2019-07-17T21:54:51.938+0800: 190009.338: [CMS-concurrent-abortable-preclean: 1.508/1.512 secs] [Times: user=2.46 sys=0.16, real=1.51 secs]
2019-07-17T21:54:51.938+0800: 190009.338: Application time: 1.6846120 seconds
2019-07-17T21:54:51.946+0800: 190009.346: [GC[YG occupancy: 351451 K (1887488 K)]2019-07-17T21:54:51.946+0800: 190009.347: [GC2019-07-17T21:54:51.949+0800: 190009.350: [ParNew2019-07-17T21:54:51.979+0800: 190009.379: [SoftReference, 2284 refs, 0.0002550 secs]2019-07-17T21:54:51.979+0800: 190009.379: [WeakReference, 5 refs, 0.0000150 secs]2019-07-17T21:54:51.979+0800: 190009.379: [FinalReference, 679 refs, 0.0002280 secs]2019-07-17T21:54:51.979+0800: 190009.380: [PhantomReference, 0 refs, 0.0000120 secs]2019-07-17T21:54:51.979+0800: 190009.380: [JNI Weak Reference, 0.0000100 secs]: 351451K->23085K(1887488K), 0.0330260 secs] 1609892K->1282310K(3984640K), 0.0370030 secs] [Times: user=0.26 sys=0.01, real=0.04 secs]
2019-07-17T21:54:51.984+0800: 190009.384: [Rescan (parallel) , 0.0272010 secs]2019-07-17T21:54:52.011+0800: 190009.411: [weak refs processing2019-07-17T21:54:52.011+0800: 190009.411: [SoftReference, 5341 refs, 0.0020150 secs]2019-07-17T21:54:52.013+0800: 190009.413: [WeakReference, 0 refs, 0.0000220 secs]2019-07-17T21:54:52.013+0800: 190009.413: [FinalReference, 80 refs, 0.0002020 secs]2019-07-17T21:54:52.013+0800: 190009.414: [PhantomReference, 0 refs, 0.0000120 secs]2019-07-17T21:54:52.013+0800: 190009.414: [JNI Weak Reference, 0.0000230 secs], 0.0023470 secs]2019-07-17T21:54:52.013+0800: 190009.414: [class unloading, 0.0198310 secs]2019-07-17T21:54:52.033+0800: 190009.434: [scrub symbol table, 0.0100610 secs]2019-07-17T21:54:52.043+0800: 190009.444: [scrub string table, 0.0011130 secs] [1 CMS-remark: 1259224K(2097152K)] 1282310K(3984640K), 0.1204410 secs] [Times: user=0.48 sys=0.02, real=0.12 secs]
2019-07-17T21:54:52.067+0800: 190009.468: Total time for which application threads were stopped: 0.1293860 seconds
2019-07-17T21:54:52.068+0800: 190009.468: [CMS-concurrent-sweep-start]
2019-07-17T21:54:52.747+0800: 190010.147: [CMS-concurrent-sweep: 0.679/0.679 secs] [Times: user=1.24 sys=0.09, real=0.68 secs]
2019-07-17T21:54:52.747+0800: 190010.147: [CMS-concurrent-reset-start]
2019-07-17T21:54:52.753+0800: 190010.153: [CMS-concurrent-reset: 0.006/0.006 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
通用字段解析
日志内容 | 含义 |
---|---|
2019-07-17T21:54:50.216+0800 | GC发生的时间,系统当前时间 |
190007.616 | 虚拟机从启动到现在经过的时间 |
[Times: user=0.04 sys=0.01, real=0.04 secs] | 当前过程耗时情况,user表示运行用户代码时间,sys表示系统调用以及系统级等待的时间,real表示实际时间 |
CMS-initial-mark
日志内容 | 含义 |
---|---|
1258440K(2097152K) 1258440K | 回收前老年代占用大小,2097152K:老年代总大小 |
1294890K(3984640K) 1294890K | 整堆内存占用大小,3984640K:堆内存总大小 |
CMS-concurrent-mark
日志内容 | 含义 |
---|---|
[Preclean SoftReferences, 0.0005560 secs] | 该阶段处理软引用的时间 |
[Preclean WeakReferences, 0.0011690 secs] | 该阶段处理弱引用的时间 |
[Preclean FinalReferences, 0.0000350 secs] | 该阶段处理最终引用的时间 |
[Preclean PhantomReferences, 0.0000070 secs] | 该阶段处理虚引用的时间 |
[CMS-concurrent-preclean: 0.011/0.011 secs] | 预清理阶段耗费的时间 |
CMS-concurrent-abortable-preclean
日志内容 | 含义 |
---|---|
[CMS-concurrent-abortable-preclean: 1.508/1.512 secs] | 可中断预清理阶段耗费的时间,该阶段并行,不会STW |
YG occupancy
日志内容 | 含义 |
---|---|
GC[YG occupancy: 351451 K (1887488 K) | 表示该阶段是MinorGC,351451表示年轻代占用大小,1887488年轻代总大小 |
351451K->23085K(1887488K), 0.0330260 secs | 351451K年轻代回收前大小,23085K年轻代回收后大小,1887488K年轻代总大小,耗时0.0330260 |
CMS-remark
日志内容 | 含义 |
---|---|
[Rescan (parallel) , 0.0272010 secs] | 重新标记花费的时间 |
[class unloading, 0.0198310 secs] | 类卸载花费的时间 |
[scrub symbol table, 0.0100610 secs] | 刷新符号表耗费的时间 |
[scrub string table, 0.0011130 secs] | 刷新常量池耗费的时间? |
[1 CMS-remark: 1259224K(2097152K)] | 1282310K(3984640K), 0.1204410 secs] 1259224K老年代占用大小,2097152K老年代总大小,1282310K堆内存占用大小,3984640K堆内存总大小,耗时0.1204410s |
/**
* 堆溢出测试
* -Xms100m -Xmx100m -XX:+UseConcMarkSweepGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof
*
* @author Horace
*/
public class HeapOOMTest {
private static Logger logger = LoggerFactory.getLogger(HeapOOMTest.class);
public static void main(String[] args) {
int _1M = 1024 * 1024;
List<byte[]> bytes = new ArrayList<>();
while (true) {
bytes.add(new byte[_1M]);
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000));
}
}
}
本地环境界面查看工具 jvisualvm
每1秒输出一次GC情况
jstat -gccause pid 1s
以下是GC情况
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC
89.83 0.00 42.05 37.10 94.37 90.41 2 0.025 0 0.000 0.025 Allocation Failure No GC
89.83 0.00 46.58 37.10 94.37 90.41 2 0.025 0 0.000 0.025 Allocation Failure No GC
89.83 0.00 51.00 37.10 94.37 90.41 2 0.025 0 0.000 0.025 Allocation Failure No GC
89.83 0.00 54.74 37.10 94.37 90.41 2 0.025 0 0.000 0.025 Allocation Failure No GC
89.83 0.00 58.49 37.10 94.37 90.41 2 0.025 0 0.000 0.025 Allocation Failure No GC
89.83 0.00 63.69 37.10 94.37 90.41 2 0.025 0 0.000 0.025 Allocation Failure No GC
89.83 0.00 67.44 37.10 94.37 90.41 2 0.025 0 0.000 0.025 Allocation Failure No GC
89.83 0.00 71.19 37.10 94.37 90.41 2 0.025 0 0.000 0.025 Allocation Failure No GC
89.83 0.00 75.71 37.10 94.37 90.41 2 0.025 0 0.000 0.025 Allocation Failure No GC
89.83 0.00 79.46 37.10 94.37 90.41 2 0.025 0 0.000 0.025 Allocation Failure No GC
89.83 0.00 83.98 37.10 94.37 90.41 2 0.025 0 0.000 0.025 Allocation Failure No GC
89.83 0.00 87.72 37.10 94.37 90.41 2 0.025 0 0.000 0.025 Allocation Failure No GC
89.83 0.00 92.14 37.10 94.37 90.41 2 0.025 0 0.000 0.025 Allocation Failure No GC
各个字段表示的含义可以查看:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html#BEHHGFAE
命令行dump内存快照
jmap -heap dump:live,format=b,file=/tmp/dump.hprof
dump出来的内存可以通过jprofile、mat、jhat等工具进行分析
http://jdk.java.net/java-se-ri/8
hotspot/src/share/vm/gc_implementation/concurrentMarkSweep 包中
hotspot/src/share/vm/gc_implementation/concurrentMarkSweep/concurrentMarkSweepGeneration.cpp
在这个文件的4550行,可以看到关于abortable_preclean阶段的实现 void CMSCollector::abortable_preclean()