大纲
1.使用jstat了解线上系统的JVM运行状况
2.使用jmap和jhat了解线上系统的对象分布
3.如何分析JVM运行状况并合理优化
4.使用jstat分析模拟的BI系统JVM运行情况
5.使用jstat分析模拟的计算系统JVM运行情况
6.问题汇总
1.使用jstat了解线上系统的JVM运行状况
(1)JVM的整体运行原理简单总结
(2)功能强大的jstat
(3)jstat -gc PID
(4)其他的jstat命令
(5)到底该如何使用jstat工具
(6)新生代对象增长的速率
(7)Young GC的触发频率和每次耗时
(8)每次Young GC后有多少对象进入老年代
(9)Full GC的触发时机和耗时
(1)JVM的整体运行原理简单总结
一.对象优先在Eden区分配
二.Young GC的触发时机和执行过程
三.对象进入老年代的时机
四.Full GC的触发时机和执行过程
接下来介绍如何使用工具分析运行的系统:
一.对象增长的速率
二.Young GC的触发频率
三.Young GC的耗时
四.每次Young GC后有多少对象存活下来
五.每次Young GC后有多少对象进入老年代
六.老年代对象增长的速率
七.Full GC的触发频率
八.Full GC的耗时
(2)功能强大的jstat
如果平时要对运行中的系统,检查其JVM的整体运行情况。比较实用的工具之一就是jstat,它可以轻易让我们看到当前JVM内:Eden区、S区、老年代的内存情况,以及YGC和FGC的执行次数和耗时。
通过这些指标,我们就可以轻松分析出当前系统的运行情况。从而判断当前系统的内存使用压力和GC压力,以及内存分配是否合理。
(3)jstat -gc PID
首先使用jps命令在生产机器Linux上,找出Java进程的PID。接着就针对我们的Java进程执行:jstat -gc PID,这样就可以看到这个Java进程的内存和GC情况了。运行这个命令后会看到如下指标的信息:
$ jstat -gc 1170 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT S0C:这是From Survivor区的大小,C代表的是Capacity S1C:这是To Survivor区的大小,C代表的是Capacity S0U:这是From Survivor区当前使用的内存大小,U代表的是Used S1U:这是To Survivor区当前使用的内存大小,U代表的是Used EC:这是Eden区的大小,E代表的是Eden,C代表的是Capacity EU:这是Eden区当前使用的内存大小,E代表的是Eden,U代表的是Used OC:这是老年代的大小,O代表的是Old,C代表的是Capacity OU:这是老年代当前使用的内存大小,O代表的是Old,U代表的是Used MC:这是方法区(永久代、元数据区)的大小,M代表的是Metaspace,C代表的是Capacity MU:这是方法区(永久代、元数据区)的当前使用的内存大小,M代表的是Metaspace,U代表的是Used YGC:这是系统运行迄今为止的Young GC次数 YGCT:这是Young GC的耗时,T代表的是Time FGC:这是系统运行迄今为止的Full GC次数 FGCT:这是Full GC的耗时,T代表的是Time GCT:这是所有GC的总耗时,T代表的是Time
这些指标都是非常实用的JVM GC分析指标。
(4)其他的jstat命令
除了上面的jstat -gc命令是最常用的以外,jstat还有一些命令可以看到更多详细的信息,如下所示:
jstat -gccapacity PID:堆内存分析 jstat -gcnew PID:年轻代GC分析,这里的TT和MTT可以看到对象在年轻代存活的年龄和存活的最大年龄 jstat -gcnewcapacity PID:年轻代内存分析 jstat -gcold PID:老年代GC分析 jstat -gcoldcapacity PID:老年代内存分析 jstat -gcmetacapacity PID:元数据区内存分析
但最完整、最常用、最实用的还是jstat -gc命令,jstat -gc命令基本足够分析JVM的运行情况了。
(5)到底该如何使用jstat工具
首先需要明确,分析线上的JVM进程,最想要知道的信息有哪些?最想要知道的信息会包括如下:
一.新生代对象增长的速率
二.Young GC的触发频率
三.Young GC的耗时
四.每次Young GC后有多少对象存活下来
五.每次Young GC后有多少对象进入老年代
六.老年代对象增长的速率
七.Full GC的触发频率
八.Full GC的耗时
只要知道了这些信息,我们就可以结合前面介绍的JVM GC优化的方法:合理分配内存,尽量让对象留在年轻代不进入老年代,避免频繁FGC。这就是对JVM最好的性能优化了。
(6)新生代对象增长的速率
需要了解JVM的第一个信息就是:随着系统运行,每秒会在新生代的Eden区分配多少对象。
要获取该信息,只需要在Linux机器上运行命令:jstat -gc PID 1000 10,该命令意思:每隔1秒更新最新jstat统计信息,一共执行10次jstat统计。通过这行命令,可以灵活地对线上机器,通过固定频率输出统计信息。从而观察出每隔一段时间,JVM中Eden区的对象占用变化。
比如执行这行命令后:第一秒显示出Eden区使用了200M内存,第二秒显示出Eden区使用了205M内存,第三秒显示出Eden区使用了209M内存,以此类推。此时我们就可以推断出来,这个系统大概每秒钟会新增5M左右的对象。
而且这里可以根据自己系统的情况灵活多变地使用,如果系统负载很低,则不一定每秒进行统计,可以每分或每10分来统计,以此查看系统每隔1分钟或者10分钟大概增长多少对象。
此外,系统一般有高峰和日常两种状态,比如系统高峰期用户很多,我们应该在系统高峰期去用上述命令看看高峰期的对象增长速率,然后再在非高峰的日常时间段内看看对象的增长速率,这样就可以了解清楚系统的高峰和日常时间段内的对象增长速率了。
(7)Young GC的触发频率和每次耗时
接着需要了解JVM的第二个信息是:大概多久会触发一次YGC,以及每次YGC的耗时。其实多久触发一次YGC是很容易推测出来的,因为系统高峰和日常时的对象增长速率都知道了,根据对象增长速率和Eden区大小,就可以推测出:高峰期多久发生一次YGC,日常期多久发生一次YGC。
比如Eden区有800M内存:如果发现高峰期每秒新增5M对象,那么大概3分钟会触发一次YGC。如果发现日常期每秒新增0.5M对象,那么大概半小时才触发一次YGC。
那么每次Young GC的平均耗时呢?jstat会展示迄今为止系统已经发生了多少次YGC以及这些YGC的总耗时。比如系统运行24小时后共发生260次YGC,总耗时为20s。那么平均每次YGC大概耗时几十毫秒,我们由此可以大概知道每次YGC时会导致系统停顿几十毫秒。
(8)每次Young GC后有多少对象进入老年代
接着要了解JVM的第三个信息是:每次YGC后有多少存活对象,即有多少对象会进入老年代。
其实每次YGC过后有多少对象会存活下来,只能大致推测出来。假设已经推算出高峰期多久会发生一次YGC,比如3分钟会有一次YGC。那么此时就可以执行下述jstat命令:jstat -gc PID 180000 10。这就相当于让JVM每隔三分钟执行一次统计,连续执行10次。观察每隔三分钟发生一次YGC时,Eden、Survivor、老年代的对象变化。
正常来说:Eden区肯定会在几乎放满后又变得很少对象,比如800M只使用几十M。Survivor区肯定会放入一些存活对象,老年代可能会增长一些对象占用。所以这时观察的关键,就是观察老年代的对象增长速率。
正常来说:老年代不太可能不停快速增长的,因为普通系统没那么多长期存活对象。如果每次YGC后,老年代对象都要增长几十M,则可能存活对象太多了。存活对象太多可能会导致放入S区后触发动态年龄判定规则进入老年代,存活对象太多也可能导致S区放不下,大部分存活对象需要进入老年代。
如果老年代每次在YGC过后就新增几百K或几M的对象,这个还算正常。但如果老年代对象快速过快增长,那一定是不正常的。
所以通过上述观察策略,就可以知道每次YGC后有多少对象是存活的,也就是Survivor区里增长的 + 老年代增长的对象,就是存活的对象。
通过jstat -gc也可以知道老年代对象的增长速率,比如每隔3分钟一次YGC,每次会有50M对象进入老年代,于是老年代对象的增长速率就是每隔3分钟增长50M。
(9)Full GC的触发时机和耗时
只要知道老年代对象的增长速率,那么Full GC的触发时机就很清晰了。比如老年代有800M,每3分钟新增50M,则每1小时就会触发一次FGC。
根据jstat输出的系统运行迄今为止的FGC次数以及总耗时,就能计算出每次FGC耗时。比如一共执行了10次FGC,共耗时30s,那么每次FGC大概耗费3s左右。
2.使用jmap和jhat了解线上系统的对象分布
(1)jstat总结
(2)使用jmap了解系统运行时的内存区域
(3)使用jmap了解系统运行时的对象分布
(4)使用jmap生成堆内存转储快照
(5)使用jhat在浏览器中分析堆转出快照
(1)jstat总结
通过jstat可以非常轻松便捷的了解到线上系统的运行状况,如新生代对象和老年代对象增速、YGC和FGC触发频率以及耗时等,通过jstat可以完全了解线上系统的JVM运行情况,为优化做准备。
接下来介绍两个在工作中非常实用的工具:jmap和jhat。这两个工具可以观察线上JVM中的对象分布,能更加细致了解系统运行。即了解系统运行过程中,哪些对象占据了大部分,占据了多少内存空间。
(2)使用jmap了解系统运行时的内存区域
其实如果只是了解JVM运行状况,然后进行GC优化,通常jstat就够用了。但有时会发现JVM新增对象速度很快,想知道什么对象占那么多内存。从而可以优化对象在代码中的创建时机,避免对象占用内存过大。
首先看一个命令:jmap -heap PID。这个命令可以打印出一系列信息,这些信息大概就是:堆内存相关的一些参数设置、当前堆内存里各个区域的情况。比如:Eden区的总容量、已经使用的容量、剩余的空间容量,两个Survivor区的总容量、已经使用的容量、剩余的空间容量,老年代的总容量、已经使用的容量、剩余的容量。
但是这些信息其实jstat已经有了,所以一般不会用jmap去获取这些信息。毕竟jmap的这种信息还没jstat全面,比如jmap就没有GC相关的统计。
(3)使用jmap了解系统运行时的对象分布
jmap中比较有用的一个命令是:jmap -histo PID。这个命令会打印出类似下面的信息:按各对象占用内存空间大小降序排列,把占用内存最多的对象放在最上面。
num #instances #bytes class name ---------------------------------------------------- 1: 46608 1111232 java.lang.String 2: 6919 734516 java.lang.Class 3: 4787 536164 java.net.SocksSocketImpl 4: 15935 497100 java.util.concurrent.ConcurrentHashMap$Node 5: 28561 436016 java.lang.Object
所以想简单了解当前JVM中的对象内存占用情况,可用jmap -histo命令。该命令可快速了解当前内存里到底哪个对象占用了大量的内存空间。
(4)使用jmap生成堆内存转储快照
如果想查看对象占用内存的具体情况,那么可以使用jmap命令生成一个堆内存转储快照到文件里:
jmap -dump:live,format=b,file=dump.hprof PID
这个命令会在当前目录下生成一个dump.hrpof文件,该文件是二进制的格式,不能直接打开看,这个命令会把这一时刻JVM堆内存里所有对象的快照放到文件里。
(5)使用jhat在浏览器中分析堆转出快照
可以使用jhat去分析堆内存快照,jhat内置了web服务器,支持通过web界面来分析堆内存转储快照。
使用如下命令即可启动jhat服务器,可以指定HTTP的端口,默认的HTTP端口为7000。
jhat -port 7000 dump.hprof
接着就可以在浏览器上访问当前这台机器的7000端口号,这样就可以通过图形化的方式去分析堆内存里的对象分布情况了。
3.如何分析JVM运行状况并合理优化
(1)开发好系统后的预估性优化
(2)系统压测时的JVM优化
(3)对线上系统进行JVM监控
(1)开发好系统后的预估性优化
什么叫预估性优化?就是估算系统每秒多少请求、每个请求创建多少对象、占用多少内存。机器应选用什么配置、新生代应给多少内存、老年代应给多少内存。Young GC触发的频率、对象进入老年代的速率、Full GC触发的频率。
这些信息其实都是可以根据系统代码,大致合理地进行预估的。在预估完成后,就可以采用前面的优化思路,先设置初始的JVM参数。比如堆内存大小、新生代大小、Eden和Survivor的比例、老年代大小、大对象的阈值、大龄对象进入老年代的阈值等。
优化思路就是:尽量让每次YGC后的存活对象小于S区的50%,可以都留在新生代里。尽量别让对象进入老年代,减少FGC的频率、避免频繁FGC影响性能。
(2)系统压测时的JVM优化
通常一个新系统开发完毕后,会经过一连串的测试,本地单元测试->系统集成测试->测试环境功能测试->预发环境压力测试。总之要保证系统的功能正常,在一定压力下稳定性和并发能力也都正常,最后才会部署到生产环境运行。
这里非常关键的一个环节就是预发布环境的压力测试,通常该环节会使用一些压力测试工具模拟如1000个用户同时访问系统。模拟每秒500个请求压力,然后看系统能否支撑住每秒500请求的压力。同时看系统各接口响应延时是否在比如200ms内,即接口性能不能太慢。或者在数据库中模拟出百万级单表数据,然后看系统是否还能稳定运行。
具体如何进行系统压测,可以搜Java压力测试,会有很多开源工具。通过这些工具可以轻松模拟出N个用户同时访问你系统的场景,同时还能生成压力测试报告:每秒可支撑多少请求、接口的响应延时等。
在该环节,压测工具会对系统发起持续不断的请求。这些请求通常会持续很长时间,如几小时甚至几天。所以可以在该环节,对测试机器运行的系统,采用jstat工具来进行分析。在模拟真实环境的压力测试下,通过jstat命令获取JVM的整体运行状态。
前面已具体介绍了如何使用jstat来分析以下JVM的关键运行指标:新生代对象增长的速率、YGC的触发频率、YGC的耗时、每次YGC后有多少对象存活下来、每次YGC后有多少对象进入老年代、老年代对象增长的速率、FGC的触发频率、FGC的耗时。
然后根据压测环境中的JVM运行状况:如果发现对象过快进入老年代,可能是因为年轻代太小导致频繁YGC;也可能因为很多对象存活,而S区太小,导致很多对象频繁进入老年代;此时就需要采用前面介绍的优化思路:合理调整新生代、老年代、Eden、Survivor各个区域的内存大小,保证对象尽量留在年轻代、不要过快进入老年代中。
有很多人会胡乱搜索网上JVM优化博客,看人家怎么优化就怎么优化。比如很多博客说新生代和老年代的占比一般是3:8,这其实是很片面的。每个系统都是不一样,特点不同、复杂度不同。真正的优化,一定是在实际观察我们的系统后,合理调整内存分布。真正的优化,并没有固定的JVM优化模板。当优化好压测环境的JVM参数,观察YGC和FGC频率都很低,就可上线。
(3)对线上系统进行JVM监控
当系统上线后,就需要对线上系统的JVM进行监控,这个监控通常来说有两种办法。
第一种方法:
每天在高峰期和低峰期用jstat、jmap、jhat看线上JVM运行是否正常。有没有频繁FGC问题,如果有就优化,没有就每天定时或每周定时去看。
第二种方法:
部署专门的监控系统,常见的有Zabbix、OpenFalcon、Ganglia等。可以将线上系统的JVM统计项发送到这些监控系统里去,这样就可以在这些监控系统可视化界面里,看到需要的所有指标:包括各内存区域的对象占用变化曲线、可直接看到Eden区的对象增速、YGC发生的频率以及耗时、老年代的对象增速、FGC的频率和耗时。而且这些工具通常还允许设置监控告警,比如10分钟之内发生5次以上FGC,就需要发送告警给我们。
4.使用jstat分析模拟的BI系统JVM运行情况
(1)服务于百万级商家的BI系统是什么
(2)刚开始上线BI系统时的部署架构
(3)实时自动刷新报表 + 大数据量报表
(4)没什么大影响的频繁Young GC
(5)模拟程序的JVM参数设置
(6)模拟程序
(7)通过jstat观察程序的运行状态
(1)服务于百万级商家的BI系统是什么
作为一个电商平台,可能会有数十万到百万的商家在平台上做生意。电商平台每天会产生大量数据,需要基于这些数据为商家提供数据报表。比如:每个商家每天有多少访客、有多少交易、付费转化率是多少。
BI系统其实就是把商家日常经营的数据收集起来进行分析,然后提供各种数据报表给商家的一套系统。
这样的一个BI系统,其运行逻辑如下:首先电商平台会提供一个业务平台给商家进行日常使用交互,该业务平台会采集到商家的很多日常经营数据。根据这些日常经营数据,通过Hadoop、Spark等技术计算各种数据报表,这些数据报表会被放入存储到MySQL、Elastcisearch、HBase中。最后基于MySQL、HBase、ES中存储的数据报表,开发出一个BI系统。通过这个BI系统就能把各种存储好的数据展示给商家进行筛选和分析。
