文章目录
  1. 1 GC相关内存
    1. 1.1 内存划分
      1. 1.1.1 堆(Heap)
      2. 1.1.2 永久代(Permanent Generation)
      3. 1.1.3 类元数据(Metaspace)
      4. 1.1.4 实例解析
    2. 1.2 JVM内存分配策略
      1. 1.2.1. 对象优先分配在 Eden
      2. 1.2.2. 大对象直接分配在老年代
      3. 1.2.3. 长期存活对象移入老年代
      4. 1.2.4. 永久代满了也会导致 FullGC
      5. 1.2.5. 动态对象年龄判定
      6. 1.2.6. 冒险模式
  2. 2 JVM优化原则
    1. 2.1 优化目标
      1. 2.1.1 尽量减少 YoungGC
      2. 2.1.2 尽量减少 FullGC
    2. 2.2 优化方法
      1. 2.2.1 代码角度
      2. 2.2.2 JVM参数角度
  3. 3 服务端开启 JMX/jstatd
    1. 3.1 设置系统环境变量
    2. 3.2 开启 JMX(指定端口 1090)
      1. 3.2.1 准备用户验证文件
      2. 3.2.2 修改用户验证文件权限
      3. 3.2.3 修改 Tomcat 启动时 JVM 选项
    3. 3.3 开启 jstatd agent(默认端口 1099)
    4. 3.4 配置防火墙(放行端口 1090/1099)
  4. 4 可视化工具
    1. 4.1 JConsole
    2. 4.2 VisualVM
      1. 4.2.1 文档
      2. 4.2.2 下载与安装
      3. 4.2.3 安装 VisualGC 插件
      4. 4.2.4 前提条件
      5. 4.2.5 添加远程主机 / JMX连接
      6. 4.2.6 监控远程应用
      7. 4.2.7 JVM 优化实战
  5. 5 命令行工具(服务端)
    1. 5.1 jps(查看Java进程)
    2. 5.2 jmap/jhat(快照的生成与查看)
    3. 5.3 jstack
    4. 5.4 jcmd
  6. 6 开发监控系统
    1. 6.1 Shell 脚本
    2. 6.2 Java 代码
  7. 7 内存泄露插件
    1. 7.1 内存泄露现象
    2. 7.2 生成快照文件(hprof Heap信息文件)
    3. 7.3 分析dump文件
    4. 7.4 分析内存泄漏
  8. 8 GC日志
    1. 8.1 开启gc日志文件
    2. 8.2 日志文件格式解析
    3. 8.3 gc.log 解析工具
      1. 8.3.1 在线分析
      2. 8.3.2 ga456(IBM)
      3. 8.3.3 HPjmeter
      4. 8.3.4 GCViewer
      5. 8.3.5 IBM GCMV(Eclipse 插件)
      6. 8.3.6 gchisto(VisualVM 插件)
      7. 8.3.7 其他更多

本文讲解了 JVM 的内存划分和分配策略,并以截图和脚本展示常用可视化和命令行工具的使用方法,完整演示了 JVM 优化、内存泄露排查、gc.log 分析方法等。

作者:王克锋
出处:https://kefeng.wang/2016/11/22/java-jvm/
版权:自由转载-非商用-非衍生-保持署名,转载请标明作者和出处。

1 GC相关内存

1.1 内存划分

1.1.1 堆(Heap)

存放 new MyClass() 的对象,是GC的主要区域,
-Xms / -Xmx 分别是堆的初始容量、最大可扩展容量,建议初始值设置为最大值,以免反复扩展或缩减的开销;

  • 新生代(Young Generation):又划分为 Eden(伊甸园,新生区), Survivor#0(幸存区S0), Survivor#1(幸存区S1)
  • 老年代(Tenured Generation)

-XX:NewRatio 是“老年代 / 新生代”的比例,默认值为 2;
-XX:SurvivorRatio 是指 Eden/Survivor#0 的比例,而Survivor#0 与 Survivor#1 容量相同;

