失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 【JVM 学习笔记 05】:JVM性能调优工具的使用和优化案例

【JVM 学习笔记 05】:JVM性能调优工具的使用和优化案例

时间:2024-05-18 11:25:24

相关推荐

【JVM 学习笔记 05】:JVM性能调优工具的使用和优化案例

【JVM 学习笔记 05】:JVM性能调优工具的使用

1、 使用 jstat(命令行工具) 查看线上系统的JVM运行状况1.1 常用命令1.2 使用技巧1.2.1 随着系统运行,每秒钟会在年轻代的Eden区分配多少对象。1.2.2 Young GC的触发频率和每次耗时1.2.3 每次Young GC后有多少对象是存活和进入老年代1.2.4 Full GC的触发时机和耗时2、使用jmap和jhat摸清线上系统的对象分布2.1 使用jmap了解系统运行时的内存区域2.2 使用jmap了解系统运行时的对象分布2.3 使用jmap生成堆内存转储快照2.4 使用jhat在浏览器中分析堆转出快照2、结合工具,从测试到上线:如何分析JVM运行状况及合理优化?2.1 开发好系统之后的预估性优化2.2 系统压测时的JVM优化2.3 对线上系统进行JVM监控3、案例分析:每秒10万并发的BI系统,如何定位和解决频繁Young GC问题?4、案例分析:案例实战:每日百亿数据量的实时分析引擎,如何定位和解决频繁Full GC问题?案例分析...

1、 使用 jstat(命令行工具) 查看线上系统的JVM运行状况

jstat 可以轻易的看到当前运行中的系统,JVM的Eden、Survivor、老年代的内存使用情况,还有Young GC和Full gC的执行 次数以及耗时。

通过这些指标,我们可以轻松的分析出当前系统的运行情况,判断当前系统的内存使用压力以及GC压力,还有就是内存分配是否合理。

1.1 常用命令

jstat -gc PID

在生产机器 linux上,用jps命令找出 Java进程的PID。

针对我们的Java进程执行:jstat -gc PID。这就可以看到这个Java进程(其实本质就是一个JVM)的内存和GC情况了。

运行这个命令之后会看到如下列:

S0C:这是From Survivor区的大小

S1C:这是To Survivor区的大小

S0U:这是From Survivor区当前使用的内存大小

S1U:这是To Survivor区当前使用的内存大小

EC:这是Eden区的大小

EU:这是Eden区当前使用的内存大小

OC:这是老年代的大小

OU:这是老年代当前使用的内存大小

MC:这是方法区(永久代、元数据区)的大小

MU:这是方法区(永久代、元数据区)的当前使用的内存大小

YGC:这是系统运行迄今为止的Young GC次数

YGCT:这是Young GC的耗时

FGC:这是系统运行迄今为止的Full GC次数

FGCT:这是Full GC的耗时

GCT:这是所有GC的总耗时

其他命令

jstat -gccapacity PID:堆内存分析 jstat -gcnew PID:年轻代GC分析,这里的TT和MTT可以看到对象在年轻代存活的年龄和存活的最大年龄 jstat -gcnewcapacity PID:年轻代内存分析 jstat -gcold PID:老年代GC分析 jstat -gcoldcapacity PID:老年代内存分析 jstat -gcmetacapacity PID:元数据区内存分析

一般来说最完整、最常用、最实用的还是jstat -gc命令,基本 足够我们日常分析jvm的运行情况了。

1.2 使用技巧

我们分析线上的JVM进程,最想要知道的信息包括如下:

新生代对象增长的速率,Young GC的触发频率,Young GC的耗时,每次Young GC后有多少对象是存活下来的,每次Young GC过后有多少对象进入了老年代,老年代对象增长的速率,Full GC的触发频率,Full GC的耗时。

根据这些信息,就可以进行JVMGC优化,合理分配内存空间,尽可能让对象留在年轻 代不进入老年代,避免发生频繁的Full GC。这就是对JVM最好的性能优化了!

