1. 前言
网上关于jvm gc的文章有很多,写这篇文章不是有什么新东西要讲,主要原因是工作时也偶尔碰到比如full gc,jvm参数设置或者使用jdk自带的一些命令查看gc或者内存占用等,每次碰到这种情况都要百度一番,看一些文章,但是不记下来久而久之就遗忘了,等到下次出现时又需要查看一番。本文主要做一些笔记,下次就不用去百度了。
注:本文内容主要来自于博客(在最后参考部分已经列出)以及「深入理解java虚拟机」一书。
2. GC算法和垃圾回收器
2.1 GC类型
- partial gc
即只对内存的一部分进行GC,主要包括- Young GC
- Old GC CMS垃圾收集器专有,其他的old区收集器进行gc等同于fullgc,会同时收集young,old和permenant
- MixGC G1专有,收集部分young区和部分old区
- full gc
收集young,old和permenant区
2.2 GC算法
2.2.1 标记清除
标记清除算法分为"标记"和“清除”两个阶段,“标记”阶段标记出可以回收的对象,“清除”阶段回收被标记的内存。过程如下图:
存在的问题
注:图片来自于图片出处
- 清除的内存比较分散,回收起来比较耗时
- 清除之后存在大量的内存碎片。
2.2.2 复制算法
hotspot新生代垃圾收集器基本采用这种算法,理论上它将内存分为大小相同的两块(实际上实现时不是按这种比例划分),分配内存时始终只使用其中一块,内存不够用时进行GC,将当前使用的内存中存活的对象复制到另一块空闲内存上,然后集中清空当前使用内存即可。如下图所示:
注图片出处
优点:
由于每次回收都会将存活对象复制到另一块区域的连续空间上,一方面不会有内存碎片存在,没有内存碎片的存在,分配新的内存时只需要在在连续的未使用区域移动堆顶指针,按序分配内存即可。
实际上垃圾回收器使用这种算法回收年轻代时,并不是按照1:1划分内存的,而是将内存划分为一块Eden区和两块Survivor, Hotspot默认Eden:Suvivor-1:Survivor-2=8:1:1,分配内存时只使用Eden区和其中一块Survivor,GC时对Eden区和这块正在使用的Survivor进行回收,然后将存活的对像复制到另一块未使用的Survivor上。
2.2.3 标记-整理算法
标记整理算是Hotspot回收老年代的算法,如下图所示:
注图片出处
如上图,标记整理算法不将内存分为两块,它使用全部的内存,在垃圾回收时它将存活的对象移动的内存的一端,然后清理掉边界以外未使用的内存。
2.2.4 分代收集
分代收集算法不是一种新的算法,而是使用上面算法的组合去管理内存的不同区块,分代算法将内存分为年轻代和老年代,各自使用不同的收集算法。
Hotspot中将内存分为年轻代(Young)和老年代(Tenure),年轻代又被划分为Eden区和2个Survivor区。年轻代采用复制算法,老年代采用标记-整理算法。如下图所示:
2.2 垃圾回收器
下图是Hotspot虚拟机包含的垃圾回收器:
注图片出处
实线分割线上面是年轻代垃圾回收器,下面是老年代垃圾回收器。G1没有传统的内存分代。
虚线连接线表示他们可以搭配共同工作完成对年轻代和老年代的垃圾回收。
2.2.1新生代垃圾回收器
并不打算写,参考垃圾回收器
1. Serial
使用复制算法,这种垃圾回收器会阻塞其他所有工作线程
- -XX:+UseSerialGC启用Serial垃圾回收器
2. ParNew
Serial的并行版本,它进行垃圾回收时也会暂停其他的工作线程,和Serial的区别就是它使用多个线程并行回收。
- -XX:+UseParNewGC 启用ParNew垃圾收集器
- -XX:ParallelGCThreads=10 设置用来并行收集的线程数
3. Parallal Scavenge
这是一个吞吐量优先的新生代回收器,所谓吞吐量:
(CPU运行用户程序时间) / (运行用户代码时间 + 垃圾回收时间)
吞吐量优先说明它适合计算型任务(比如Spark的executor使用这种回收器),相应时间有限适合交互的程序,很多网站的web后台使用ParNew。
- -XX:+UseParallelGC 启用Parallal Scavenge回收器
- -XX:MaxGCPauseMillis=100 新生代垃圾回收最大停顿时间
单位ms, 这是一个软指标,收集器会try best达到目标
- -XX:GCTimeRatio=10 垃圾回收时间占时间比率
它是一个(0,100)之间的整数值,上面表示GC占总时间10%
- -XX:+UseAdaptiveSizePolicy 指定有虚拟机动态调节年轻代大小
Parallal Scavenge尽量达到用户的吞吐量设置要求,通过这个选项虚拟机动 态调节年轻代大小,间接做到控制gc时间。 即然动态调节年轻代大小,就不需要显式通过-Xmn, -XX:SurvivorRatio来调节年轻代大小了 同时参数-XX:PretenureSizeThreshold在Parallal Scavenge下无效
2.2.2老年代垃圾回收器
1. Serial Old
Serial的老年代版本,使用标记-整理算法,单线程收集,Stop-the-world。
在CMS垃圾回收器出现Concurrent Mode Failure
后作为CMS的备选。
2. ParallelOld
Parallel Scavenge的老年代版本,标记-整理算法,使用多线程回收,Stop-the-world,
- -XX:+UseParallelOldGC 开启Parallel Old垃圾回收器
- -XX:ParallelGCThreads=10 设置用来并行收集的线程数
- -XX:+ScavengeBeforeFullGC 这个选项是ParallelOld独有的,默认是开启的,在FullGC前触发一次YoungGC,是为了加快FullGC,在FullGC前先来一次YoungGC也是ParallelOld独有的。
2. CMS
使用标记-清除算法(这种算法会产生内存碎片),回收过程尽力过个步骤如下:
注图片出处
初始标记和重新标记阶段,都是Stop-the-world,但是时间很短,不像其他两个整个过程都是Stop-the-world
由于并发清除和用户线程同时运行,也就是说清理阶段可能会有新的内存分配和新的垃圾产生,这种垃圾称为“浮动垃圾”,显然这中垃圾只能等到下一次GC了。并发清除过程中新的内存分配产生也就意味着CMS不能像其他垃圾回收器一样:在老年大沾满了之后再回收,CMS必须在老年代有一定的预留空间是回收,默认是老年代达到68%时开始回收。如果预留空间不够会触发"Concurrent Mode Failture",此时CMS退化为Serial Old垃圾回收器。
由于CMS采用标记-清除,所以会产生内存碎片
- -XX:+UseConcMarkSweepGC 开启CMS
- -XX:CMSinitiatingOccupancyFraction=70
设置触发CMS垃圾回收时的老年代占用,越高以为着预留空间越小,越有可能发生“Concurrent Mode Failture” 过低可能会导致过多的老年代回收次数
- -XX:+UseCMSCompactAtFullCollection
CMS内存碎片回到导致没有足够连续空间,触发FullGC,这个选项设置在FullGC后进行内存整理
- -XX:CMSFullGCsBeforeCompaction=5
配合上面参数一起使用,在触发多少次FullGC之后再进行内存整理
- -XX:+CMSScavengeBeforeRemark
注:其他的老年代垃圾回收器对老年代的回收其实等价于FullGC,但是CMS在concurrent collection阶段只对old区回收,这个选项能让它进行一次YoungGC, 避免过多的年轻代对老年代的跨代引用影响old回收时间。在重新标记阶段之前进行一次YoungGC,有时候young区对old区的跨代引用也会导致cms对old区回收效果差。
2.3 G1垃圾回收器
G1是Garbage First的简称,G1垃圾回收器对内存的划分和上面几种都不一样,如下图所示:
如上图,G1将内存分为一个个的Region,尽管依然存在eden,survivor,old区,但是它们不是连续的分布,其中Humongous用来分配给特别大的对象(大雨region的一半)。
G1在GC时采用一种启发式的算法,他根据指定的jvm参数MaxGCPauseMillis
(最大垃圾回收停顿时间)来决定本次收集多少内存(这个和ParallelOld还是挺像的)
关于G1的原理,以及G1的日志参数可以参考:
1 . 深入理解 Java G1 垃圾收集器
2 . G1 垃圾收集器入门
3 . 理解G1垃圾收集器日志
G1相关参数:
- -XX:+UseG1GC 启用G1
- -XX:MaxGCPauseMillis=n gc最大停顿时间
- -XX:G1HeapRegionSize=n G1的region大小,1M到32M之间,最多有2048个Region
- -XX:G1ReservePercent=n old区预留内存给young区晋升对象
- -XX:ParallelGCThreads=n并发标记线程数
- -XX:ConcGCThreads=n 并发回收线程数
注:由于G1的gc(YoungGC和MixGC)会扫描整个young区,而GC控制垃圾收集时间的方式是通过通知yong区的region个数来达到的,所以不要使用-Xmn或者-XX:NewSize或-XX:NewRatio去指定young区大小,不然就扫描整个young内存了,耗时
2.4 jvm参数
1. heap大小相关
- -Xms2G 设置最小可用内存大小,这里表示设置成2G
- -Xmx4G 设置最大可用内存大小
- -Xmn1G 设置年轻代大小
另一种设置方式: -XX:newSize=1G 设置年轻代最小可用内存1G -XX:MaxnewSize=1G 设置年轻代最大可用内存 -Xmn相当于上两个参数组合,相当于将这两个设置为同一个值
- -XX:NewRatio=4 设置老年代/年轻代的比值,默认比值是2.
这个比值和-Xmn同时设置时以-Xmn的值为年轻代大小
- -XX:SurvivorRatio=8 设置年轻代Eden/Survivor 的比值
两个Survivor大小一样,这个比值是跟其中一个Survivor大小的比值
- -XX:MaxPermSize=100m 设置持久代大小
持久代存放jvm加载的类信息,包括类名,方法信息,类静态成员信息,常量信息等
- -XX:MaxTenuringThreshold=15 设置对象从年轻代晋升到老年代之前存活的gc次数
- -XX:PretenureSizeThreshold=10M 超过这个大小的对象直接分配在老年代,对Parallal Scavenge垃圾回收器无效
2.4.1 GC日志相关参数
- -XX:+PrintGC
- -XX:+PrintGCDetails(包含年轻代和老年代回收详细信息)
- -XX:+PrintGCTimeStamps
- -Xloggc:<filename> 输出gc 日志到文件
- -XX:+PrintHeapAtGC 详细的gc前后堆变化信息
- -verbose:gc
3. JDK常用命令
3.1 jps
列出当前所有运行jvm进程
3.2 jstat
jstat用来查看GC和堆相关信息, 命令格式:
jstat <option> vmid [interval [count]]
其中[]
表示可选,interval表示采样间隔时间(s|ms),count表示输出结果数,比如:
jstat -gc 2141 3s 3
表示输出2141号jvm进程gc统计信息(-gc选项) , 相隔3s统计一次,输出3行
常用命令
- jstat -gc 输出gc统计信息,输出字段如下:
S0C:第一个幸存区的大小 S1C:第二个幸存区的大小 S0U:第一个幸存区的使用大小 S1U:第二个幸存区的使用大小 EC:伊甸园区的大小 EU:伊甸园区的使用大小 OC:老年代大小 OU:老年代使用大小 MC:方法区大小 MU:方法区使用大小 CCSC:压缩类空间大小 CCSU:压缩类空间使用大小 YGC:年轻代垃圾回收次数 YGCT:年轻代垃圾回收消耗时间 FGC:老年代垃圾回收次数 FGCT:老年代垃圾回收消耗时间 GCT:垃圾回收消耗总时间
- jstat -gccapacity 统计堆区信息,输出字段如下:
NGCMN:新生代最小容量 NGCMX:新生代最大容量 NGC:当前新生代容量 S0C:第一个幸存区大小 S1C:第二个幸存区的大小 EC:伊甸园区的大小 OGCMN:老年代最小容量 OGCMX:老年代最大容量 OGC:当前老年代大小 OC:当前老年代大小 MCMN:最小元数据容量 MCMX:最大元数据容量 MC:当前元数据空间大小 CCSMN:最小压缩类空间大小 CCSMX:最大压缩类空间大小 CCSC:当前压缩类空间大小 YGC:年轻代gc次数 FGC:老年代GC次数
- jstat -gcnewcapacity 统计年轻代内存情况
- jstat -gcoldcapacity 统计老年区内存情况
3.3 jmap
jmap用于生成当前内存转储快照,一般是在怀疑有内存泄漏时输出内存快找供后面分析。
常用命令
- jmap -dump:[live,]format=b,file=<path_to_file>, 表示将java堆快照输出到二进制文件中,live是可选的表示只输出活的对象。
- jmap histo:[live] vmid输出vmid进程的对象统计信息,包括对象个数,类型,占用大小,live表示只统计活的对象
- jmap -heap vmid 输出堆内存的使用情况
3.4 jstack
用于输出jvm进程中所有线程执行的方法的堆栈,在发现java程序没有响应时可是使用jstack查看java程序所有的线程运行状况,是否阻塞。
3.5 jinfo
查看jvm参数信息,格式为:
jinfo <option> vmid
如:
- jinfo -flag <name> vmid查看vmid这个jvm是否启用了name参数,如:
jinfo -flag UseParallelGC 3312
- jinfo -flag +|-<name> vmid 启用|停用(+表示启用) 无需参数值的jvm选项
- jinfo -flag <name>=<value> 设置jvm参数
- jinfo -flags 打印显式指定的jvm参数
本文参考