1.1.2 永久代(Permanent Generation)

存放类信息、常量、大对象(比如 new byte[n]对象);
-XX:PermSize / -XX:MaxPermSize 为永久代的初始、最大容量,建议初始容量指定为最大容量;

1.1.3 类元数据(Metaspace)

JDK8 中,永久代被完全的移除了(相关参数 -XX:PermSize / -XX:MaxPermSize 被忽略)。改用 Metaspace,相关参数如下:

  • -XX:MetaspaceSize
  • -XX:MaxMetaspaceSize: 最大容量,默认没有限制(机器内存);
  • -XX:CompressedClassSpaceSize

1.1.4 实例解析

选项 -Xms300M -Xmx300M -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:PermSize=100M -XX:MaxPermSize=100M 含义为:

  • 永久代固定尺寸为 100M;
  • 整个堆固定尺寸为 300M,其中“老年代 / 新生代”为-XX:NewRatio=2,所以老年代为 200M,新生代为 100M;
  • 新生代总共 100M,其中“Eden / Survivor0”为-XX:SurvivorRatio=8,所以 Eden 为 80M,Survivor0=Survivor1=10M。
    官方资料:http://www.oracle.com/technetwork/articles/java/vmoptions-jsp-140102.html

1.2 JVM内存分配策略

1.2.1. 对象优先分配在 Eden

首先尝试在 Eden 分配,若 Eden 空间不足,就发起 YoungGC(简称YGC);

  • 如果幸存对象(被根对象直接或间接引用)在 SurvivorTo 能放下,则存活对象全部移入 SurvivorTo;
  • 如果幸存对象在 SurvivorTo 存放不下,则存活对象全部移入老年代;
    如果此时老年代空间不足,则发起一次 FullGC(简称FYC,整个系统停顿,老年代也参与回收);
    如果 FullGC 时空间仍然周转不过来,则报 OutOfMemoryError 并导致进程结束;
  • 如果YGC/FGC能周转过来,则新对象分配在 Eden 中;

1.2.2. 大对象直接分配在老年代

所谓的大对象是指,需要大量连续内存的 Java 对象,尤其是长字符串或数组,比如 byte[n] 对象;
可以指定多大才算大对象(只对 Serial/ParNew 有效);

1.2.3. 长期存活对象移入老年代

经历 n 次 YGC 仍然存活的对象,下次 YGC 时将被移入老年代;
可以设置该数值:-XX:MaxTenuringThreshold=15(默认)

1.2.4. 永久代满了也会导致 FullGC

老年代、永久代的垃圾收集是捆绑在一起的,因此无论两者谁满了,都会触发两者的FullGC。

1.2.5. 动态对象年龄判定

如果 Survivor 相同年龄对象占用空间达到一半,则大于等于该年龄的对象都移入老年代;

1.2.6. 冒险模式

JDK 6u24 之后,总是开启冒险模式(先尝试 YGC,以免 FGC 频繁);
每次 YGC 之前,如果老年代最大连续空间,大于新生代所有对象空间之和,则 YGC 肯定成功,否则:

  • 如果老年代最大连续空间,大于历次晋升老年代的平均值,则先冒险尝试 YGC;
  • 如果老年代最大连续空间,小于历次晋升老年代的平均值,则直接 FullGC;

2 JVM优化原则

2.1 优化目标

2.1.1 尽量减少 YoungGC

2.1.2 尽量减少 FullGC

一天最多 FullGC 一次,最好在系统空闲期(如深夜);

2.2 优化方法

2.2.1 代码角度

缩短对象生命期,尤其是大对象

2.2.2 JVM参数角度

优化JVM参数以减少YGC/FGC次数,可替换收集器

3 服务端开启 JMX/jstatd

这两项功能必须开启,下面的可视化工具 VisualVM 要用到。

3.1 设置系统环境变量

1
2
3
4
### 下文多处用到此变量,统一维护在系统环境变量里
# sudo vim /etc/profile
set JMX_HOSTNAME=192.168.214.128 ## Windows
export JMX_HOSTNAME=192.168.214.128 ## Linux