1.2.1 随着系统运行,每秒钟会在年轻代的Eden区分配多少对象。

使用命令:

jstat -gc PID 1000 10

每隔1秒钟更新出来最新的一行jstat统计信息,一共执行10次jstat统计。

通过这个命令,你可以非常灵活的对线上机器通过固定频率输出统计信息,观察每隔一段时间的jvm中的Eden区对象占用变化。

这里可以根据自己系统的情况灵活多变的使用,比如你们系统负载很低,不一定每秒都有请求,那么可以把 上面的1秒钟调整为1分钟,甚至10分钟,去看你们系统每隔1分钟或者10分钟大概增长多少对象。

还有就是一般系统都有高峰和日常两种状态,比如系统高峰期用的人很多,此时你就应该在系统高峰期去用上述命令 看看高峰期的对象增长速率。然后你再得在非高峰的日常时间段内看看对象的增长速率。

按照上述思路,基本上你可以对线上系统的高峰和日常两个时间段内的对象增长速率有很清晰的了解。

1.2.2 Young GC的触发频率和每次耗时

多久触发一次Young GC根据系统高峰和日常时候的对象增长速率,那么非常简单就可以推测出来高峰期多久发生一次Young GC,日常期多久发生一次Young GC。

jstat会告诉你迄今为止系统已经发生了多少次Young GC以及这些Young GC的总耗时。也可以推算

比如系统运行24小时后共发生了260次Young GC,总耗时为20s。那么平均下来每次Young GC大概就耗时几十毫秒 的时间。

你大概就知道每次Young GC的时候会导致系统停顿几十毫秒。

1.2.3 每次Young GC后有多少对象是存活和进入老年代

每次Young GC过后有多少对象会存活下来,这个没法直接看出来,但是有办法可以大致推测出来。

之前我们已经推算出来高峰期的时候多久发生一次Young GC,比如3分钟会有一次Young GC,那么此时我们可以执行下述jstat命令:jstat -gc PID 180000 10。这就相当于是让他每隔三分钟执行一次统计,连续 执行10次。

此时大家可以观察一下,每隔三分钟之后发生了一次Young GC,此时Eden、Survivor、老年代的对象变化。

正常来说,Eden区肯定会在几乎放满之后重新变得里面对象很少,比如800MB的空间就使用了几十MB。Survivor区 肯定会放入一些存活对象,老年代可能会增长一些对象占用。所以这里的关键,就是观察老年代的对象增长速率。

从一个正常的角度来看,老年代的对象是不太可能不停的快速增长的,因为普通的系统其实没那么多长期存活的对 象。如果你发现比如每次Young GC过后,老年代对象都要增长几十MB,那很有可能就是你一次Young GC过后存活对象太多了。

存活对象太多,可能导致放入Survivor区域之后触发了动态年龄判定规则进入老年代,也可能是Survivor区域放不下了,所以大部分存活对象进入老年代。

最常见的就是这种情况。如果你的老年代每次在Young GC过后就新增几百KB,或者几MB的对象,这个还算情有可缘,但是如果老年代对象快速增长,那一定是不正常的。

所以通过上述观察策略,你就可以知道每次Young GC过后多少对象是存活的,实际上Survivor区域里的和进入老年代的对象,都是存活的。

你也可以知道老年代对象的增长速率,比如每隔3分钟一次Young GC,每次会有50MB对象进入老年代,这就是年代 对象的增长速率,每隔3分钟增长50MB。

1.2.4 Full GC的触发时机和耗时

只要知道了老年代对象的增长速率,那么Full GC的触发时机就很清晰了,比如老年代总共有800MB的内存,每隔3分 钟新增50MB对象,那么大概每小时就会触发一次Full GC。

然后可以看到jstat打印出来的系统运行起劲为止的Full GC次数以及总耗时,比如一共执行了10次Full GC,共耗时 30s,每次Full GC大概就是需要耗费3s左右。

