Contents
  1. 1. 默认的内存分配
  2. 2. 回收的过程
  3. 3. 栈中的垃圾回收
  4. 4. 年轻代GC
    1. 4.1. 结构
    2. 4.2. 触发
  5. 5. 垃圾回收器
    1. 5.1. Serial收集器
    2. 5.2. ParNew收集器
    3. 5.3. Parallel Scanvenge 收集器
    4. 5.4. CMS收集器(concurrent Mark Sweep) 并发标记清除
      1. 5.4.1. 特点:
      2. 5.4.2. 缺点:
      3. 5.4.3. JVM优化
    5. 5.5. G1回收器(Gabage First)
      1. 5.5.1. G1的新生代回收
      2. 5.5.2. G1老年代的回收
  6. 6. JVM调优
    1. 6.1. jstat
      1. 6.1.1. 查看JVM内存容量分配
      2. 6.1.2. 查看当前使用情况
      3. 6.1.3. 当前总容量下的实际使用率(强调百分比)
    2. 6.2. jmap
      1. 6.2.1. 查看当前堆的参数设置和使用情况
    3. 6.3. jinfo
  7. 7. GC日志计算规则
    1. 7.1. 虚拟机环境
    2. 7.2. 测试环境含FULL GC
    3. 7.3. GC日志参数设置

参考大神的blog:
http://www.cnblogs.com/ityouknow/p/5614961.html

另外参考《深入理解JAVA虚拟机》

默认的内存分配

  • 堆区默认最小1/64,最大1/4,老年代:新生代=2,Eden:S0=8
  • ratio 堆区空余空间 40% 到70% 都会触发resize

所以JVM优化的第一步就是修改默认的内存设置

回收的过程

  • 年轻代的内置增长机制和老年代增长机制可以不同步,比如 NGC 增大,但是OGC 可以不变。
  • 第一次Minor GC,年轻代 double,而且每2次Minor都触发double。(差不多是 86% – 92%的比例扩增)

栈中的垃圾回收

jvm 栈中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 java 堆和方法区中。

在Java语言中,GC Roots包括:

  • 虚拟机栈中引用的对象。
  • 方法区中类静态属性实体引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI引用的对象。

年轻代GC

结构

  • Eden : S0 :S1 默认(8:1:1) 可以通过设置-XX:SurvivorRatio 设置比例
  • minor GC 针对Eden 和 S(from),复制法GC.Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

触发

  • Eden内存不够的时候触发minor GC
  • S(to) 被填满之后会触发所有对象到老年代
  • Minor GC一定次数的对象也会被分配到 老年代(默认15,可以通XX:MaxTenuringThreshold=15来设置)

垃圾回收器

  • 年轻代的垃圾回收器: Serial、Parnew、Parallel Scanvenge
  • 老年代的垃圾回收器: Serial Old、CMS、Parallel Old
  • 混合带垃圾回收器: G1回收器

垃圾回收器搭配

并且垃圾回收器的搭配是有限定的,不是任意都可以搭配

Serial收集器

  • 分为年轻代就叫 Serial 和老年代的 Serial Old
  • 串行回收,高效 稳定但是stop the world较长
  • 适合单核CPU,效率高而且稳定,但是STW 较长
  • 是Client模式下的默认GC机制

ParNew收集器

  • serial 收集器的年轻代多线程版本
  • 但是存在线程切换的开销,如果CPU核数不多的情况下,效率还没有Serial高,且不能保证在2个内核的情况下比Serial快
  • 因为Parallel Scanvage不能和CMS搭配,所以新生代只有ParNew可以和CMS进行搭配了

Parallel Scanvenge 收集器

  • 根据内部算法自动调整内存分配,从而达到吞吐量(工作时间/工作时间+GC时间)最大,默认99%
  • 适合不需要和后台交互的系统,从而达到CPU利用最大化
    参数: 设置最大停顿时间,设置吞吐量比例

CMS收集器(concurrent Mark Sweep) 并发标记清除

特点:

  • 追求的是停顿时间最短
  • 初始标记:STW 快速可达根节点对象
  • 并发标记:并发客户线程,分配一个线程来tracking GC ROOTS
  • 重新标记 并发清除

缺点:

  • 对CPU 资源敏感
    在耗时的并发标记和并发清除的时候,都需要单独一个线程来耗时完成,如果只有2个CPU的话,那么需要占用1个CPU来长时间的GC,程序就会慢

  • 空间利用率不高
    因为是标记清除所以有内存碎片,可以设置几次Full GC的时候压缩内存空间,可能因为有大对象没有足够的连续空间而提前触发full GC

  • 无法处理浮动垃圾
    也就是并发清除的阶段可能会产生新的垃圾,所以这一阶段产生的垃圾要在下一次GC完成,并且要预留一定的space来存这些对象。如果新增的对象太多,会GC失败,触发Serial Old来重新GC。

JVM优化

  • 默认情况下每次FULL GC都会做一次 标记压缩,可以设置参数,多少次FULL GC后压缩一次
  • 可以设置在FULL GC之前进行压缩,从而减少FULL GC的频率

G1回收器(Gabage First)

特点:

  • 针对多CPU 高性能服务器采用的复杂的回收机制(都是多线程的)
  • 同时做到高吞吐 可以设置 STW时间,又保证高效(因为是按照活度来优先回收region)
  • G1回收器管理了整个堆,而不是像其他回收器针对年轻代还是老年代管理。