选项 java.rmi.server.hostname 的作用:服务器把该值传给 VisualVM,VisualVM 使用该地址查找 RMI 服务,所以必须是客户可以访问的 RMI 服务器的外网地址。
重新登录后生效。

3.2 开启 JMX(指定端口 1090)

需要注意的是,如果服务端 JMX 开启了修改和控制权限,此时如果不验证监控客户端的身份,那么所有用户都可以修改和控制 Tomcat 服务,所以重要的服务器应该开启用户名和密码验证。

3.2.1 准备用户验证文件

1
2
3
4
5
6
7
### Windows
copy/b "%JAVA_HOME%\jre\lib\management\jmxremote.password.template" "%CATALINA_BASE%\conf\jmxremote.password"
copy/b "%JAVA_HOME%\jre\lib\management\jmxremote.access" "%CATALINA_BASE%\conf\jmxremote.access"

### Linux
cp "$JAVA_HOME/jre/lib/management/jmxremote.password.template" "$CATALINA_BASE/conf/jmxremote.password"
cp "$JAVA_HOME/jre/lib/management/jmxremote.access" "$CATALINA_BASE/conf/jmxremote.access"

jmxremote.password 用于设置各个【用户名|密码】

1
2
# sudo vim $CATALINA_BASE/conf/jmxremote.password
jmxadmin jmxpwd

jmxremote.access 用于设置各个【用户名|权限】

1
2
3
4
# sudo vim $CATALINA_BASE/conf/jmxremote.access
jmxadmin readwrite \
create javax.management.monitor.*,javax.management.timer.* \
unregister

3.2.2 修改用户验证文件权限

安全起见,JMX 限制其他用户不可读这两个用户验证文件。
默认情况下,Windows/Linux 下分别会报如下错误:

  • 错误: 必须限制口令文件读取访问权限: %CATALINA_BASE%\conf\jmxremote.password
  • Error: Password file read access must be restricted: $CATALINA_BASE/conf/jmxremote.password

Windows下可按如下操作修改文件权限:

  • 右键单击文件 jmxremote.password,弹出菜单中选“属性”,再点“安全”/“高级”/“更改权限”/“包括可从该对象的父项继承的权限”(弹出窗口中选“删除”以删除所有访问权限);
  • 再选“添加”/“高级”/“立即查找”,选中你的用户(如 WKF-PC),点“确定”;
  • 权限项目窗口中勾选“完全控制”,点“确定”。

Linux 下则更简单:

1
sudo chmod 600 $CATALINA_BASE/conf/jmxremote.*

3.2.3 修改 Tomcat 启动时 JVM 选项

如果测试服务器无需开启用户验证,只需修改下面参数 authenticate=false, 并去掉 password.file 和 access.file 两个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
### Windows: %CATALINA_HOME%\bin\startup.bat
set CATALINA_OPTS=%CATALINA_OPTS% -Djava.rmi.server.hostname=%JMX_HOSTNAME% ^
-Dcom.sun.management.jmxremote=true ^
-Dcom.sun.management.jmxremote.port=1090 ^
-Dcom.sun.management.jmxremote.ssl=false ^
-Dcom.sun.management.jmxremote.authenticate=true ^
-Dcom.sun.management.jmxremote.password.file=%CATALINA_BASE%\conf\jmxremote.password ^
-Dcom.sun.management.jmxremote.access.file=%CATALINA_BASE%\conf\jmxremote.access

### Linux: sudo vim $CATALINA_HOME/bin/startup.sh
export CATALINA_OPTS="$CATALINA_OPTS -Djava.rmi.server.hostname=$JMX_HOSTNAME
-Dcom.sun.management.jmxremote=true
-Dcom.sun.management.jmxremote.port=1090
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.password.file=$CATALINA_BASE/conf/jmxremote.password
-Dcom.sun.management.jmxremote.access.file=$CATALINA_BASE/conf/jmxremote.access"

重启 Tomcat 生效。

