JVM(三)性能调优及实战

六、jvm常用命令

Sun JDK监控和故障处理命令有 jps、jstat、jmap、jhat、jstack、jinfo。

1. jps

JVM Process Status Tool,显示指定系统内所有的 HotSpot 虚拟机进程。

2. jstat

jstat(JVM statistics Monitoring)是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。

3. jmap

dump 堆到文件,可用于对文件的分析。

4. jhat

jhat(JVM Heap Analysis Tool)命令是与 jmap 搭配使用,用来分析 jmap 生成的 dump,jhat 内置了一个微型的 HTTP/HTML 服务器,生成 dump 的分析结果后,可以在浏览器中查看。

在此要注意,一般不会直接在服务器上进行分析,因为 jhat 是一个耗时并且耗费硬件资源的过程,一般把服务器生成的 dump 文件复制到本地或其他机器上进行分析。

5. jstack

jstack 用于生成 java 虚拟机当前时刻的线程快照。

线程快照是当前 java 虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。

线程出现停顿的时候通过 jstack 来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

七、jvm性能检测工具

1.jconsole

1.1 概念

(1)Jconsole(Java Monitoring and Management Console)是从 java5 开始,在 JD K中自带的 java 监控和管理控制台,
(2)用于对 JVM 中内存,线程和类等的监控,
(3)是一个基于 JMX(java management extensions)的 GUI 性能监测工具。
(4)jconsole 使用 jvm 的扩展机制获取,并展示虚拟机中运行的应用程序的性能和资源消耗等信息。

1.2 图

1.2.1 概览图

包括堆内存使用情况、线程、类、CPU 使用情况四项信息的曲线图。
在这里插入图片描述

1.2.2 线程图

相当于可视化的 jstack 命令,同时也可以点击 “检测死锁” 来检查线程之间是否有死锁的情况。

在这里插入图片描述

2. VisualVM

2.1概念

(1)VisualVM(All-in-One Java Troubleshooting Tool)是功能最强大的运行监视和故障处理程序之一,曾经在很长一段时间内是 Oracle 官方主力发展的虚拟机故障处理工具。

(2)相比一些第三方工具,VisualVM 有一个很大的优点:不需要被监视的程序基于特殊 Agent 去运行,因此它的通用性很强,对应用程序实际性能的影响也较小,使得它可以直接应用在生产环境中。

(3)Visual GC 是常常使用的一个功能,需要通过插件,可以明显的看到年轻代、老年代的内存变化,以及 gc 频率、gc 的时间等。

2.2 图

2.2.1 GC图

在这里插入图片描述

2.2.1监控的主页

cpu、内存、类、线程的图表,这里面可以执行堆 dump。

在这里插入图片描述

2.2.1 堆 dump:

在这里插入图片描述

八、 jvm调优

高效记忆:jvm调优

(1)使用收集器(选择合适收集器)时根据大对象标准(调整大对象标准)去调整内存大小(调整内存、本地内存大小)、比率(调整内存比率)

(2)调整停顿时间(设置符合预期的停顿时间)以触发(设置合适的GC触发时机)对象升级老年代(调整对象升级老年代年龄)

1.选择合适的垃圾回收器

1.1 如何选择

CPU 单核:那么毫无疑问 Serial 垃圾收集器是你唯一的选择;

CPU 多核:关注吞吐量 ,那么选择 PS+PO 组合;

CPU 多核:关注用户停顿时间,JDK 版本 1.6 或者 1.7,那么选择 CMS;

CPU 多核:关注用户停顿时间,JDK1.8 及以上,JVM 可用内存 6G 以上,那么选择 G1。

1.2 参数配置

//设置Serial垃圾收集器(新生代)
 开启:-XX:+UseSerialGC
 
 //设置PS+PO,新生代使用功能Parallel Scavenge 老年代将会使用Parallel Old收集器
 开启 -XX:+UseParallelOldGC
 
 //CMS垃圾收集器(老年代)
 开启 -XX:+UseConcMarkSweepGC
 
 //设置G1垃圾收集器
 开启 -XX:+UseG1GC

2.调整内存大小

2.1 内存分析

(1)现象

垃圾收集频率非常频繁。

(2)原因

如果内存太小,就会导致频繁的需要进行垃圾收集才能释放出足够的空间来创建新的对象,所以增加堆内存大小的效果是非常显而易见的。

(1) 注意

如果垃圾收集次数非常频繁,但是每次能回收的对象非常少,那么这个时候并非内存太小,而可能是内存泄露导致对象无法回收,从而造成频繁 GC。

2.2 参数配置

 //设置堆初始值
 指令1-Xms2g
 指令2-XX:InitialHeapSize=2048m
 
 //设置堆区最大值
 指令1:`-Xmx2g` 
 指令2-XX:MaxHeapSize=2048m
 
 //新生代内存配置
 指令1-Xmn512m
 指令2-XX:MaxNewSize=512m

