jvm 垃圾回收机制

1. 前言

网上关于jvm gc的文章有很多,写这篇文章不是有什么新东西要讲,主要原因是工作时也偶尔碰到比如full gc,jvm参数设置或者使用jdk自带的一些命令查看gc或者内存占用等,每次碰到这种情况都要百度一番,看一些文章,但是不记下来久而久之就遗忘了,等到下次出现时又需要查看一番。本文主要做一些笔记,下次就不用去百度了。

注:本文内容主要来自于博客(在最后参考部分已经列出)以及「深入理解java虚拟机」一书。

2. GC算法和垃圾回收器

2.1 GC类型

  1. partial gc
    即只对内存的一部分进行GC,主要包括
    • Young GC
    • Old GC CMS垃圾收集器专有,其他的old区收集器进行gc等同于fullgc,会同时收集young,old和permenant
    • MixGC G1专有,收集部分young区和部分old区
  2. full gc
    收集young,old和permenant区

2.2 GC算法

2.2.1 标记清除

标记清除算法分为"标记"和“清除”两个阶段,“标记”阶段标记出可以回收的对象,“清除”阶段回收被标记的内存。过程如下图:

标记清除_两个阶段

存在的问题
注:图片来自于图片出处

  1. 清除的内存比较分散,回收起来比较耗时
  2. 清除之后存在大量的内存碎片。

2.2.2 复制算法

hotspot新生代垃圾收集器基本采用这种算法,理论上它将内存分为大小相同的两块(实际上实现时不是按这种比例划分),分配内存时始终只使用其中一块,内存不够用时进行GC,将当前使用的内存中存活的对象复制到另一块空闲内存上,然后集中清空当前使用内存即可。如下图所示:

复制算法.jpg

图片出处
优点
由于每次回收都会将存活对象复制到另一块区域的连续空间上,一方面不会有内存碎片存在,没有内存碎片的存在,分配新的内存时只需要在在连续的未使用区域移动堆顶指针,按序分配内存即可。

实际上垃圾回收器使用这种算法回收年轻代时,并不是按照1:1划分内存的,而是将内存划分为一块Eden区和两块Survivor, Hotspot默认Eden:Suvivor-1:Survivor-2=8:1:1,分配内存时只使用Eden区和其中一块Survivor,GC时对Eden区和这块正在使用的Survivor进行回收,然后将存活的对像复制到另一块未使用的Survivor上。

2.2.3 标记-整理算法

标记整理算是Hotspot回收老年代的算法,如下图所示:

标记-整理.jpg

图片出处
如上图,标记整理算法不将内存分为两块,它使用全部的内存,在垃圾回收时它将存活的对象移动的内存的一端,然后清理掉边界以外未使用的内存。

2.2.4 分代收集

分代收集算法不是一种新的算法,而是使用上面算法的组合去管理内存的不同区块,分代算法将内存分为年轻代和老年代,各自使用不同的收集算法。
Hotspot中将内存分为年轻代(Young)和老年代(Tenure),年轻代又被划分为Eden区和2个Survivor区。年轻代采用复制算法,老年代采用标记-整理算法。如下图所示:

jvm内存分代.png

2.2 垃圾回收器

下图是Hotspot虚拟机包含的垃圾回收器:

垃圾回收器.jpg

图片出处
实线分割线上面是年轻代垃圾回收器,下面是老年代垃圾回收器。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
使用标记-清除算法(这种算法会产生内存碎片),回收过程尽力过个步骤如下:

CMS垃圾回收过程.png

图片出处

  1. 初始标记和重新标记阶段,都是Stop-the-world,但是时间很短,不像其他两个整个过程都是Stop-the-world

  2. 由于并发清除和用户线程同时运行,也就是说清理阶段可能会有新的内存分配和新的垃圾产生,这种垃圾称为“浮动垃圾”,显然这中垃圾只能等到下一次GC了。并发清除过程中新的内存分配产生也就意味着CMS不能像其他垃圾回收器一样:在老年大沾满了之后再回收,CMS必须在老年代有一定的预留空间是回收,默认是老年代达到68%时开始回收。如果预留空间不够会触发"Concurrent Mode Failture",此时CMS退化为Serial Old垃圾回收器。

  3. 由于CMS采用标记-清除,所以会产生内存碎片

  • -XX:+UseConcMarkSweepGC 开启CMS
  • -XX:CMSinitiatingOccupancyFraction=70
    设置触发CMS垃圾回收时的老年代占用,越高以为着预留空间越小,越有可能发生“Concurrent Mode Failture”
    过低可能会导致过多的老年代回收次数
    
  • -XX:+UseCMSCompactAtFullCollection
    CMS内存碎片回到导致没有足够连续空间,触发FullGC,这个选项设置在FullGC后进行内存整理
    
  • -XX:CMSFullGCsBeforeCompaction=5
    配合上面参数一起使用,在触发多少次FullGC之后再进行内存整理
    
  • -XX:+CMSScavengeBeforeRemark
    在重新标记阶段之前进行一次YoungGC,有时候young区对old区的跨代引用也会导致cms对old区回收效果差。
    
    注:其他的老年代垃圾回收器对老年代的回收其实等价于FullGC,但是CMS在concurrent collection阶段只对old区回收,这个选项能让它进行一次YoungGC, 避免过多的年轻代对老年代的跨代引用影响old回收时间。

2.3 G1垃圾回收器

G1是Garbage First的简称,G1垃圾回收器对内存的划分和上面几种都不一样,如下图所示:

g1内存分代.png

如上图,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参数

本文参考

  1. JVM初探- 内存分配、GC原理与垃圾收集器
  2. 深入理解 Java G1 垃圾收集器
  3. Java HotSpot VM options
  4. JVM系列三:JVM参数设置、分析
  5. G1 垃圾收集器入门
  6. 理解G1垃圾收集器日志
  7. GC种类 R大的回答
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容