3.3 开启 jstatd agent(默认端口 1099)

1
2
3
4
5
### 可借鉴 $JAVA_HOME/jre/lib/security/java.policy
# sudo vim $CATALINA_BASE/conf/jstatd.policy
grant codeBase "file:${java.home}/../lib/tools.jar" {
permission java.security.AllPermission;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# sudo vim $CATALINA_HOME/bin/jstatd.sh
# chmod +x $CATALINA_HOME/bin/jstatd.sh
#!/bin/sh

PID=`jps | grep Jstatd | awk '{print $1}'`
if [ -n "$PID" ]; then
echo "kill -9 $PID ..."
kill -9 $PID
fi

jstatd -J-Djava.rmi.server.hostname=$JMX_HOSTNAME \
-J-Djava.security.policy=$CATALINA_BASE/conf/jstatd.policy -J-Djava.rmi.server.logCalls=true \
-J-Xms128M -J-Xmx128M -J-XX:NewRatio=1 -J-XX:SurvivorRatio=8 -J-XX:PermSize=16M -J-XX:MaxPermSize=16M \
< /dev/null &> $CATALINA_BASE/logs/jstatd.log &

执行 $CATALINA_HOME/bin/jstatd.sh 后生效。

3.4 配置防火墙(放行端口 1090/1099)

1
2
3
4
# sudo vim /etc/sysconfig/iptables
-A INPUT -m state --state NEW -m tcp -p tcp --dport 1090 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 1099 -j ACCEPT
### 重启生效: systemctl restart iptables

4 可视化工具

4.1 JConsole

早期的 Java 故障和监控工具,现在可以由强大的 VisualVM 代替。

4.2 VisualVM

4.2.1 文档

http://m.blog.csdn.net/article/details?id=51090115
http://docs.oracle.com/javase/8/docs/technotes/guides/visualvm/index.html

4.2.2 下载与安装

可以使用 JDK 自带的 %JAVA_HOME%\bin\jvisualvm.exe;
也可以使用最新版本,下载方法如下:
下载页:https://visualvm.github.io/download.html
主程序:https://github.com/visualvm/visualvm.src/releases/download/1.3.9/visualvm_139.zip
汉化包:https://github.com/visualvm/visualvm.src/releases/download/1.3.9/visualvm_139-ml.zip
IDE插件:https://visualvm.github.io/idesupport.html
主程序与汉化包解压到同一目录,最终主程序为 bin\visualvm.exe;
IDE插件支持 Eclipse/IntelliJ IDEA,可以随着 IDE 启动。

配置 VisualVM 插件中心(默认的配置中心已失效)
菜单:工具 / 插件 / “设置”、“编辑”,修改为:
https://visualvm.github.io/uc/release139/updates.xml.gz
更多可用地址列表:https://visualvm.github.io/pluginscenters.html

4.2.3 安装 VisualGC 插件

http://www.oracle.com/technetwork/java/visualgc-136680.html
VisualVM 中,点击菜单“工具”/“插件”,如下图安装:

有些插件不能成功安装,可下载 nbm 包再手工安装:
https://visualvm.github.io/plugins.html
http://visualvm.java.net/pluginscenters.html
或者在插件设置里,修改或新增插件源(pluginscenters.html里点击后能找到),再安装。
http://bits.netbeans.org/VisualVM/uc/release138/updates.xml
http://bits.netbeans.org/VisualVM/uc/8u40/updates.xml
几款有用的插件:

  • BTrace Workbench(只适用于本地应用): 用于不停止进程的情况下添加代码跟踪,入口在应用的右键菜单里;
  • KillApplication(只适用于本地应用): 用于杀掉进程,入口在应用的右键菜单里;
  • VisualVM-MBeans(本地和远程通用):类似于 JConsole 展示应用的 MBean,包括值、操作、通知等;
  • Tracer(本地和远程通用): 以时间曲线图展示多种指标,包括CPU/GC/Heap/PermGen/Classes/Threads等。

4.2.4 前提条件

对于本地应用,VisualVM 所有功能直接支持它;
对于远程应用,VisualGC 标签页要求服务端开启 jstatd agent,其他标签页要求开启 JMX,否则会显示错误“不受此 JVM 支持”。
对于远程应用,需要注意的是,服务端 jstatd/JMX 重启后,VisualVM 必须重启或者重建 JMX 连接,否则服务端调整在 VisualVM 中不生效。

4.2.5 添加远程主机 / JMX连接

(1)添加“远程主机”,指定远程服务器的 IP 和 jstatd 端口:

(2)添加“JMX 连接”,指定远程应用的 JMX 端口、用户名和密码:

如遇连接失败,请检查 JMX_HOSTNAME 没有生效的原因。

4.2.6 监控远程应用

(1)双击左侧的“JMX 连接”(注意小图标底部有 JMX 字样),切换至“概述”标签页,可看到概述和 JVM 参数信息:

(2)切换至“监视”标签页,可看到 CPU、Heap、Class加载、线程等时间曲线图:

(3)切换至“线程”标签页,可看到各线程 CPU 耗时统计:

(4)切换至“抽样器”标签页,可看到热点方法耗时、主要对象占用空间统计(可用于定位内存泄露):

(5)切换至“VisualGC”标签页,可看到各种内存变化曲线、各GC时间点(可用于JVM参数调优):

4.2.7 JVM 优化实战

(1)优化前,没有明确指定各内存大小,使用 Java 默认内存大小,相当于指定为:

1
2
export CATALINA_OPTS="$CATALINA_OPTS -Xms52M -Xmx244M -XX:NewRatio=2
-XX:SurvivorRatio=8 -XX:PermSize=30M -XX:MaxPermSize=1G"

可见 YoungGC 很频繁:

观察图形可得出以下优化方案:

  • 机器内存为 1G,本应用为系统唯一大应用,分给它 512M,另外 512M 预留给系统和其他应用;
  • 永久代只需 30M,可指定初始和最大值为 64M,避免反复伸缩的开销;
  • 整个堆(新生代+老年代)目前内存为 240M,可增加至 384M,指定初始和最大值都为 384M,避免反复伸缩的开销;
  • YoungGC 过于频繁,原因是 Eden 区过小。“老年代/新生代”目前比例为 -XX:NewRatio=2,看图形可知,老年代中长寿对象并不多,可缩减老年代让给新生代,所以调整 -XX:NewRatio=1;
  • “Eden / Survivor0”比值目前为 -XX:SurvivorRatio=8,暂不调整。

(2)优化后,指定 JVM 选项为:

1
2
3
### Linux: $CATALINA_HOME/bin/startup.sh
export JAVA_OPTS="$JAVA_OPTS -Xms384M -Xmx384M -XX:NewRatio=1
-XX:SurvivorRatio=8 -XX:PermSize=64M -XX:MaxPermSize=64M"

可见 YoungGC 大幅减少:

(3)优化前后对比:
优化前:YoungGC 102次,总耗时 965ms, FullGC 5次,总耗时 240ms,而且应用稳定后 YoungGC 仍然反复发生;
优化后:YoungGC 3次,总耗时 26ms, FullGC 1次,总耗时 4ms,而且应用稳定后 YoungGC 长期未再发生;
优化效果非常明显。

5 命令行工具(服务端)

5.1 jps(查看Java进程)

1
2
3
4
5
### jps -vlm | grep Bootstrap
1162 ## 进程ID
-Xms300M -Xmx300M -XX:NewRatio=2 -XX:SurvivorRatio=8 ## -v的作用,显示 JVM 参数
org.apache.catalina.startup.Bootstrap ## -l的作用,显示主类的全名(或JAR包路径)
start ## -m 的作用,显示传递给主类 main() 函数的参数

5.2 jmap/jhat(快照的生成与查看)

1
2
3
4
5
6
7
8
9
10
11
12
### 服务器上执行,生成 heapdump 快照文件
jmap -dump:live,format=b,file=tomcat.hprof 1162
sz tomcat.hprof ## 下载到个人PC

### 创建WEB服务,个人PC上可以用浏览器查看 http://localhost:8080/
### 功能较弱,以 package 分组展示,“Show heap histogram”可用于分析内存泄露。
### 也可以用 VisualVM 打开快照文件来查看
jhat -port 8080 tomcat.hprof

### jmap 更多功能
jmap -heap 1162 ## 查看堆内存划分、各区块尺寸、已用容量和比率等
jmap -histo:live 1162 ## 可用于排查内存泄露,查看堆中(仅存活的)各 class 的实例数、总占用空间

5.3 jstack

1
jstack 1162 > jstack.log ## 可生成应用的各线程 StackTrace

5.4 jcmd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
jcmd ## 类似于 jps,查看 java 进程
jcmd 1162 help ## 查看该进程支持的命令

jcmd 1162 VM.uptime ## 显示启动时间
jcmd 1162 VM.flags ## 显示VM标志
jcmd 1162 VM.version ## 查看虚拟机版本
jcmd 1162 VM.command_line ## 显示命令行及参数
jcmd 1162 VM.system_properties ## 显示系统属性

jcmd 1162 GC.run ## 执行 System.gc()
jcmd 1162 GC.run_finalization ## 执行 System.runFinalization()
jcmd 1162 GC.class_histogram ## 相当于 jmap -histo:live 1162
jcmd 1162 GC.heap_dump tomcat.hprof ## 生成堆快照文件
jcmd 1162 Thread.print ## 相当于 jstack 1162
jcmd 1162 PerfCounter.print ## 显示进程内各种计数器

6 开发监控系统

假设有个需求(这里只是为了演示,现实中不会这么简单粗暴,现实中还需要限制在凌晨才触发):Heap 使用率高于 60%,就要求强制 FullGC。

6.1 Shell 脚本

1
2
3
4
5
6
7
8
9
PID=`jps | grep Bootstrap | awk '{print $1}'`

## 计算 Heap 使用率
## 下面命令会输出 Eden/S0/S1/Tenured 各自的总空间和已用空间,很容易算出 Heap 使用率:
## used(Eden+S0+S1+Tenured)/total(Eden+S0+S1+Tenured)
# jstat -gc $PID

## 判断使用率大于 60% 才执行 FullGC
jcmd $PID GC.run

6.2 Java 代码

代码中使用的“java.lang:type=Memory”、“HeapMemoryUsage”可以借助 VisualVM/MBeans插件的 Attribtes/Operations 标签页中找到,如下图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package wang.kefeng.JmxAdmin;

import java.lang.management.MemoryUsage;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author http://kefeng.wang
* @date 2016-11-24 20:20:08
*/
public class App {
private static final String JMX_HOSTNAME = "192.168.214.128:1090";
private static final String JMX_USERNAME = "jmxadmin";
private static final String JMX_PASSWORD = "jmxpwd";

private static final Logger logger = LoggerFactory.getLogger(App.class);

public static void main(String[] args) throws Exception {
// 1.建立连接(mbsc)
String jmxURL = "service:jmx:rmi:///jndi/rmi://" + JMX_HOSTNAME + "/jmxrmi";
JMXServiceURL serviceURL = new JMXServiceURL(jmxURL);

Map<String, Object> map = new LinkedHashMap<>();
map.put("jmx.remote.credentials", new String[] { JMX_USERNAME, JMX_PASSWORD });
JMXConnector connector = JMXConnectorFactory.connect(serviceURL, map);
MBeanServerConnection mbsc = connector.getMBeanServerConnection();

// 2.获取远程应用数据
ObjectName objectName = new ObjectName("java.lang:type=Memory");
Object heapMemoryUsage = mbsc.getAttribute(objectName, "HeapMemoryUsage");
MemoryUsage usage = MemoryUsage.from((CompositeDataSupport) heapMemoryUsage);

// 示例:如果 Heap 使用率大于 60%,则请求 FullGC
long usedRate = usage.getUsed() * 100 / usage.getMax();
logger.info("usedRate={}, {} / {}", usedRate, usage.getUsed(), usage.getMax());
if (usedRate > 30) {
mbsc.invoke(objectName, "gc", null, null);
logger.info("gc() OK.");
}

// 3.更多有用的 MBeans
// (1)com.sun.management:type=HotSpotDiagnostic
// dumpHeap(), getVMOption(), setVMOption()
// (2)java.util.logging:type=Logging
// getLoggerLevel(), setLoggerLevel()
}
}

7 内存泄露插件

7.1 内存泄露现象

老年代越来越大,GC越来越频繁、执行时间越来越长,而且GC后内存未释放。

7.2 生成快照文件(hprof Heap信息文件)

  • 使用Java的jmap命令来生成;
  • 通过JMX的MBean用Java代码生成;

7.3 分析dump文件

  • 最佳方案是使用 Eclipse MAT 插件;
  • 其他候选方案:Java 自带工具 Visual VM / jhat、IBM HeapAnalyzer;

7.4 分析内存泄漏

可看到可疑的内存泄露对象,看到占用空间大的对象及其调用关系。

8 GC日志

8.1 开启gc日志文件

1
2
3
### Linux: $CATALINA_HOME/bin/startup.sh
# -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:$CATALINA_BASE/logs/gc.log"

8.2 日志文件格式解析

[停顿类型
  [新生代: GC前本区域已用容量 -> GC后本区域已用容量 (本区域总容量), 本区域GC耗时]
  [老年代: GC前本区域已用容量 -> GC后本区域已用容量 (本区域总容量), 本区域GC耗时]
    GC前Heap已用容量 -> GC后Heap已用容量 (Heap总容量)
  [永久代: GC前本区域已用容量 -> GC后本区域已用容量 (本区域总容量), 本区域GC耗时]
  [Times: 用户耗时=xx 系统耗时=yy, 实际耗时=zz secs]

==== server 模式的 GC+FullGC ====
新生代:PSYoungGen=Parallel Scavenge
老年代:ParOldGen=Parallel Old
永久代:PSPermGen

1
2
3
4
5
6
7
8
9
10
[GC
[PSYoungGen: 6980K->600K(9216K)]
6980K->6744K(19456K), 0.0028901 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC
[PSYoungGen: 600K->0K(9216K)]
[ParOldGen: 6144K->6617K(10240K)]
6744K->6617K(19456K)
[PSPermGen: 2567K->2566K(21504K)], 0.0095238 secs]
[Times: user=0.02 sys=0.00, real=0.02 secs]

==== client 模式的 GC+FullGC ====
新生代:DefNew=Serial(Default New Generation)
老年代:Tenured
永久代:Perm

1
2
3
4
5
6
7
8
9
10
11
[GC
[DefNew: 6871K->382K(9216K), 0.0037941 secs]
[Tenured: 6144K->6525K(10240K), 0.0033262 secs]
6871K->6525K(19456K),
[Perm : 186K->186K(12288K)], 0.0081090 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC
[Tenured: 6525K->6514K(10240K), 0.0026225 secs]
6525K->6514K(19456K),
[Perm : 186K->186K(12288K)], 0.0028221 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]

=================================

8.3 gc.log 解析工具

8.3.1 在线分析

http://gceasy.io/

8.3.2 ga456(IBM)

https://www.ibm.com/developerworks/community/alphaworks/tech/pmat
ftp://public.dhe.ibm.com/software/websphere/appserv/support/tools/pmat/ga456.jar

8.3.3 HPjmeter

https://h20392.www2.hpe.com/portal/swdepot/displayProductInfo.do?productNumber=HPJMETERSW
需要注册帐号并登录,最终下载页中选择“Use Standard Download”,Windows上可下载这两个:
MS Windows XP/Vista/7 HPjmeter 4.4.00.00 Console - Oct 2014 (Z7550-01611_hpjmeter_console_4.4.00.00_windows_setup.exe)
HPjmeter 4.4.00.00 Console jar file zip format - Feb 2015 (Z7550-63266_hpjmeter_4.4.00.00.zip)

8.3.4 GCViewer

http://www.tagtraum.com/gcviewer.html
http://www.tagtraum.com/gcviewer-download.html
http://www.tagtraum.com/download/gcviewer-1.29-bin.zip

8.3.5 IBM GCMV(Eclipse 插件)

GCMV=Garbage Collection and Memory Visualizer
插件中心搜索 “GCMV” 来安装,然后在菜单中打开: Window / Perspective / Open Perspective / GCMV

8.3.6 gchisto(VisualVM 插件)

https://gchisto.dev.java.net/
http://java.net/projects/gchisto
http://pietrowski.info/wp-content/uploads/2009/06/GCHisto.tgz

8.3.7 其他更多

https://code.google.com/archive/p/gclogviewer/
https://code.google.com/archive/p/verbosegcanalyzer
http://fasterj.com/tools/gcloganalysers.shtml
http://techblog.netflix.com/2013/05/garbage-collection-visualization.html

文章目录
  1. 1 GC相关内存
    1. 1.1 内存划分
      1. 1.1.1 堆(Heap)
      2. 1.1.2 永久代(Permanent Generation)
      3. 1.1.3 类元数据(Metaspace)
      4. 1.1.4 实例解析
    2. 1.2 JVM内存分配策略
      1. 1.2.1. 对象优先分配在 Eden
      2. 1.2.2. 大对象直接分配在老年代
      3. 1.2.3. 长期存活对象移入老年代
      4. 1.2.4. 永久代满了也会导致 FullGC
      5. 1.2.5. 动态对象年龄判定
      6. 1.2.6. 冒险模式
  2. 2 JVM优化原则
    1. 2.1 优化目标
      1. 2.1.1 尽量减少 YoungGC
      2. 2.1.2 尽量减少 FullGC
    2. 2.2 优化方法
      1. 2.2.1 代码角度
      2. 2.2.2 JVM参数角度
  3. 3 服务端开启 JMX/jstatd
    1. 3.1 设置系统环境变量
    2. 3.2 开启 JMX(指定端口 1090)
      1. 3.2.1 准备用户验证文件
      2. 3.2.2 修改用户验证文件权限
      3. 3.2.3 修改 Tomcat 启动时 JVM 选项
    3. 3.3 开启 jstatd agent(默认端口 1099)
    4. 3.4 配置防火墙(放行端口 1090/1099)
  4. 4 可视化工具
    1. 4.1 JConsole
    2. 4.2 VisualVM
      1. 4.2.1 文档
      2. 4.2.2 下载与安装
      3. 4.2.3 安装 VisualGC 插件
      4. 4.2.4 前提条件
      5. 4.2.5 添加远程主机 / JMX连接
      6. 4.2.6 监控远程应用
      7. 4.2.7 JVM 优化实战
  5. 5 命令行工具(服务端)
    1. 5.1 jps(查看Java进程)
    2. 5.2 jmap/jhat(快照的生成与查看)
    3. 5.3 jstack
    4. 5.4 jcmd
  6. 6 开发监控系统
    1. 6.1 Shell 脚本
    2. 6.2 Java 代码
  7. 7 内存泄露插件
    1. 7.1 内存泄露现象
    2. 7.2 生成快照文件(hprof Heap信息文件)
    3. 7.3 分析dump文件
    4. 7.4 分析内存泄漏
  8. 8 GC日志
    1. 8.1 开启gc日志文件
    2. 8.2 日志文件格式解析
    3. 8.3 gc.log 解析工具
      1. 8.3.1 在线分析
      2. 8.3.2 ga456(IBM)
      3. 8.3.3 HPjmeter
      4. 8.3.4 GCViewer
      5. 8.3.5 IBM GCMV(Eclipse 插件)
      6. 8.3.6 gchisto(VisualVM 插件)
      7. 8.3.7 其他更多