前言

从oracle官方文档出发搬运GC的基础理论知识

目录

  • JVM架构
  • GC回收过程
  • GC监控
  • GC日志查看
  • GC收集器介绍

JVM架构

JVM的主要组件(HotSopt虚拟机):

Key JVM Hotspot Components

当进行性能调优时,JVM的三大组件需要关注:堆,JIT,GC。

  • 堆是存放对象数据的地方,受到GC的管理
  • 大部分调优选项都跟调整堆大小、选择合适的垃圾回收器有关
  • JIT也对性能有很大影响,但是一般不需要调优

什么是GC

Automatic garbage collection is the process of looking at heap memory, identifying which objects are in use and which are not, and deleting the unused objects. An in use object, or a referenced object, means that some part of your program still maintains a pointer to that object. An unused object, or unreferenced object, is no longer referenced by any part of your program. So the memory used by an unreferenced object can be reclaimed.
In a programming language like C, allocating and deallocating memory is a manual process. In Java, process of deallocating memory is handled automatically by the garbage collector. The basic process can be described as follows.

GC的基本过程

  • 步骤1: 标记

marking

  • 步骤2: 删除

normal deletion

  • 步骤2a: 删除并压缩

deletion with compacting

为了进一步改进性能,删除未引用的对象之后还可以压缩剩余有引用的对象,将有引用的对象挪到一起,使新内存对象分配更容易、更快。

为什么选择分代回收

JVM里面标记和压缩对象比较低效,分配的对象越来越多,对象列表的增长会导致GC时间越来越长。应用程序的经验分析显示,大部分对象其实都比较“短命”,不同生命周期的对象采用不同的收集方式,以便提高回收效率。

bytes surviving

JVM中堆的分代

根据上面分析出来的信息,堆被分解成很多个小块,或者叫代,年轻代、老年代、永久代

JVM heap structure

  • 年轻代:新对象分配到年轻代,当年轻代满了就触发一次minorGC,充满死亡对象的年轻代会被快速回收掉,有些对象存活下来,会被转移到老年代。minorGC是一次“让世界静止(stop-the-world)”的事件,所有的线程都会停止直到minorGC操作结束。
  • 老年代:老年代用来存储存活久的对象,老年代需要进行majorGC,这也是“让世界静止”的事件,所有线程必须停止等待。但因为需要回收所有存活对象,一次majorGC会更慢一些,所以要最小化majorGC行为。majorGC的时间长短会受到GC收集器的种类影响。
  • 永久代(PermGen):存储类和方法的元数据信息,包括Java SE类库的类和方法。当JVM发现class不再需要的时候会被回收。永久代在full GC中回收。

    注: JDK8移除了永久代,取而代之的是元空间(Metaspace),类的元数据信息都被存储在native heap中,空间大小受硬件内存影响,也可以通过参数-XX:MaxMetaspaceSize=来指定最大大小。

GC回收的过程

我们已经知道堆被分成了多个代,那每个代的空间是如何交互的呢?

1.首先新对象都会在年轻代中的eden空间分配

object allocation

2.当eden空间满了,一次minorGC被触发

filling the eden space

3.有引用的对象会被复制到survivor空间s0,然后eden空间被清除,注意这个时候对象有一个年龄,值为1

copying referenced objects

4.下一次minorGC的时候,会将eden空间以及s0空间内有引用的对象复制到survivor空间s1,同时对象年龄+1,eden空间以及s0空间会被全部清除

object aging

5.再下一次minorGC的时候,同样的事情会再发生一次,但是这次是eden空间以及s1空间中有引用的对象会被复制到s0,同时对象年龄+1,eden空间以及s1空间会被全部清除

additional aging

6.就这样反复经过几次minorGC之后,有些对象的年龄会达到一定的阀值(图片示例中阀值为8,hotspot虚拟机默认是15),达到阀值的对象会被提升到老年代中

promotion

7.随着minorGC不断的进行,老年代的空间不断有新的对象增加

promotion

8.最终老年代会进行一次majorGC,标记-清除+压缩老年代的空间

GC process summary

什么是safe-point,safe-region

安全点:多线程环境下可能会存在不安全的GC回收,JVM为了能安全的回收内存,会在Safe Point点时进行回收,所谓Safe Point就是Java线程执行到某个位置这时候JVM能够安全、可控的回收对象,这样就不会导致上GC回收正在使用的对象