问题:

  1. 说是老年代用的是标记整理算法,为什么有 Recently Copied old generation?这不是标记复制吗?不是说效率很低的吗?
  2. 是否支持部分回收? 因为设置了最长时间,可以按照存活比率优先回收 region

G1的新生代回收

当新生代(可能是Eden)区达到一定比例的时候(新生代可能是多个region组成的),并行的做一次MinorGC,多个Region的 Eden 和Survior区(recently copied young generation 新名子),扫完之后,年龄超过15的进入老年代,其余的参与对象复制到一个或者多个 空的region里,成为新的Suvior区,然后清空掉老的Suvior和Eden区。

G1老年代的回收

网上的都看不懂,特点记住

  • 分region的,老年代采用标记整理的方式进行,相比CMS,不会有碎片导致大对象引发提前Full GC
  • 而且目前也不是非常成熟
  • Remember Set 看不懂

JVM调优

在不依赖工具的情况下,调用JVM接口查看jvm参数和使用情况
http://www.ityouknow.com/jvm/2017/09/03/jvm-command.html

注意事项:
jstat -gc 看的S0C 容量是当前容量,和-gccapacity 的S0C不是一个概念, 后者是上限和下限,而前者指的是当前容量,因为当前容量是可以策略调整的。
-gc 里面的 S0U 才是最真实的实时的使用量。可以根据这个实际使用量来衡量设置的阈值是否合适

jstat

查看JVM内存容量分配

./jstat -gccapacity pid

ps -ef|grep java 找到JDK的pid 和路径,然后进入到bin目录下,查看容量大小
java内存分解图

查看当前使用情况

./jstat -gc pid times count
对20289 pid进行1秒钟监控一次,共输出5次
./jstat -gc 20289 1000 5

当前总容量下的实际使用率(强调百分比)

jstat -gcutil pid
查看在当前总容量下的实际使用率,当前总容量不一定是设置的最大容量,可以动态调整的

jmap

查看当前堆的参数设置和使用情况

这个是最详细的描述堆区信息的命令,包括GC回收器的类型,堆区默认参数和使用情况
./jmap -heap 28920

jinfo

./jinfo -flags 3181 查看jvm的所有参数
./jinfo pid 查看系统参数 和jvm参数

GC日志计算规则

堆内存信息

  • 回收器类型 JDK 1.7 默认是 Mark Sweep Compact
  • 最大堆区:设置的1500M
  • 最小500M, 而默认的新生代 和老年代的比例是2,所以 新生代的大小是500/3=166M
  • Eden:S=4,所以S0=166/6=27.75, Eden: 27.75*4=111M, 111 +27.75 +27.75=16
  • 都是不含S0区的大小
  • GC实际耗时 参考的是一个时间0.0314790秒 解决 real=0.04
  • FULL GC会触发 Minor GC OldGC PermGC

虚拟机环境

所以第一行: 113792 –> 13544K (142208)
年轻代GC前大小 –> 年轻代GC后大小 (年轻代容量 不含S0)

(500M/3)1024K=170666K (新生代总大小)
170666/6=28444K(S0区大小)
(170666/6)
5=142221( S0+Eden区的大小 也就是显示的 142208K)

113792 –>13544K(483585K) 堆内存GC前实际大小 –> GC后堆内存大小 (堆实际大小 不含S0区)
483585K+28444=512029K /1024=500M

MinorGClog分析

测试环境含FULL GC

因为测试环境用的是 Parallel GC回收器,系统会根据算法分配内存空间,从而获取最大的吞吐量(减少GC时间占用总时间的比率)
MinorGClog分析

GC日志参数设置

可以参照这个设置在 catelina.sh中配置
JAVA_OPTS=”-server -Xms500m -Xmx1500m -XX:PermSize=64m -XX:MaxPermSize=256m -XX:SurvivorRatio=4
-verbose:gc -Xloggc:/usr/local/jvmlog/gc.log
-Djava.awt.headless=true
-XX:+PrintGCDateStamps -XX:+PrintGCDetails
-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000 -XX:MaxTenuringThreshold=15”

-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式 基准时间是tomcat的启动时间)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径

Contents
  1. 1. 默认的内存分配
  2. 2. 回收的过程
  3. 3. 栈中的垃圾回收
  4. 4. 年轻代GC
    1. 4.1. 结构
    2. 4.2. 触发
  5. 5. 垃圾回收器
    1. 5.1. Serial收集器
    2. 5.2. ParNew收集器
    3. 5.3. Parallel Scanvenge 收集器
    4. 5.4. CMS收集器(concurrent Mark Sweep) 并发标记清除
      1. 5.4.1. 特点:
      2. 5.4.2. 缺点:
      3. 5.4.3. JVM优化
    5. 5.5. G1回收器(Gabage First)
      1. 5.5.1. G1的新生代回收
      2. 5.5.2. G1老年代的回收
  6. 6. JVM调优
    1. 6.1. jstat
      1. 6.1.1. 查看JVM内存容量分配
      2. 6.1.2. 查看当前使用情况
      3. 6.1.3. 当前总容量下的实际使用率(强调百分比)
    2. 6.2. jmap
      1. 6.2.1. 查看当前堆的参数设置和使用情况
    3. 6.3. jinfo
  7. 7. GC日志计算规则
    1. 7.1. 虚拟机环境
    2. 7.2. 测试环境含FULL GC
    3. 7.3. GC日志参数设置