基于JDK命令行工具的监控
一、JVM的参数类型
JVM的参数类型主要分成三类
- 标准参数
- X参数
- XX参数
标准参数,在JVM的各个版本中基本不变的(尽可能保持兼容),是相对比较稳定的参数。
比方说,大家在第一次安装Java后,都会敲的命令行
java -version
里面会显示JVM的一些基本信息,比如版本号,和编译方式,这里的minxed mode表示混合编译,还有Server VM 表示JVM的运行模式是Server模式。
除了-version还有-help
java -help
它可以列举一些Java的标准参数,并标有解释。
下面还有很多,这里仅仅列举一部分。
这里说两个参数: -server 和 -client
JVM有两种运行模式Server与Client。两种模式的区别在于,Client模式启动速度较快,Server模式启动较慢;但是启动进入稳定期长期运行之后Server模式的程序运行速度比Client要快很多。这是因为Server模式启动的JVM采用的是重量级的虚拟机,对程序采用了更多的优化;而Client模式启动的JVM采用的是轻量级的虚拟机。
server:默认为堆提供了一个更大的空间和并行的垃圾收集器 并且在运行时可以更大程度的优化代码
client:客户端虚拟机有较小的默认堆内存 可以缩短JVM启动的时间和占用更少的内存 客户端的JVM只有在32位操作系统中才有
值得我们关注的是:
- 从JDK5开始 当应用启动时会检测当前的运行环境是否是服务器 如果是服务器就使用Server JVM
- 在JDK6中 Server JVM要求至少双核CPU和2GB物理内存
- 在32位操作系统上 JDK可以运行Server JVM 但是JRE只能运行Client JVM
这些了解即可,一般不会变动。
X参数
X参数和XX参数都是非标准化参数,在各个JVM版本中可能会变化。
不过X参数相对于XX参数,使用频率低很多,下面看几个常见的。
设置JVM的编译方式:
- -Xint:解释执行
- -Xcomp:第一次使用就编译成本地代码
- -Xmixed:混合模式,JVM自己来决定是否如何编译本地代码
使用演示:
这里再还原成混合模式:
默认的混合模式 是最好的。
XX参数
XX参数主要分为两大类:
Boolean类型
格式:
-XX:[+-] <name> 表示启用或者禁用了name参数 +表示启用
比如:
- -XX:+UseConcMarkSweepGC表示启用了CMS垃圾回收器
- -XX:+UseG1GC 表示启用了G1垃圾回收器
非Boolean(Key-Value类型)
格式:
-XX:<name>=<value> 表示name参数的值是value
比如:
- -XX:MaxGCPauseMills=500表示GC最大的停顿时间是500
- -XX:GCTimeRatio=19
GCTimeRatio参数的值应当是一个大于0且小于100的整数,也就是垃圾收集时间占总时间的比率,相当于是吞吐量的倒数。如果把此参数设置为19,那允许的最大GC时间就占总时间的5%(即1/(1+19)),默认值为99,就是允许最大1%(即1/(1+99))的垃圾收集时间。
-XX参数常见的参数还有一种缩写模式(虽然是-X开头,但是其实是-XX参数)
比如:
- -Xms 等价于-XX:InitialHeapSize 表示初始化的堆大小
- -Xmx 等价于-XX:MaxHeapSize 表示最大的堆的大小
- -Xss 等价于 -XX:ThreadStackSize 线程堆栈的大小
二、JDK的命令行监控工具
查看JVM运行时参数的值
1、-XX:+PrintFlagsFina
1 ) -XX:+PrintFlagsFinal 打印出所有XX参数和值
java -XX: +PrintFlagsFinal
=表示默认值
:= 表示被用户或者JVM修改后的值
2、-XX:+PrintFlagsInitial
java -XX:+PrintFlagsInitial
打印的是XX参数的初始化值
这里列举一小部分:
3、-XX:+PrintCommandLineFlags
这个参数让JVM打印出那些已经被用户或者JVM设置过的详细的XX参数的名称和值。
它列举出 -XX:+PrintFlagsFinal的结果中第三列有”:=”的参数。
以这种方式,我们可以用-XX:+PrintCommandLineFlags作为快捷方式来查看修改过的参数
正常使用
java -XX:+PrintCommandLineFlags
可以查看到垃圾回收器的信息
别人测试的都能看到垃圾回收器的信息,我的JVM并不能看到这个信息,暂时没有找到原因,这里记录一下。下面两张图引自传送门
4、-XX:+UnlockDiagnosticVMOptions解锁诊断参数
5、-XX:+UnlockExperimentalVMOptions解锁实验参数
这两条后面会补上。
(1)jps
在介绍这几款工具之前,有必要说明:JDK内置命令行工具的监控的详细信息都可以在这里找到 传送门。
jps是虚拟机进程状况工具
下面到了JDK的命令行监控工具的使用,首先是jps,使用非常简单。
Java的jps 是专门查看Java进程的
加一个 -l参数,可以看到完整的名称
jps工具主要选项
(2)jinfo
jinfo 是 Java配置信息工具
jinfo(Configuration Info for Java)的作用是实时查看和调整虚拟机各项参数。
使用jps命令的-v参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,除了去找资料外,就只能使用jinfo的-flag选项进行查询了(如果只限于JDK 6或以上版本的话,使用java-XX:+PrintFlagsFinal查看参数默认值也是一个很好的选择)。
jinfo还可以使用-sysprops选项把虚拟机进程的System.getProperties()的内容打印出来。这个命令在JDK5时期已经随着Linux版的JDK发布,当时只提供了信息查询的功能,JDK 6之后,jinfo在Windows和Linux平台都有提供,并且加入了在运行期修改部分参数值的能力(可以使用-flag[+|-]name或者-flag name=value在运行期修改一部分运行期可写的虚拟机参数值)。在JDK 6中,jinfo对于Windows平台功能仍然有较大限制,只提供了最基本的-flag选项。
可以使用 jinfo -flag <参数名称> 命令行工具去查看当前java进程的JVM参数。
比如:
jinfo -flag MaxHeapSize pid
查看线程堆栈的大小 这里显示是1024KB。
查看是否使用了启用相关的配置。比如压缩指针。
+ 表示有
- 表示没有
在堆中,32位的对象引用(指针)占4个字节,而64位的对象引用占8个字节。也就是说,64位的对象引用大小是32位的2倍。64位JVM在支持更大堆的同时,由于对象引用变大却带来了性能问题。为了能够保持32位的性能,oop须保留32位。方法是压缩指针
(3)jstat
jstat是虚拟机统计信息监视工具
确切点来说,jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程[插图]虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据,在没有GUI图形界面、只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的常用工具。
比如类装载信息、垃圾收集信息、以及JIT编译信息等
命令格式如下:
option主要有如下内容:
-class 查看类加载信息
-compiler 查看编译信息
-gc 查看垃圾回收信息
更多的选项,可以从-help查看,或者想要了解更多, JDK内置命令行工具的监控的详细信息都可以在这里找到 传送门
演示
java -class 进程号 间隔毫秒数 输出次数
-class option
Class loader statistics.
Loaded: Number of classes loaded. -- 加载的类的个数
Bytes: Number of kBs loaded. -- 加载的类的大小
Unloaded: Number of classes unloaded -- 卸载的类的个数.
Bytes: Number of Kbytes unloaded. -- 卸载的类的大小
Time: Time spent performing class loading and unloading operations. -- 花在加载和卸载类的时间
垃圾收集的内容还是比较多的。
java -gc 进程号 间隔毫秒数 输出次数
参数比较多哈,但是根据下面的注释 很容易区分出JVM中的每一个分块。
其中S0C、S0U。C表示总容量大小,U表示使用大小,这样就好理解了。
- S0C、S1C、S0U、S1U:S0和S1的总量与使用量
- EC、EU:Eden区总量与使用量
- OC、OU:Old区总量与使用量
- MC、MU: Metaspace区总量与使用量
- CCSC、CCSU:压缩类空间总量与使用量(压缩指针的作用)
- YGC、YGCT:Young Go的次数与时间
- FGC、FGCT:FullGC的次数与时间
- GCT:总的GC时间
-gc option
Garbage-collected heap statistics.
S0C: Current survivor space 0 capacity (kB). --
S1C: Current survivor space 1 capacity (kB).
S0U: Survivor space 0 utilization (kB).
S1U: Survivor space 1 utilization (kB).
EC: Current eden space capacity (kB).
EU: Eden space utilization (kB).
OC: Current old space capacity (kB).
OU: Old space utilization (kB).
MC: Metaspace capacity (kB).
MU: Metacspace utilization (kB).
CCSC: Compressed class space capacity (kB).
CCSU: Compressed class space used (kB).
YGC: Number of young generation garbage collection events.
YGCT: Young generation garbage collection time.
FGC: Number of full GC events.
FGCT: Full garbage collection time.
GCT: Total garbage collection time.
除此之外 还有别的option。具体需要查文档,演示的内容是比较常用的。
下面有必要说一下Java的内存结构:
JVM主要分成两大块,一块是堆区、一块是非堆区。
堆区又分成两大块,一块是Young、一块是Old。
如果young区是复制算法的话(现在的商用Java虚拟机大多都优先采用了这种收集算法去回收新生代)。
Young区会被分成两大块,一块是S0+S1、另外一块是Eden,对象优先在Eden分配。
发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。
HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也即每次新生代中可用内存空间为整个新生代容量的90%(Eden的80%加上一个Survivor的10%),只有一个Survivor空间,即10%的新生代是会被“浪费”的。
非堆区采用的是操作系统的本地内存,Java8中引入了元空间(方法区移入Metaspace),里面有两个重要的内容,一个是如果启用了压缩指针,就会存在CCS。CodeCahe中存放的是JIT的代码信息(Java代码转换成的native代码、JNI代码)。
下面看一下JIT编译的情况
java -compiler 进程号
java -printcompilation 进程号
-compiler option
Java HotSpot VM Just-in-Time compiler statistics.
Compiled: Number of compilation tasks performed. -- 完成多少编译任务(将方法编译成本地代码)
Failed: Number of compilations tasks failed. -- 失败的
Invalid: Number of compilation tasks that were invalidated. -- 错误的、无效的
Time: Time spent performing compilation tasks. -- 时间
FailedType: Compile type of the last failed compilation.
FailedMethod: Class name and method of the last failed compilation.
Compiled: Number of compilation tasks performed by the most recently compiled method.
Size: Number of bytes of byte code of the most recently compiled method.
Type: Compilation type of the most recently compiled method.
Method: Class name and method name identifying the most recently compiled method. Class name uses slash (/) instead of dot (.) as a name space separator. Method name is the method within the specified class. The format for these two fields is consistent with the HotSpot -XX:+PrintCompilation option.
其它选项的说明:
(4)jmap
jmap是Java内存映像工具
jmap(Memory Map for Java)命令可以用于生成堆转储快照(一般称为heapdump或dump文件),还可以查询finalize执行队列、Java堆和方法区的详细信息,如空间使用率、当前用的是哪种收集器等。
和jinfo命令一样,jmap有部分功能在Windows平台下是受限的,除了生成堆转储快照的-dump选项和用于查看每个类的实例、空间占用统计的-histo选项在所有操作系统中都可以使用之外,其余选项都只能在Linux/Solaris中使用。
命令格式
jmap [option] vmid 进程号
option选项的合法值与具体含义如表示。
通过jmap可以定位内存溢出的问题。
我们看一下堆溢出
public class Main {
public static void main(String[] args) {
ArrayList list = new ArrayList();
while (true) {
list.add(new Main());
}
}
}
结果:java.lang.OutOfMemoryError
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOf(Arrays.java:3721)
at java.base/java.util.Arrays.copyOf(Arrays.java:3690)
at java.base/java.util.ArrayList.grow(ArrayList.java:235)
at java.base/java.util.ArrayList.grow(ArrayList.java:242)
at java.base/java.util.ArrayList.add(ArrayList.java:452)
at java.base/java.util.ArrayList.add(ArrayList.java:465)
at com.leetcodePractise.test.Main.main(Main.java:13)
Process finished with exit code 1
再看一下非堆溢出,非堆指的是非堆内存,主要包括元空间 , Jvm Stack(java虚拟机栈), Local Method Statck(本地方法栈)。
public class Main {
public static void main(String[] args) {
new Main().test();
}
public void test() {
test();
}
}
结果:java.lang.StackOverflowError
Exception in thread "main" java.lang.StackOverflowError
at com.leetcodePractise.test.Main.test(Main.java:10)
at com.leetcodePractise.test.Main.test(Main.java:10)
at com.leetcodePractise.test.Main.test(Main.java:10)
at com.leetcodePractise.test.Main.test(Main.java:10)
....
当发生内存溢出的时候,我们希望有内存映像文件的自动导出,来帮助我们去进行分析。这里有两种方式,一种是通过参数的设置
-XX:+HeapDumpOnOutOfMemoryError 意思是当发生内存溢出的时候将Heap dump出来
-XX:HeapDumpPath=./ 意思是导出的文件存放在哪个路径下面
另外一种方式是使用jmap命令手动导出
dump文件直接打开是乱码的,如果我们要分析dump文件,需要借助工具MAT,可以参考这篇文章传送门
下面演示一下其他的option
-heap 显示每个区块占多大的内存。
其他的选项使用类型。
(5)jhat
jhat是虚拟机堆转储快照分析工具
JDK提供jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析jmap生成的堆转储快照。
jhat内置了一个微型的HTTP/Web服务器,生成堆转储快照的分析结果后,可以在浏览器中查看。
不过,在实际工作中,除非手上真的没有别的工具可用,否则多数人是不会直接使用jhat命令来分析堆转储快照文件的,主要原因有两个方面。
- 一般不会在部署应用程序的服务器上直接分析堆转储快照,即使可以这样做,也会尽量将堆转储快照文件复制到其他机器上进行分析,因为分析工作是一个耗时而且极为耗费硬件资源的过程,既然都要在其他机器上进行,就没有必要再受命令行工具的限制了。
- jhat的分析功能相对来说比较简陋,有大量工具都能实现比jhat更强大专业的分析功能。比如上面介绍的MAT。
(6)jstack
jstack是Java堆栈跟踪工具
jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等,都是导致线程长时间停顿的常见原因。线程出现停顿时通过jstack来查看各个线程的调用堆栈,就可以获知没有响应的线程到底在后台做些什么事情,或者等待着什么资源。
jstack命令格式:
jstack [option] 进程号
选项
演示
这是JIT负责编译的线程
线程状态是一项非常重要的信息,记牢了。
想必大家都曾遇到这样的问题:为什么有时中CPU占用会过高,怎么查看与解决?
关于CPU占用过高的原因大致有这几条:
1、大型程序:可能是编写的程序有不合理的地方,也有可能是电脑配置低
2、病毒与木马:病毒、木马造成。比如大量的蠕虫病毒在系统内部迅速复制,造成CPU占用资源率据高不下。
3、磁盘碎片:经常对文档进行复制和删除,会使得硬盘中数据排列非常分散,使计算机在查找的时候速度变慢,从而占用大量的CPU。
关于排查可以这样做:
通过top命令查看是哪个进程导致CPU占用率高 然后shitf+p 倒序排列
如果是Java进程引起的问题,我们就需要看一下Java进程中的详细信息了。
在终端执行 top -H -p ,查询pid进程下的所有线程消耗cpu的情况,然后shift+p倒序找到消耗cpu最高的线程id,如图:
日志文件中线程id是采用16进制的,所以要将查询出来的10进制线程id转换成16进制,数字转换的方式有很多中,在这里通过linux命令printf转换,如下:
使用jstack下载堆栈信息日志文件,sudo -u appadmin jstack 179258 >/usr/local/test2.txt,如下:
因为当前用户是root,而非179258进程所属用户appadmin,因此需要切换用户。然后根据进程id将对应的日志重定向到某个目录下,当然也可以在线查找 sudo -u appadmin jstack <进程id> | grep -a <16进制线程id>.
生成的日志文件位于linux上,可以通过sz下载到windows下进行查找,根据上文转成16进制的线程id进行全局搜索,就能定位到具体的原因,如图:
根据以上步骤能快速找到问题所在,但是这个日志文件的分析是需要经验积累的,只有当出现诸如线程死锁的情况下,会出现很明显的日志,deadLock关键字样,并且能提示出现问题的代码所在行。其他问题如网络带宽,文件io等硬件资源跟不上的原因导致,是需要有一定的经验积累才能发现的!
参考《深入理解Java虚拟机:JM高级特性与最佳实践》