安全区域:当线程无法到达安全点时,这个时候就需要安全区域,安全区域是指一段代码片中,引用关系不会发生变化,在这个区域任何地方GC都是安全的。线程执行到安全区域的代码时,首先标识自己进入了安全区域,这样GC时就不用管进入安全区域的线程了,线程要离开安全区域时就检查JVM是否完成了GC Roots枚举,如果完成就继续执行,如果没有完成就等待直到收到可以安全离开的信号。

HotSpot并没有为每条指令都生成OopMap,而只是在“特定的位置”记录了这些信息,这些位置称为安全点(Safepoint) ,既程序执行时并非在所有地方都能停顿下来开始GC,只有在达到安全点时才能暂停。 在线程执行到Safe Region中的代码时,首先标识自己已经进入了Safe Region,那样,当在这段时间里JVM要发起GC时,就不用管标识自己为Safe Region状态的线程了,在线程要离开Safe Region时,它要检查系统是否已经完成了根节点枚举(或者是整个GC过程),如果完成了,那线程就继续执行,否则它就必须等待直到收到可以安全离开Safe Region的信号为止。

jvm大局观之内存管理篇: 理解jvm安全点,写出更高效的代码

如何监控GC

jvisualvm直观查看GC情况

通过jdk自带的visual vm工具可以直观的看到一个java进程的GC分代回收的情况

C:\Java\jdk1.8.0_172\bin>jvisualvm
选择工具>插件
安装Visual GC插件
右击java应用打开,右侧选择Visual GC

java visual vm

HPjmeter分析离线GC日志

首先启动jdk demo应用

H:\learning\jdkdemos1.8.0_201\demo\jfc\Java2D>java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -XX:+PrintHeapAtGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -verbose:gc -Xloggc:E:/logs/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=512k -XX:+HeapDumpOnOutOfMemoryError -jar Java2Demo.jar

通过 -verbose:gc -Xloggc:E:/logs/gc.log 参数将GC日志打印到本地日志文件

启动HPjmeter,拖入日志文件,可以观察到GC的一些情况

Java -Xms512M -Xmx1024M -Xss8M -jar HPjmeter_4.4.00.00.jar

官方使用方式

jstat命令查看GC情况

每隔1000ms显示一次GC监控信息,一共显示10次,vmid (Virtual Machine ID)可以通过jps -l 命令来查看

jstat –gc <vmid> 1000 10

如果开启 jstatd 则可远程使用 jvisualvm 的图形化监控.

1.首先需要配置一个policy文件,内容如下( 假设名称为 tools.policy,根据安装路径不一进行相应修改 ):

grant codebase "file:/usr/java/jdk1.7.0_79/lib/tools.jar" {
permission java.security.AllPermission;
};

2.然后启动jstatd服务,后面的IP地址为该服务绑定的地址:

jstatd -J-Djava.security.policy=/home/XXX/tools.policy -J-Djava.rmi.server.hostname=192.168.X.X

3.在jvisualvm中添加 Remote,输入IP新建主机后右键添加 jstatd connection,稍等片刻即可连接

# jstat除去可以查看gc信息,其实也可以查看JVM的其它信息

# 查看class统计
jstat -class <pid>

# 查看编译统计
jstat -compiler

jmap查看当前应用的堆情况

  • jmap查看当前jvm进程的情况
> jmap -heap <PID>

