JVM GC分析

2019-09-29  本文已影响0人  星冉子

GC简介

GC(垃圾回收)是针对Java堆内存,Java堆是被所有线程共享的一块内存区域,所有对象实例和数组都在堆上进行内存分配。为了进行高效的垃圾回收,虚拟机把堆内存划分成新生代、老年代和永久代3个区域。

堆内存

新生代:由 Eden 与 Survivor Space(S0,S1)构成,大小通过-Xmn参数指定,Eden 与 Survivor Space 的内存大小比例默认为8:1,可以通过-XX:SurvivorRatio 参数指定,比如新生代为10M 时,Eden分配8M,S0和S1各分配1M。

Eden:大多数情况下,对象在Eden中分配,当Eden没有足够空间时,会触发一次Minor GC,虚拟机提供了-XX:+PrintGCDetails参数,告诉虚拟机在发生垃圾回收时打印内存回收日志。

Survivor:意思为幸存者,是新生代和老年代的缓冲区域。当新生代发生GC(Minor GC)时,会将存活的对象移动到S0内存区域,并清空Eden区域,当再次发生Minor GC时,将Eden和S0中存活的对象移动到S1内存区域。

存活对象会反复在S0和S1之间移动,当对象从Eden移动到Survivor或者在Survivor之间移动时,对象的GC年龄自动累加,当GC年龄超过默认阈值15时,会将该对象移动到老年代,可以通过参数-XX:MaxTenuringThreshold 对GC年龄的阈值进行设置。

老年代:老年代的空间大小即-Xmx 与-Xmn 两个参数之差,用于存放经过几次Minor GC之后依旧存活的对象。当老年代的空间不足时,会触发Major GC/Full GC,速度一般比Minor GC慢10倍以上。

永久代:JDK8已不存在。

GC 是一个“停止所有处理(stop the world)”的事件,它意味着除了 GC 线程自身外,其他所有执行线程都将处于挂起状态。


GC触发条件

Minor GC/Young GC:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存;

触发条件:当Eden区满时,触发Minor GC。 

Major GC/Full GC:一般指对老年代GC(整个堆GC);由于出现Full GC的时候经常伴随至少一次的Minor GC,而许多 Major GC 是由 Minor GC 触发的,所以不用太区分;

触发条件

1. 调用System.gc时,系统建议执行Full GC,但是不必然执行;

2. 老年代空间不足;

3. 方法区空间不足;

4. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存;

5. 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小;

 对象进入老年代的常见的4个时机:

1. 躲过15次gc,达到15次高龄之后进入老年代;

2. 动态年龄判定规则,如果Survivor区域内年龄1+年龄2+年龄3+年龄n的对象总和大于Survivor区的50%,此时年龄n以上的对象会进入老年代,不一定要达到15岁;

3.如果一次Young GC后存活对象太多无法进入Survivor区,此时直接进入老年代;

4.大对象直接进入老年代;

GC算法

判断对象是否存活算法:GC动作发生之前,需要确定堆内存中哪些对象是存活的,一般有两种方法:引用计数法和可达性分析法。

引用计数法:在对象上添加一个引用计数器,每当有一个对象引用它时,计数器加1,当使用完该对象时,计数器减1,计数器值为0的对象表示不可能再被使用。引用计数法实现简单,判定高效,但不能解决对象之间相互引用的问题。

可达性分析法:通过一系列称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,搜索路径称为 “引用链”,当一个对象到 GC Roots 没有任何引用链时,意味着该对象可以被回收。以下对象可作为GC Roots:本地变量表中引用的对象(局部变量)、方法区中静态变量引用的对象(static成员变量)、方法区中常量引用的对象(static final常量)、Native方法引用的对象;

不存活的垃圾对象收集算法:标记-清除、复制和标记-整理。

标记-清除算法:对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收;(缺点:由于直接回收不存活对象,会造成内存碎片);