2、使用jmap和jhat摸清线上系统的对象分布

2.1 使用jmap了解系统运行时的内存区域

有的时候可能我们会发现JVM新增对象的速度很快,然后就想要去看看,到底什么对象占据了那么多的内存。

如果发现有的对象在代码中可以优化一下创建的时机,避免那种对象对内存占用过大,那么也许甚至可以去反过来优化一下代码。

当然,其实如果不是出现OOM那种极端情况,也并没有那么大的必要去着急优化代码。

命令:

jmap -heap PID

这个命令可以打印出来一系列的信息,大致来说,这个信息会打印出来堆内存相关的一些参数设置,然后就是当前堆内存里的一些基本各个区域的情况。

比如Eden区总容量、已经使用的容量、剩余的空间容量,两个Survivor区的总容量、已经使用的容量和剩余的空间容量,老年代的总容量、已经使用的容量和剩余的容量。

这些信息jstat已经有了,所以一般不会用jmap去看这些信息,因为没有gc 相关的统计。

2.2 使用jmap了解系统运行时的对象分布

jmap -histo PID

这个命令会打印出来类似下面的信息:

他会按照各种对象占用内存空间的大小降序排列,把占用内存最多的对象放在最上面。

所以如果你只是想要简单的了解一下当前jvm中的对象对内存占用的情况,只要直接用jmap -histo命令即可,非常好用

可以快速了解到当前内存里到底是哪个对象占用了大量的内存空间。

2.3 使用jmap生成堆内存转储快照

可以用jmap命令生成一个堆内存快照放到一个文件里去,用如下的命令即可:

jmap -dump:live,format=b,file=dump.hprof PID

这个命令会在当前目录下生成一个dump.hrpof文件,这里是二进制的格式,不能直接打开看,他把这一时刻JVM堆内存里所有对象的快照放到文件里去了,供你后续去分析。

2.4 使用jhat在浏览器中分析堆转出快照

接着就可以使用 jhat 去分析堆快照了,jhat 内置了web服务器,他会支持你通过浏览器来以图形化的方式分析堆转储快照

使用如下命令即可启动jhat服务器,还可以指定自己想要的http端口号,默认是7000端口号:

jhat dump.hprof -port 7000

接着在浏览器上访问当前这台机器的7000端口号,就可以通过图形化的方式去分析堆内存里的对象分布情况了。

2、结合工具,从测试到上线:如何分析JVM运行状况及合理优化?

2.1 开发好系统之后的预估性优化

在系统开发完毕之后,实际上各位同学就应该参照之前我们多个案例中介绍的思路,对系统进行预估性的优化。

自行估算系统每秒大概多少请求,每个请求会创建多少对象,占用多少内存,机器应该选用什么样的配 置,年轻代应该给多少内存,Young GC触发的频率,对象进入老年代的速率,老年代应该给多少内存,Full GC触发的频率。

这些东西其实是可以根据自己写的代码,大致合理的预估一下的。

在预估完成之后,就可以采用之前多个案例介绍的优化思路,先给自己的系统设置一些初始性的JVM参数

比如堆内存大小,年轻代大小,Eden和Survivor的比例,老年代的大小,大对象的阈值,大龄对象进入老年代的阈值,等等。

优化思路其实简单来说就一句话:尽量让每次Young GC后的存活对象小于Survivor区域的50%,都留存在年轻代里。尽量别让对象 进入老年代。尽量减少Full GC的频率,避免频繁Full GC对JVM性能的影响。

2.2 系统压测时的JVM优化

通常一个新系统开发完毕之后,就会经过一连串的测试。从本地的单元测试,到系统集成测试,再到测试环境的功能测试,预发布环境的压力测试,要保证系统的功能全部正常。而且在一定压力下性能、稳定性和并发能力都正常,最后才会部署到生产环境运行。