Attaching to process ID 589, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.171-b11

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 4150263808 (3958.0MB)
   NewSize                  = 86507520 (82.5MB)
   MaxNewSize               = 1383071744 (1319.0MB)
   OldSize                  = 173539328 (165.5MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 695730176 (663.5MB)
   used     = 581091216 (554.1717681884766MB)
   free     = 114638960 (109.32823181152344MB)
   83.52249708944636% used
From Space:
   capacity = 17825792 (17.0MB)
   used     = 6067376 (5.7863006591796875MB)
   free     = 11758416 (11.213699340820312MB)
   34.03706270105698% used
To Space:
   capacity = 17301504 (16.5MB)
   used     = 0 (0.0MB)
   free     = 17301504 (16.5MB)
   0.0% used
PS Old Generation
   capacity = 243793920 (232.5MB)
   used     = 33423000 (31.874656677246094MB)
   free     = 210370920 (200.6253433227539MB)
   13.709529753654234% used

26808 interned Strings occupying 3328616 bytes.
  • jmap也可以生成堆dump文件
jmap -dump:format=b,file=test.hprof 589
jmap -dump:format=b,file=test.dump 589

可以通过jvisualvm>文件>装入,将dump文件加载进来进行进一步分析

  • jmap查看当前jvm进程的对象数量及大小信息
# 查看所有的对象实例
jmap -histo <pid> | more
# 查看活跃对象实例
jmap -histo:live <pid> | more
# 对象说明
# B  byte
# C  char
# D  double
# F  float
# I  int
# J  long
# Z  boolean
# [  数组,如[I表示int[]
# [L+类名 其他对象

如何查看GC日志

常见GC相关的命令

Option Description
-verbose:(gc) 显示GC的操作内容,JDK8中与-XX:+PrintGC命令功能上一样,但是这个命令是标准参数而-XX:+PrintGC是非稳定参数
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDateStamps GC日志中打印时间,例如:2019-03-18T18:57:14.062+0800
-XX:+PrintGCTimeStamps GC日志中打印从JVM启动,直到当前垃圾收集所经历的时间(s),例如:33.205
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息: DefNew: Default New Generation,Full GC: Major GC,Tenured: 老年代,Perm: 永久代
-Xloggc 将GC日志输出到本地磁盘,例如:-Xloggc:E:/gc.log
-XX:+UseGCLogFileRotation 启用日志滚动
-XX:NumberOfGCLogFiles=5 控制日志数量
-XX:GCLogFileSize=512k 控制每个日志大小
-XX:SurvivorRatio=8 设置Eden:Survivior=8:1
-XX:NewSize=10M -XX:MaxNewSize=10M 设置整个新生代的大小为10M
-XX:-UseAdaptiveSizePolicy 禁用动态调整,使SurvivorRatio可以起作用

启动示例程序,将GC打印到日志文件

H:\learning\jdkdemos1.8.0_201\demo\jfc\Java2D>java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -XX:+PrintHeapAtGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -verbose:gc -Xloggc:E:/logs/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=512k -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintCommandLineFlags -jar Java2Demo.jar

打印的日志如下:

2018-06-15T10:44:26.631-0800: [Full GC (System.gc()) [PSYoungGen: 496K->0K(38400K)] [ParOldGen: 8K->402K(87552K)] 504K->402K(125952K), [Metaspace: 3300K->3300K(1056768K)], 0.0066154 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]

[GC类型 (System.gc()) [Young区: GC前Young的内存占用->GC后Young的内存占用(Young区域总大小)] [old老年代: GC前Old的内存占用->GC后Old的内存占用(Old区域总大小)] GC前堆内存占用->GC后堆内存占用(JVM堆总大小), [永久代区: GC前占用大小->GC后占用大小(永久代区总大小)], GC用户耗时] [Times:用户耗时 sys=系统时间, real=实际时间]

垃圾收集器介绍

收集器 串行、并行or并发 新生代/老年代 算法 目标 适用场景
Serial 串行 新生代 复制算法 响应速度优先 单CPU环境下的Client模式
ParNew 并行 新生代 复制算法 响应速度优先 多CPU环境时在Server模式下与CMS配合
Parallel Scavenge 并行 新生代 复制算法 吞吐量优先 在后台运算而不需要太多交互的任务
CMS 并发 老年代 标记-清除 响应速度优先 集中在互联网站或B/S系统服务端上的Java应用
Serial Old 串行 老年代 标记-整理 响应速度优先 单CPU环境下的Client模式、CMS的后备预案
Parallel Old 并行 老年代 标记-整理 吞吐量优先 在后台运算而不需要太多交互的任务
G1 并发 both 标记-整理+复制算法 响应速度优先 面向服务端应用,将来替换CMS

hotspot gabage collectors

关于每个垃圾收集器详细介绍,参考深入理解JVM(3)——7种垃圾收集器

JDK7、8、9默认垃圾回收器

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1

-XX:+PrintCommandLineFlags参数可查看默认设置收集器类型

-XX:+PrintGCDetails亦可通过打印的GC日志的新生代、老年代名称判断

常用参数

1) -Xmn: 新生代内存上限值
2) -Xms: 整个堆区初始内存分配的大小
3) -Xmx: 整个堆区内存分配的最大上限,推荐-Xms和-Xmx设置等同大小,避免动态回收消耗资源
4) -XX:+HeapDumpOnOutOfMemoryError 当出现OOM时,打印堆转储dump文件
5) -XX:HeapDumpPath= 指定堆转储dump文件存储路径
6) -XX:MaxTenuringThreshold=5 : 手动设置对象在新生代中存活年龄(存活次数),默认15次
7) -XX:SurvivorRatio=6 : Eden区与Survivor0、Survivor1区的大小比值,一般设置为6,Eden:S0:S1=6:1:1
8) 【**】-XX:PretenureSizeThreshold 手动指定对象大小,当对象达到指定大小时直接存放到老年代中,由于新生代大多使用复制算法,为了节省复制消耗,PretenureSizeThreshold 参数只对 Serial 和 ParNew 两款收集器有效。-XX:PretenureSizeThreshold=4m
9) -XX:+UseParNewGC : 手动指定新生代使用 ParNew 收集器
10) -XX:+UseConcMarkSweepGC : 手动指定老年代使用CMS收集器
11) -XX:PermSize=512M 指定非堆区域(永久代)初始内存分配大小, JDK 1.7 及以下生效
12) -XX:MaxPermSize= 1024M 指定非堆区域(永久代)内存分配的最大上限,JDK1.7 及以下生效
13) -XX:+UseCMSInitiatingOccupancyOnly : Hotspot会根据成本计算决定是否需要执行CMS收集器,可手动设置-XX:+UseCMSInitiatingOccupancyOnly关闭计算策略,强制使用CMS 收集器
14) -XX:+CMSClassUnloadingEnabled : 手动指定CMS 收集器对非堆区域永久代进行回收,默认永久代不回收
15) -XX:CMSInitiatingOccupancyFraction=80 : 手动指定当老年代已用空间达到80%时,触发老年代回收(默认92%)
16) -XX:CMSInitiatingPermOccupancyFraction=80  : 手动指定当永久代已用空间达到80%时,触发永久代回收(默认92%)
17) -XX:+DisableExplicitGC : 手动配置禁止使用外部调用System.gc 来进行触发垃圾回收
18) -XX:+UseCMSCompactAtFullCollection : 在进行Full GC时对内存进行压缩,JDK1.6以前不需要配置,默认开启
19) 【**】-XX:CMSFullGCsBeforeCompaction=2 : 与-XX:+UseCMSCompactAtFullCollection 关联使用标识着每经过多少次Full GC 触发对内存进行一次压缩,默认是0次
20) -XX:-CMSParallelRemarkEnabled : 手动配置开启并行标记,节省年轻代标记时间,JDK1.6以前不需要配置,默认开启
21) -Xnoclassgc  : 关闭CLASS的垃圾回收功能,默认20分钟这个class未被使用,虚拟机会卸载这个类。再次使用时重新加载

