概要描述
本案例简单介绍几个JVM内存分析相关的命令,以及案例解析;
本案例环境:TDH 5.2.2 inceptor-server 的内存分析
详细说明
JVM内存模型比较复杂,本文只简单介绍一下堆内存的分析;使用到的命令包括以下:
- jps
- jstat
- jmap
- jstack
- jinfo
内存模型
JVM 内存模型主要有三大块:堆内存、方法区和栈。
- 堆内存是 JVM中 最大的一块,主要是用来为对象实例分配内存;由年轻代和老年代组成,年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配;
- 方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆),方法区大小一般比较固定,jstat 时使用率高是正常现象;
- 栈又分为java虚拟机栈和本地方法栈,主要用于方法的执行;
jps
jps 作用是显示当前系统的 java 进程情况,及其 id 号
jps [-q] [-mlvV] [hostid]
例如:查看当前系统下,当前用户运行的java进程( -v可以输出当前java进程的启动参数)
# jps
428 InceptorServer2
4003 Jps
# jps -v
428 InceptorServer2 -agentpath:/usr/lib/inceptor/bin/libagent.so -XX:PermSize=512m -XX:MaxPermSize=2g -Djava.net.preferIPv4Stack=true -Dsun.net.inetaddr.ttl=60 -XX:+UseParNewGC -XX:NewRatio=4 -XX:+CMSClassUnloadingEnabled -XX:MinHeapFreeRatio=100 -XX:MaxHeapFreeRatio=100 -XX:CMSMaxAbortablePrecleanTime=1000 -XX:+ExplicitGCInvokesConcurrent -XX:MaxTenuringThreshold=4 -XX:TargetSurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -Xms2048m -Xmx4096m -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Djava.library.path=/usr/lib/hadoop/lib/native -Dspark.akka.threads=8 -Dspark.akka.threads=8 -Dspark.rdd.compress=false -Dspark.storage.memoryFraction=0.5 -Dspark.driver.host=tdh-01 -Dclass.default.serializer= -Dspark.fastdisk.dir=/vdir/mnt/ramdisk/ngmr -Dspark.storage.fastdiskFraction=0.5 -Dngmr.task.pipeline=false -Dngmr.task.pipeline.start.fraction=0.5 -Dngmr.task.pipeline.task.timeout.ms=-1 -Dspark.local.dir=/vdir/mnt/disk1/hadoop/ngmr/inceptor1 -Ds
3985 Jps -Dapplication.home=/usr/java/jdk1.7.0_71 -Xms8m
jstat
jstat (JVM statistics Monitoring) 是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集等运行数据。
jstat -option [-t] [-h lines] vmid [interval [count]]
例如:查看vmid为428的进程的GC统计概述,每隔1000ms统计一次,共输出5次( -gc打印的内容更详细,但是可读性不如 -gcutil)
$ jstat -gcutil 428 1000 5
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 11.42 69.45 24.25 7.36 489 20.144 2 0.165 20.308
0.00 11.42 69.46 24.25 7.36 489 20.144 2 0.165 20.308
0.00 11.42 69.46 24.25 7.36 489 20.144 2 0.165 20.308
0.00 11.42 69.46 24.25 7.36 489 20.144 2 0.165 20.308
0.00 11.42 69.46 24.25 7.36 489 20.144 2 0.165 20.308
显示列名 | 具体描述 |
---|---|
S0 | 年轻代中第一个survivor(幸存区)已使用的占当前容量百分比 |
S1 | 年轻代中第二个survivor(幸存区)已使用的占当前容量百分比 |
E | 年轻代中Eden(伊甸园)已使用的占当前容量百分比 |
O | 老年代已使用的占当前容量百分比 |
P | 永久代已使用的占当前容量百分比 |
YGC | 从应用程序启动到采样时年轻代中gc次数 |
YGCT | 从应用程序启动到采样时年轻代中gc所用时间(s) |
FGC | 从应用程序启动到采样时老年代(Fullgc)gc次数 |
FGCT | 从应用程序启动到采样时老年代(Fullgc)gc所用时间(s) |
GCT | 从应用程序启动到采样时gc用的总时间(s) |
jmap
jmap 可以生成 java 程序的 dump 文件,也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列;
一般在运行缓慢、性能下降时收集;
jmap [option] pid
例如:查看pid为428的进程的堆的活对象统计,包括对象数、内存大小( :live 则只统计这个jmap 触发的 fgc 之后依然保留下来的对象,因此强烈建议带上live)
$ sudo -u hive jmap -histo:live 428
num #instances #bytes class name
----------------------------------------------
1: 7516794 240537408 java.util.Hashtable$Entry
2: 726798 236990472 [B
3: 4998671 159957472 java.util.concurrent.ConcurrentHashMap$HashEntry
4: 1644249 140301752 [C
5: 13815 52684464 [Ljava.util.Hashtable$Entry;
6: 166425 51313208 [S
7: 131544 45079712 [Ljava.util.concurrent.ConcurrentHashMap$HashEntry;
8: 83768 43484088 [I
9: 1627768 40297248 [Ljava.lang.String;
10: 259394 37995216
... ... ... ...
9998: 1 16 akka.actor.ActorPathExtractor$
Total 23593415 1375506264
jmap 主要看哪些对象占用了大量的内存空间,占用的是否合理等信息,需要同时关注数量(instances),大小(bytes);
jmap 信息的最后的一行 Total 显示了对象的数量以及他们使用的内存信息;
jstack
jstack 是用于生成 java 虚拟机当前时刻的线程快照。
线程快照是当前 JVM 内每一个线程正在执行的方法堆栈的集合,一般在服务卡住的时候收集(如线程间死锁、死循环、请求外部资源导致的长时间等待等); 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
jstack [-l] pid
例如:查看pid为428进程对应的线程快照、各线程的运行情况等信息
# sudo -u hive jstack 428
2019-10-09 19:00:28
Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.71-b01 mixed mode):
"ForkJoinPool-2-worker-13" daemon prio=10 tid=0x00007f58c41a1800 nid=0xdb2 waiting on condition [0x00007f585befd000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c30838a0> (a scala.concurrent.forkjoin.ForkJoinPool)
at scala.concurrent.forkjoin.ForkJoinPool.scan(ForkJoinPool.java:2075)
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
"ForkJoinPool-2-worker-15" daemon prio=10 tid=0x00007f5910005800 nid=0xdb1 waiting on condition [0x00007f585acea000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c30838a0> (a scala.concurrent.forkjoin.ForkJoinPool)
at scala.concurrent.forkjoin.ForkJoinPool.idleAwaitWork(ForkJoinPool.java:2135)
at scala.concurrent.forkjoin.ForkJoinPool.scan(ForkJoinPool.java:2067)
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
使用jstack命令查看线程堆栈信息时可能会遇到以下几种线程状态,重点关注相同方法的数量和异常状态的方法;
线程状态 | 具体描述 |
---|---|
NEW | 未启动的,不会出现在Dump中 |
RUNNABLE | 在虚拟机内执行的 |
BLOCKED | 受阻塞并等待监视器锁 |
WATING | 无限期等待另一个线程执行特定操作 |
TIMED_WATING | 有时限的等待另一个线程的特定操作 |
TERMINATED | 已退出的 |
jstack 主要看线程之间资源抢占情况,是分析线程执行快慢的一种方法;
jinfo
jinfo(JVM Configuration info) 是实时查看和调整虚拟机运行参数的命令。和 jps -v不同的地方是,jinfo 可以显示未指定的参数的值,也就是默认运行参数的值;
命令格式
jinfo [option] pid
例如:查看pid为428的进程的运行参数(如果不带任何参数,可以显示该进程对应的所有JVM信息)
$ jinfo -flags 428 |more
Attaching to process ID 428, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.71-b01
-agentpath:/usr/lib/inceptor/bin/libagent.so -XX:PermSize=512m -XX:MaxPermSize=2g -Djava.net.preferIPv4Stack=true -Dsun.net.inetaddr.ttl=60 -XX:+UseParNewGC -XX:NewRatio=4 -XX:+CMSClassUnloadingEnabled -XX:MinHeapFreeRatio=100 -XX:MaxHeapFreeRatio=100 -XX:CMSMaxAbortablePrecleanTime=1000 -XX:+ExplicitGCInvokesConcurrent -XX:MaxTenuringThreshold=4 -XX:TargetSurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -Xms2048m -Xmx4096m -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Djava.library.path=/usr/lib/hadoop/lib/native -Dspark.akka.threads=8 -Dspark.akka.threads=8 -Dspark.rdd.compress=false -Dspark.storage.memoryFraction=0.5 -Dspark.driver.host=tdh-01 -Dclass.default.serializer= -Dspark.fastdisk.dir=/vdir/mnt/ramdisk/ngmr -Dspark.storage.fastdiskFraction=0.5 -Dngmr.task.pipeline=false -Dngmr.task.pipeline.start.fraction=0.5 -Dngmr.task.pipeline.task.timeout.ms=-1 -Dspark.local.dir=/vdir/mnt/disk1/hadoop/ngmr/inceptor1 -Dspark.driver.port=51888 -Dspark.driver.portfixed -Dinceptor.executorID.zkPath=/inceptor1/executorID -Dinceptor.executorID.zkServer=tdh-01,tdh-02,tdh-03 -Dinceptor.executorID.zkPort=2181 -Dinceptor.executorID.zkTimeout=10000 -Dspark.kryoserializer.buffer.mb=4 -Dshark.checkpoint.dir= -Dspark.ui.port=4040 -Dspark.cleaner.ttl=14400 Djava.security.auth.login.config=/etc/inceptor1/conf/kafka_client_jaas.conf -Dhive.log.dir=/var/log/inceptor1 -Dhive.log.file=hive-server2.log
jinfo在不加任何参数的情况下,输出内容比较详细,一般用来分析脏包;也可以用来检查启动参数是否正常;