这里非常关键的一个环节就是预发布环境的压力测试,通常在这个环节,会使用一些压力测试工具模拟比如1000个用户同时访问系 统,造成每秒500个请求的压力,然后看系统能否支撑住每秒500请求的压力。同时看系统各个接口的响应延时是否在比如200ms之 内,也就是接口性能不能太慢,或者是在数据库中模拟出来百万级单表数据,然后看系统是否还能稳定运行。

具体如何进行系统压测有很多开源的工具,可以轻松模拟 出N个用户同时访问你系统的场景,还能给你一份压力测试报告,告诉你系统可以支撑每秒多少请求,包括系统接口的响应延时。

在这个环节,通常压测工具会对系统发起持续不断的请求,持续很长时间,比如几个小时,甚至几天时间。

所以此时,大家完全就可以在这个环节,对测试机器运行的系统,采用jstat工具来分析在模拟真实环境的压力下,JVM的整体运行状 态。

具体如何使用jstat来进行分析,之前都讲的很详细了,包括如何借助jstat的各种功能分析出来以下JVM的关键运行指标:新生代对象 增长的速率,Young GC的触发频率,Young GC的耗时,每次Young GC后有多少对象是存活下来的,每次Young GC过后有多少对象进入了老年代,老年代对象增长的速率,Full GC的触发频率,Full GC的耗时。

然后根据压测环境中的JVM运行状况,如果发现对象过快进入老年代,可能是因为年轻代太小导致频繁Young GC,然 后Young GC的时候很多对象还是存活的,结果Survivor也太小,导致很多对象频繁进入老年代。当然也可能是别的什 么原因。

此时就需要采用之前介绍的优化思路,合理调整新生代、老年代、Eden、Survivor各个区域的内存大小,保证对象尽 量留在年轻代,不要过快进入老年代中。

每个系统都是不一样的,特点不同,复杂度不同。

真正的优化,必须是根据自己的系统,实际观察之后,然后合理调整内存分布,根本没什么固定的 JVM优化模板。

当对压测环境下的系统优化好JVM参数之后,观察Young GC和Full GC频率都很低,此时就可以部署系统上线了。

2.3 对线上系统进行JVM监控

当你的系统上线之后,你就需要对线上系统的JVM进行监控,这个监控通常来说有两种办法。

第一种方法会“low”一些,其实就是每天在高峰期和低峰期都用jstat、jmap、jhat等工具去看看线上系统的JVM运 行是否正常,有没有频繁Full GC的问题。

如果有就优化,没有的话,平时每天都定时去看看,或者每周都去看看即可。

第二种方法在中大型公司里会多一些,大家都知道,很多中大型公司都会部署专门的监控系统,比较常见的有 Zabbix、OpenFalcon、Ganglia,等等。然后你部署的系统都可以把JVM统计项发送到这些监控系统里去。

此时你就可以在这些监控系统可视化的界面里,看到你需要的所有指标,包括你的各个内存区域的对象占用变化曲 线,直接可以看到Eden区的对象增速,还会告诉你Young GC发生的频率以及耗时,包括老年代的对象增速以及Full GC的频率和耗时。

而且这些工具还允许你设置监控。也就是说,你可以指定一个监控规则,比如线上系统的JVM,如果10分钟之内发生5 次以上Full GC,就需要发送报警给你。比如发送到你的邮箱、短信里,这样你就不用自己每天去看着了。

但是这些监控工具的使用不在我们专栏范畴里,因为这些内容并不一定每个公司都一样,也不一定每个公司都有。比如“OpenFalcon监控JVM”,会看到很多资料。

简单一句话总结:对线上运行的系统,要不然用命令行工具手动监控,发现问题就优化,要不然就是依托公司的监控 系统进行自动监控,可视化查看日常系统的运行状态。

3、案例分析:每秒10万并发的BI系统,如何定位和解决频繁Young GC问题?

4、案例分析:案例实战:每日百亿数据量的实时分析引擎,如何定位和解决频繁Full GC问题?

案例分析…

如果觉得《【JVM 学习笔记 05】:JVM性能调优工具的使用和优化案例》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。