线上4C8G示例:

-Denv=pro -Dspring.profiles.active=pro -Duser.timezone=GMT+08 -Dapp.id=member-data -Ddubbo.protocol.telnet=-ls -Xms5800m -Xmx5800m -Xmn2188m -XX:MaxMetaspaceSize=512M -XX:MetaspaceSize=512M -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses -XX:+CMSClassUnloadingEnabled -XX:+ParallelRefProcEnabled -XX:+CMSScavengeBeforeRemark -verbose:class -XX:+HeapDumpOnOutOfMemoryError -XX:ErrorFile=/data/logs/hs_err_pid%p.log -Xloggc:/dev/shm/gc.log -XX:HeapDumpPath=/data/logs/ -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintClassHistogramBeforeFullGC -XX:+PrintClassHistogramAfterFullGC -XX:+PrintCommandLineFlags -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -XX:+PrintTenuringDistribution -XX:+PrintHeapAtGC

如何判断一个对象是否存活?引用类型与垃圾回收时机

GC回收之一:判断对象存活算法、四种引用、回收方法区

总结

gc.png

问题

京东:说下JVM内存模型与Java线程内存模型的区别?腾讯:JVM的GC执行时机是任何时候都可以吗?安全点知道吗?美团:CMS垃圾收集器的并发更新失败是怎么回事?如何优化?阿里:高并发系统为何建议选择G1垃圾收集器?拼多多:线上系统GC问题如何快速定位与分析?阿里:阿里巴巴Arthas实现原理能大概说下吗?百度:单机几十万并发的系统JVM如何优化?阿里:解释下JVM垃圾收集底层的三色标记算法?美团:Volatile底层的内存屏障是如何实现的?

CMS的promotion failed&concurrent mode failure问题原因及解决办法?

CMS的调优经验

JVM调优实战:解决CMS concurrent-abortable-preclean LongGC的问题

JVM 源码解读之 CMS 何时会进行 Full GC

***CMS之promotion failed&concurrent mode failure

CMS的CMSInitiatingOccupancyFraction解析–老年代最大使用率参数

G1的调优经验

G1GC 概念与性能调优

G1调优常用参数及其作用

JVM之G1回收器和常见参数配置

参考

*****Java Garbage Collection Basics

Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide

Java Platform Standard Edition 8 Documentation

About G1 Garbage Collector, Permanent Generation and Metaspace

*****深入理解JVM(3)——7种垃圾收集器

How to Monitor Java Garbage Collection

【JVM研习】目录大纲

***G1垃圾回收器详解

**JVM GC 三色标记、读写屏障