Java内存分析与GC收集器

Java内存分析工具

memory anlayzer tool (mat)
复制代码
工具下载

下载链接:Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation

若启动报“version 11 or greater is required”,则安装jdk 11或更高版本,并指定mat的jdk版本,如下图:

image.png

dump文件导出

1、window使用Jdk自带工具jvisualvm导出

  • 找到应用,生成堆文件

2-0.jpg

2-1.jpg

2、Jmap命令导出

- 执行 jps -l 查看下需要导出dump的进程号
- 执行命令 jmap -dump:file=a.dump <pid>
- 执行head -1 a.dump查看是否导出成功
- 执行scp 将导出的文件导出到对应的主机进行分析    
复制代码
mat内存分析

1、用mat打开hprof文件

2-3.jpg

从图中可以清晰看到应用中内存占比情况以及对象占用内存大小,如图中:PdmsSyncServiceImpl 中某个LinkedList占用内存达到91.05%未被释放,然后在查看对面代码,分析对象List未被释放的原因。

GC

通过命令查看当前Jvm设置的参数

java -XX:+PrintCommandLineFlags -version
复制代码

2-4.jpg

从图中,可以看出Jvm使用的是G1收集器(-XX:+UseG1GC)。下面我们来看下各种GC收集器的原理,在这之前先给大家介绍一下GC算法。

image.png

GC算法

1. 标记清楚算法

它是最基础的收集算法,这个算法分为两个阶段,“标记”和”清除“。首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它有两个不足的地方:

  1. 效率问题,标记和清除两个过程的效率都不高;
  2. 空间问题,标记清除后会产生大量不连续的碎片;

image.png

2. 复制算法

image.png

如图所示,复制算法会将内存均分为两块。每次只使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块区,然后再把使用的空间一次清理掉。每次的内存回收都是对内存区间的一半进行回收

3.标记-整理算法

根据老年代的特点提出的一种标记算法,标记过程和“标记-清除”算法一样,但是后续步骤不是直接对可回收对象进行回收,而是让所有存活的对象向一段移动,然后直接清理掉边界以外的内存

image.png

4.分代收集算法

现在的商用虚拟机的垃圾收集器基本都采用"分代收集"算法,这种算法就是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

在新生代中,每次收集都有大量对象死去,所以可以选择复制算法,只要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率时比较高的,而且没有额外的空间对它进行分配担保,就必须选择“标记-清除”或者“标记-整理”算法进行垃圾收集

GC收集器

Serial ParNew Parallel G1 CMS Serial Old Parallel Old
回收代 新,老
算法 复制 复制 复制 分代 标记-清除 标记–整理 标记–整理
线程
特点 STW,简单高效 Serial的多线程版本 自适应调节策略,吞吐量大 堆划分为大小相同的region 低停顿 低停顿 低停顿
1.Serial收集器

串行垃圾收集器,即GC线程与用户线程先后运行,即GC时需要STW(暂停所有用户线程),直至GC结束才恢复用户线程的运行

专注于收集年轻代,底层是复制算法

相关参数:-XX:+UseSerialGC

2.ParNew收集器

相关参数:

-XX:+UseConcMarkSweepGC:指定使用CMS后,会默认使用ParNew作为新生代收集器
-XX:+UseParNewGC:强制指定使用ParNew
-XX:ParallelGCThreads:指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同

3.Parallel收集器(JDK1.8)

关注吞吐量的收集器

吞吐量 = 运行用户代码时间 / (运行用户代码时间+垃圾收集时间)

相关参数:

  • -XX:MaxGCPauseMillis:是一个大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过设定值。不过大家不要异想天开地认为如果把这个参数的值设置得稍小一点就能使得系统的垃圾收集速度变得更快,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的:系统把新生代调小一些,收集300MB新生代肯定比收集500MB快吧,这也直接导致垃圾收集发生得更频繁一些,原来10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、每次停顿70毫秒。停顿时间的确在下降,但吞吐量也降下来了。

  • -XX:GCTimeRatio:一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率。如果把此参数设置为19,那允许的最大GC时间就占总时间的5%(即1 /(1+19)),默认值为99,就是允许最大1%(即1 /(1+99))的垃圾收集时间。

  • -XX:+UseAdaptiveSizePolicy:一个开关参数,当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)

image.png