简单配置:服务器运行内存6G
堆最大内存占服务器2/3(1/2-2/3)为4G;
堆初始内存占堆最大内存的1/3(1/4-1/2)为1.25G;
新生代内存占堆最大内存的1/4(1/4-1/3)为1G;

参考链接:java应用程序启动的时候jvm分配的内存最好占用服务器可分配内存的多少

3. 设置符合预期的停顿时间

3.1 停顿时间分析

(1)现象:程序间接性的卡顿
(2)原因

如果没有确切的停顿时间设定,垃圾收集器以吞吐量为主,那么垃圾收集时间就会不稳定。

(3)注意

不要设置不切实际的停顿时间,单次时间越短也意味着需要更多的 GC 次数才能回收完原有数量的垃圾.

3.2 参数配置:

 //GC停顿时间,垃圾收集器会尝试用各种手段达到这个时间
 -XX:MaxGCPauseMillis 

4.调整内存区域大小比率

4.1 内存大小比率分析

(1)现象

某一个区域的GC频繁,其他都正常。

(2)原因

如果对应区域空间不足,导致需要频繁GC来释放空间,在JVM堆内存无法增加的情况下,可以调整对应区域的大小比率。

(3) 注意

也许并非空间不足,而是因为内存泄露造成内存无法回收,从而导致 GC 频繁。

4.2 参数配置:

 //survivor区和Eden区大小比率
 指令:-XX:SurvivorRatio=6  //S区和Eden区占新生代比率为1:6,两个S区2:6
 
 //新生代和老年代的占比
 -XX:NewRatio=4  //表示新生代:老年代 = 1:4 即老年代占整个堆的4/5;默认值=2

5.调整对象升级老年代的年龄

5.1 对象升级分析

(1)现象

老年代频繁 GC,每次回收的对象很多。

(2)原因

(1)如果升代年龄小,新生代的对象很快就进入老年代了,导致老年代对象变多,
(2)而这些对象其实在随后的很短时间内就可以回收,这时候可以调整对象的升级代年龄,
(3)让对象不那么容易进入老年代解决老年代空间不足频繁 GC 问题。

(3)注意

(1)增加了年龄之后,这些对象在新生代的时间会变长可能导致新生代的 GC 频率增加,
(2)并且频繁复制这些对象新生的 GC 时间也可能变长。

5.2 配置参数:

//进入老年代最小的GC年龄,年轻代对象转换为老年代对象最小年龄值,默认值7
 -XX:InitialTenuringThreshol=7 

6.调整大对象的标准

6.1 大对象分析

(1)现象

老年代频繁 GC,每次回收的对象很多,而且单个对象的体积都比较大。

(2)原因

如果大量的大对象直接分配到老年代,导致老年代容易被填满而造成频繁 GC,可设置对象直接进入老年代的标准。

(3)注意

这些大对象进入新生代后可能会使新生代的 GC 频率和时间增加。

6.2 配置参数:

 //新生代可容纳的最大对象,大于则直接会分配到老年代,0代表没有限制。
  -XX:PretenureSizeThreshold=1000000 

7.调整GC的触发时机

7.1 GC的触发时机分析

(1)现象:CMS,G1 经常 Full GC,程序卡顿严重。
(2)原因

(1)G1 和 CMS 部分 GC 阶段是并发进行的,
(2)业务线程和垃圾收集线程一起工作,也就说明垃圾收集的过程中业务线程会生成新的对象,所以在 GC 的时候需要预留一部分内存空间来容纳新产生的对象,
(3)如果这个时候内存空间不足以容纳新产生的对象,那么JVM就会停止并发收集暂停所有业务线程(STW)来保证垃圾收集的正常运行。
(4)这个时候可以调整GC触发的时机(比如在老年代占用 60% 就触发 GC),这样就可以预留足够的空间来让业务线程创建的对象有足够的空间分配。

(3)注意

提早触发 GC 会增加老年代 GC 的频率。

7.2 配置参数:

 //使用多少比例的老年代后开始CMS收集,默认是68%,如果频繁发生SerialOld卡顿,应该调小
 -XX:CMSInitiatingOccupancyFraction
 
 //G1混合垃圾回收周期中要包括的旧区域设置占用率阈值。默认占用率为 65%
 -XX:G1MixedGCLiveThresholdPercent=65 

8.调整 JVM本地内存大小

8.1 JVM本地内存分析

(1)现象

GC 的次数、时间和回收的对象都正常,堆内存空间充足,但是报 OOM

(2)原因

JVM 除了堆内存之外还有一块堆外内存,这片内存也叫本地内存,
可是这块内存区域不足了并不会主动触发 GC,只有在堆内存区域触发的时候顺带会把本地内存回收了,
而一旦本地内存分配不足就会直接报 OOM 异常。

