目录
Concurrent Mark-Sweep(CMS) 收集器
基础知识
JVM运行时内存消耗是指哪些方面
JVM运行时内存消耗通常包含以下几个部分:
- Heap:我们常说的JVM运行时堆内存(通过-Xmx, -Xms参数控制)
- 所有线程占用的Thread Stack:Java的每一个线程最多会占用一个Thread Stack大小的内存(通过-Xss参数控制)
- Direct Memory:一些网络编程框架常会使用,如,Netty(通过-XX:MaxDirectMemorySize参数控制)
- JNI Native Memory:一些通过JNI实现的库会使用,如,压缩用到的Deflater
- Meta Space(JDK8)/ Perm Gen(JDK7)(通过-XX:MaxPermSize、-XX:MaxMetaspaceSize参数控制)
- Code Cache:用于编译和保存本地代码(native code)的内存(通过-XX:ReservedCodeCacheSize参数控制)
- JVM自身使用的内存:存储一些JVM内部的数据结构(如,GC用到的数据结构)
内存布局如下图:
分代内存管理
Hotspot虚拟机的heap内存被划分成Young区和Old区。
- Young区:包含一个Eden区和两个Survivor区,当此区域用完时会发生Young GC(或者叫做Minor GC)。绝大部分对象都在Eden区分配,至少经过一次Young区垃圾回收后依然存活的对象会被移到Survivor区。两个Survivor区同一时间只有一个包含了对象(叫做From区),另外一个为空(叫做To区)。每一次Young GC会把Eden区以及From区存活的对象Copy到To区,GC结束后From区和To区交换,原来的From区变成To区(此时为空),原来的To区叫做From区(此时包含了GC后Eden区及原From区依然存活的对象)。如下图
- Old区:在Survivor区存活超过一定次数的对象会被移到Old区,也有一部分大对象直接在Old区进行分配。当此区域用完时会发生Full GC(或者叫做Major GC),Full GC发生时会对Young区和Old区都进行垃圾回收
Stop the World(STW)
进程内的所有应用线程停止运行,如果发生STW的时候一个线程正在处理请求(如,一个远程调用请求),则此请求只有STW结束后才会继续执行,所以此请求的处理时间一定大于STW的时间。
Concurrent 和 STW 的GC对比
- Concurrent GC在某些GC阶段还是需要STW的,详见下面CMS的介绍
- 由于Concurrent GC执行时,GC线程和应用线程同时运行,所以需要关注两个问题:1. CPU使用,GC线程会消耗CPU导致系统CPU变高,当系统CPU不足时可能会导致应用线程的CPU时间片被GC线程抢占而导致处理变慢;2. 因为GC发生时应用线程仍在运行,同时也会产生内存分配,所以需要提前进行Concurrent GC,保证heap内可用内存足以支撑正在同时运行的应用线程对内存分配的需求。否则,Concurrent GC会失败,然后重新进入STW的GC
Young GC(Minor GC)
- 不管哪种GC算法,Young GC都会STW
- 存活对象会被Copy到Survivor区,GC后的Eden区和另外一个Survivor区只包含不再被引用的对象,这两块区域可以被完全重新使用而不需要进行内存碎片整理。所以Young GC的耗时只和Young区存活的对象数量和引用关系复杂度有关,和Young区大小无关
- 在Survivor区存活时间超过一定次数(-XX:MaxTenuredThreshold)的对象将会移动到Old区
- 如果存活对象大于一个Survivor区,则全部移动到Old区
Full GC(Major GC)
- 对整个Heap进行回收,不仅限于Old区
- 耗时和整个Heap大小、存活对象数量,对象之间引用关系有关
常用GC
- Young区
- 串行Copy收集器(-XX:+UseSerialGC):单线程,STW,Old区可以使用串行收集器或者CMS收集器
- PS Scavenge收集器(-XX:+UseParallelGC):并行,STW,Old区可以使用串行收集器或者PS MarkSweep收集器
- ParNew收集器(-XX:+UseParNewGC):并行,STW,Old区可以使用串行收集器或者CMS收集器
- G1收集器(-XX:+UseG1GC):并行,STW,Old区也使用G1
- Old区
- 串行MarkSweepCompact收集器(-XX:+UseSerialGC):单线程,STW
- PS MarkSweep收集器(-XX:+UseParallelOldGC,设置了这个后自动会设置-XX:+UseParallelGC):并行,STW
- Concurrent Mark-Sweep收集器(CMS, -XX:+UseConcMarkSweepGC):部分阶段并行(STW),部分阶段并发
- G1(-XX:+UseG1GC)收集器:部分阶段并行(STW),部分阶段并发
并行收集器
- 整个回收过程STW
- 由于整个过程STW,不需要担心回收过程中产生新的对象,且CPU资源可以被GC线程充分利用,所以回收的效率和吞吐都比较高,适合于后台运算而不需要太多交互(对响应时间要求不高)的应用(如后台批处理任务,异步消息消费者等)
Concurrent Mark-Sweep(CMS) 收集器
-
由于是Concurrent的,所以适合对响应时间要求高,且CPU资源比较充裕的应用
-
需要考虑的点请参看上面关于“Concurrent和STW的GC对比”
- 当剩余内存不足以完成Concurrent回收时,退化成单线程的Full GC
- 不一定会进行内存整理,所以需要考虑内存碎片问题
- 整个过程中依然存在STW阶段,Remark阶段STW且多线程并行,见下图
G1收集器
- 设计初衷用于替换CMS收集器,适合大堆(>=6G)
- 和CMS收集器类似,可以并发执行,但是G1会进行内存整理
- STW时间可控
- 当发生Full GC时单线程执行
常用JVM参数
参数 | 描述 |
---|---|
-Xms | 初始堆大小 |
-Xmx | 最大堆大小 |
-Xmn | Young区大小(初始和最大值均为此值) |
-Xss | 每个线程的Stack大小 |
-XX:PermSize=size | 初始Perm Gen大小 |
-XX:MaxPermSize=size | 最大Perm Gen大小 |
-XX:NewSize=size | 初始Young区大小 |
-XX:MaxNewSize=size | 最大Young区大小 |
-XX:MetaspaceSize=size | 当meta space大小超过此值时,会触发一次GC |
-XX:MaxMetaspaceSize=size | 最大meta space大小 |
-XX:ReservedCodeCacheSize=size | 最大Code Cache大小 |
-XX:+HeapDumpOnOutOfMemoryError | 当发生OOM时,产生heap dump |
-XX:HeapDumpPath=path | 设置当参数-XX:+HeapDumpOnOutOfMemoryError打开时heap dump文件存放的路径和文件名 |
-XX:NewRatio=ratio | 设置young区和old区的比例 |
-XX:+PrintGC | 每次GC发生时打印GC信息 |
-XX:+PrintGCTimeStamps | 每次GC发生时打印时间戳 |
-XX:+PrintGCDetails | 每次GC发生时打印GC详细信息 |
-XX:SurvivorRatio=ratio | 设置eden区和survivor区的比例 |
常用JVM工具
jps
用来查看基于HotSpot的JVM里面中,所有具有访问权限的Java进程的具体状态, 包括进程ID,进程启动的路径及启动参数等等,与unix上的ps类似,但是jps仅显示Java进程。
jstat
用于监控基于HotSpot的JVM,对其堆的使用情况进行实时的命令行的统计。
常用模式
-
-
- jstat -gc {进程号}:垃圾回收的行为统计
- jstat -gccapacity {进程号}:同-gc,还会输出Java堆各区域使用到的最大、最小空间
- jstat -gcutil {进程号}:同-gc,输出的是已使用空间占总空间的百分比
- jstat -gccause {进程号}:垃圾收集统计概述(同-gcutil),附加最近两次垃圾回收事件的原因
- jstat -gcnew {进程号}:统计新生代行为
- jstat -gcold {进程号}:统计老年代行为
-
jmap
打印出某个java进程(使用pid)内存中所有对象的情况(如:产生那些对象,及其数量)。
可以用于产生heap dump
常用模式
-
-
- jmap -dump:live,format=b,file={文件路径和文件名} {进程号}:dump heap到文件,format指定输出格式,live指明是仅包含存活的对象(注意:加上此参数会触发一次Full GC)
- jmap -heap {进程号}:打印heap的概要信息,GC使用的算法,heap的配置及使用情况,可以用此来判断内存目前的使用情况以及垃圾回收情况
- jmap -histo {进程号}:打印堆的对象统计,包括对象数、内存大小等等。jmap -histo:live 这个命令执行,JVM会先触发gc,然后再统计信息
- jmap -permstat {进程号}:打印Java堆内存的永久区的类加载器的智能统计信息。对于每个类加载器而言,它的名称、活跃度、地址、父类加载器、它所加载的类的数量和大小都会被打印。此外,包含的字符串数量和大小也会被打印。
-
jstack
jstack用于打印出给定的java进程ID的Java堆栈信息
常用模式
-
- jstack {进程号}:打印此Java进程此时的所有运行时线程的堆栈信息
MAT(Memory Analyzer (MAT) )
用于分析heap dump文件,可以用来排查内存泄漏问题、也可以用来对java进程的内存使用情况进行分析
Flight Recorder
用于收集Java进程运行时的诊断信息,内存、cpu使用情况以及各种JVM内部和系统级别的事件,以便对此Java进程进行 Profiling。场景举例如下:
-
- 查看运行时哪些类分配内存最多,方便进行内存使用调优
- 查看运行时哪些执行路径占用最多CPU,方便进行CPU调优
- 查看上下文切换情况
- 查看IO情况
- ...
以上文章转载自JVM内功心法,供大家参考学习哈!