标记-整理算法:基于标记-清除算法,清除后移动存活的对象;(可解决内存碎片)(年老代默认使用

复制算法:将内存分成大小相等的两块区域,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块区域上,然后对该块进行内存回收;(年轻代默认使用

GC收集器

Oracle Hotspot JVM中实现了多种垃圾收集器,针对不同的年龄代内存中的对象的生存周期和应用程序的特点,实现了多款垃圾收集器。

Serial/SerialOld:单线程GC收集器包括Serial和SerialOld这两款收集器,分别用于年轻代和老年代的垃圾收集工作。后来,随着CPU多核的普及,为了更好了利用多核的优势,开发了ParNew收集器,这款收集器是Serial收集器的多线程版本。

Parallel Scavenge/ParNew/ParallelOld:多线程收集器还包括Parallel Scavenge、ParNew和ParallelOld收集器,这两款也分别用于年轻代和老年代的垃圾收集工作,不同的是,它们是两款可以利用多核优势的多线程收集器。

CMS:相对来说更加复杂的还有CMS收集器。这款收集器,在运行的时候会分多个阶段进行垃圾收集,而且在一些阶段是可以和应用线程并行运行的,提高了这款收集器的收集效率。

G1:其中最先进的收集器,要数G1这款收集器了。这款收集器是当前最新发布的收集器,是一款面向服务端垃圾收集器。

年轻代收集器:Serial收集器、ParNew收集器以及Parallel Scavenge收集器。

老年代收集:Serial Old收集器、Parallel Old收集器以及CMS收集器。

查看GC日志

查看方法

设置 JVM 参数为,使得控制台或文件能够显示 GC 相关的日志信息;

-XX:+PrintGC :打印GC详情;

-XX:+PrintGCDetails :打印GC更详细的信息;

-Xloggc:[path] :设置gc日志位置;


输出样例:

GC日志

监控GC信息

监控方法:

使用jstat命令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控;

Jstat –class <pid>:显示加载class的数量,及所占空间等信息;

Jstat –gc <pid>:显示gc相关的堆信息,查看gc的次数,及时间;

Jstat -gcutil <pid>:统计gc信息;

Jstat –gccause <pid>:统计gc信息(同gcutil),同时显示最后一次或当前正在发生的垃圾回收的诱因;

Jstat –gc –h2 <pid> 3000 10:每2次打印一次表头,每3秒输出一次结果,一共输出10次;

输出样例:

控制台输出

一些术语的中文解释:

S0C:年轻代中第一个survivor(幸存区)的容量 (字节)

S1C:年轻代中第二个survivor(幸存区)的容量 (字节)

S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)

S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)

EC:年轻代中Eden(伊甸园)的容量 (字节)

EU:年轻代中Eden(伊甸园)目前已使用空间 (字节)

OC:Old代的容量 (字节)

OU:Old代目前已使用空间 (字节)

PC:Perm(持久代)的容量 (字节)

PU:Perm(持久代)目前已使用空间 (字节)

YGC:从应用程序启动到采样时年轻代中gc次数

YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)

FGC:从应用程序启动到采样时old代(全gc)gc次数

FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)

GCT:从应用程序启动到采样时gc用的总时间(s)

NGCMN:年轻代(young)中初始化(最小)的大小 (字节)

NGCMX:年轻代(young)的最大容量 (字节)

NGC:年轻代(young)中当前的容量 (字节)

OGCMN:old代中初始化(最小)的大小 (字节)

OGCMX:old代的最大容量 (字节)

OGC:old代当前新生成的容量 (字节)

PGCMN:perm代中初始化(最小)的大小 (字节)

PGCMX:perm代的最大容量 (字节)

PGC:perm代当前新生成的容量 (字节)

S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比

S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比

E:年轻代中Eden(伊甸园)已使用的占当前容量百分比

O:old代已使用的占当前容量百分比

P:perm代已使用的占当前容量百分比

S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节)

S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节)

ECMX:年轻代中Eden(伊甸园)的最大容量 (字节)

DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满)

TT: 持有次数限制

MTT : 最大持有次数限制

GC优化

如何看待GC优化:

1. 并非所有基于Java的应用都需要进行GC优化;

2. GC优化是最后的手段

3. 开发过程中需要关注一些小事情,避免"养成气候"之后难以驾驭,如使用StringBuilder或StringBuffer代替String等;

4. 有些场景我们也无能为力,比如XML和JSON的解析会消耗大量的内存,但是又不太可能不使用XML和JSON,只能任由内存被消耗;

5. 如果在几次参数调整后内存使用情况有所改善,你就可以进行GC优化了;

如何优化:

1. 监控GC并分析是否需要优化,比如GC时间太长超过几秒,会导致服务器请求超时,则可以考虑优化,如果GC时间都很短则没有必要;

2. 调整影响GC的JVM参数,如内存大小、内存大小比例、垃圾收集器类型等;

3. 重复第1步,监控并分析结果,如果不满意则停止优化;

上一篇下一篇

猜你喜欢

热点阅读