(3)注意

本地内存异常的时候除了上面的现象之外,异常信息可能是 OutOfMemoryError:Direct buffer memory。
解决方式除了调整本地内存大小之外,也可以在出现此异常时进行捕获,手动触发 GC(System.gc())。

8.2 配置参数:

 XX:MaxDirectMemorySize

九、jvm调优实战

1.App浏览量暴增后,反应页面响应很慢

1.1 推测问题

在测试环境测速度比较快,但是一到生产就变慢,可能是因为垃圾收集导致的业务线程停顿。

1.2 定位、确定问题

(1)为了确认推测的正确性,在线上通过 jstat -gc 指令 看到 JVM 进行 GC 次数频率非常高,GC 所占用的时间非常长,
(2)所以基本推断就是因为 GC 频率非常高,所以导致业务线程经常停顿,从而造成网页反应很慢。

1.3 解决问题

(1)因为App访问量很高,所以对象创建速度非常快,导致堆内存容易填满从而频繁 GC,
(2)这里问题在于新生代内存太小,增加 JVM 内存就行了,
(3)于是从原来的 2G 内存增加到 4G 内存。

1.4 又出现问题

(1)增加内存后的确平常的请求比较快了,
(2)但是又出现了另外一个问题,就是不定期的会间断性的卡顿,而且单次卡顿的时间要比之前要长很多。

1.5 推测问题

之前的优化加大了内存,所以推测可能是因为内存加大了,从而导致单次 GC 的时间变长从而导致间接性的卡顿。

1.6 定位、确定问题

(1)还是通过 jstat -gc 指令 查看到 的确 FGC 次数并不是很高,
(2)但是花费在 FGC 上的时间是非常长的,根据 GC 日志 查看到单次 FGC 的时间有达到几十秒的。

1.7 解决问题

(1)因为 JVM 默认使用的是 PS+PO 的组合,PS+PO 垃圾标记和收集阶段都是 STW,
(2)内存加大了之后,需要进行垃圾回收的时间就变长了,
(3)这里要想避免单次 GC 时间过长,就更换并发类的收集器,
(4)我们的 JDK 版本为 1.8,而且我们的内存够大,最后选择 G1垃圾收集器,
(5)并根据之前垃圾收集情况设置了一个预期的停顿的时间,上线后网站再也没有了卡顿问题。

2.后台导出数据引发的 OOM

问题描述:公司的后台系统,偶发性的引发 OOM 异常,堆内存溢出。

2.1 加大堆内存

因为是偶发性的,可能是堆内存不足导致,于是把堆内存从 2G 调整到 4G。

2.2 打印堆内存dump文件

(1)问题依然没有解决,再从堆内存信息下手。
(2)开启 -XX:+HeapDumpOnOutOfMemoryError 参数,获得堆内存的 dump 文件。

2.3 使用VisualVM 对堆 dump 文件分析

(1)通过 VisualVM 查看到占用内存最大的对象是 String 对象。
(2)本来想跟踪着 String 对象找到其引用的地方,但 dump 文件太大,跟踪进去的时候总是卡死,
(3)而 String 对象占用比较多也比较正常,于是就先从线程信息里面找突破点。

2.4 线程分析

(1)线程分析先找到了几个正在运行的业务线程,然后逐一跟进业务线程看了下代码,发现有个引起我注意的方法,导出订单信息。

备注: 如何通过进程找到线程,再找到代码,请看 Java基础之JVM(一) 中的 14.cpu使用率飙升,怎么排查?

2.5 推测问题

(1)因为订单信息导出这个方法可能会有几万的数据量,
(2)首先要从数据库里面查询出来订单信息,然后把订单信息生成 excel,这个过程会产生大量的 String 对象。

2.6 定位、确定问题

(1)为了进一步确定,于是登录后台去测试下,
(2)在测试的过程中发现订单的按钮前端,居然没有做点击后按钮置灰交互事件,结果按钮可以一直点,
(3)因为导出订单数据本来就非常慢,使用的人员可能发现点击后很久后页面都没反应,然后就一直点,
(4)结果就大量的请求进入到后台,堆内存产生了大量的订单对象和 EXCEL 对象,
(5)而且方法执行非常慢,导致这一段时间内这些对象都无法被回收,所以最终导致内存溢出。

2.7 解决问题

(1)最终没有再继续调整任何 JVM 参数,只是在前端的导出订单按钮上加上了置灰状态,等后端响应之后按钮才可以进行点击,
(2)然后减少了查询订单信息的非必要字段来减少生成对象的体积,问题就解决了。


上一篇跳转 ---- JVM(二)垃圾回收及收集器


参考链接1

参考链接2


随心所往,看见未来。Follow your heart,see light!

*欢迎点赞、关注、留言,收藏及转发,一起学习、交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值