4.Serial Old收集器

Serial收集器的老年代版本。基于标记-整理算法实现,

有两个用途:

1、与Serial收集器、Parallel收集器搭配使用

2、作为CMS收集器的后备方案

5.Parallel Old收集器

Parallel收集器的老年代版本。基于标记-整理算法实现。

6.CMS收集器

image.png

聚焦低延迟。基于标记-清除算法实现。

由于CMS收集器是并发收集器,即在运行阶段用户线程依然在运行,会产生对象,所以CMS收集器不能等到老年代满了才触发,而是要提前触发,这个阈值是92%。这个阈值可以通过参数-XX:CMSInitiatingOccupancyFraction设置


相关参数:

-XX:+UseConcMarkSweepGC:手动开启CMS收集器
-XX:+CMSIncrementalMode:设置为增量模式
-XX:CMSFullGCsBeforeCompaction:设定进行多少次CMS垃圾回收后,进行一次内存压缩
-XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收
-XX:UseCMSInitiatingOccupancyOnly:表示只在到达阀值的时候,才进行CMS回收
-XX:CMSInitiatingOccupancyFraction:设置CMS收集器在老年代空间被使用多少后触发
-XX:+UseCMSCompactAtFullCollection:设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理


CMS收集器工作分四个步骤:

1、初始标记

会STW。只标记GC Roots直接关联的对象。

2、并发标记

不会STW。GC线程与用户线程并发运行。

会沿着GC Roots直接关联的对象链遍历整个对象图。可想而知需要的时间较长,但因为是与用户线程并发运行的,除了能感知到CPU飙升,不会出现卡顿现象。

3、重新标记

会STW。 CMS垃圾收集器通过写屏障+增量更新记录了并发标记阶段新建立的引用关系,重新标记就是去遍历这个记录。

4、并发清除

GC线程与用户线程并发运行,清理未被标记到的对象

默认启动的回收线程数 = (处理器核心数 + 3) / 4


显然CMS收集器依然不是完美的,不然后面就不会出现G1、ZGC等。那有哪些缺点呢?

1、运行期间会与用户线程抢夺CPU资源。当然,这是所有并发收集器的缺点
2、无法处理浮动垃圾(标记结束后创建的对象)
3、内存碎片

7.G1收集器

image.png

G1收集器与之前的所有收集器都不一样,它将堆分成了一个一个Region,这些Region用的时候才被赋予角色:Eden、from、to、humongous。一个region只能是一个角色,不存在一个region既是Eden又是from。

每个region的大小可通过参数-XX:G1HeapRegionSize设置,取值范围是2-32M。

一个对象的大小超过region的一半则被认定为大对象,会用N个连续的region来存储。


G1名字的由来

回收某个region的价值大小 = 回收获得的空间大小 + 回收所需时间

G1收集器会维护一个优先级列表,每个region按价值大小排序存放在这个优先级列表中。收集时优先收集价值更大的region,这就是G1名字的由来。

image.png

四个步骤:

1、初始标记

会STW。

做了两件事: 1、修改TAMS的值,TAMS以上的值为新创建的对象,默认标记为存活对象,即多标
2、标记GC Roots能直接关联到的对象

2、并发标记

耗时较长。GC线程与用户线程并发运行。

从GC roots能直接关联到的对象开始遍历整个对象图

3、最终标记

遍历写屏障+SATB记录下的旧的引用对象图

4、筛选回收

更新region的统计数据,对各个region的回收价值进行计算并排序,然后根据用户设置的期望暂停时间的期望值生成回收集。

然后开始执行清除操作。将旧的region中的存活对象移动到新的Region中,清理这个旧的region。这个阶段需要STW。


相关参数:

-XX:G1HeapRegionSize:设置region的大小
-XX:MaxGCPauseMillis:设置GC回收时允许的最大停顿时间(默认200ms)
-XX:+UseG1GC:开启g1
-XX:ConcGCThreads:设置并发标记、并发整理的gc线程数
-XX:ParallelGCThreads:STW期间并行执行的gc线程数

缺点:

1、需要10%-20%的内存来存储G1收集器运行需要的数据,如不cset、rset、卡表等
2、运行期间会与用户线程抢夺CPU资源。当然,这是所有并发收集器的缺点

猜你喜欢

转载自juejin.im/post/7017447916671336484