JVM常用命令(九)
前面东西说完后,现在可以说一些和我们平时进行性能调优相关的东西了,那怎么看和我们JVM性能调优相关的东西呢,其实这对我们开发来说是一个比较头痛的问题,其实我们JDK官网给了一些我们相关的指令,我们可以用这些命令去排查当前JAVA中当前有多少个进程、可以知道我们内存空间中他是一个什么样的结构、哪些对象占用的比较大、哪些对象占用的比较小、还有查看我们的类加载信息、查看GC信息等。
官网:https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/index.html
一、jps
我们不管查什么信息,首先第一个针对的对象肯定是java的进程而言的,所以第一个要说的命令就是查看当前系统的java的进程。在演示之前先随便打开一个自己的java项目,这个只要是开发人员都会有,我就不上传DEMO了。启动项目后然后打开本地的命令行。
输入命令jps后可以看到我启动的项目java进程和进程号PID
接下来所有的操作都可以以这个进程号进行开展
二、jinfo
(1)实时查看和调整JVM配置参数
The jinfo command prints Java configuration information for a specified Java process or core file or a remote debug server.
The configuration information includes Java system properties and Java Virtual Machine (JVM) command-line flags.
(2)查看用法
jinfo -flflag name PID 查看某个java进程的name属性的值
jinfo -flag MaxHeapSize PID
jinfo -flag UseG1GC PID #查看是否用到了G1,有'-'号表示没有用到
(3)修改
参数只有被标记为manageable的flflags可以被实时修改
jinfo -flag [+|-] PID
jinfo -flag <name>=<value> PID
(4)查看曾经赋过值的一些参数
jinfo -flags PID #查看全部
三、jstat
查看进程的类的信息和GC的信息
(1)查看虚拟机性能统计信息
The jstat command displays performance statistics for an instrumented Java HotSpot VM.
The target JVM is identified by its virtual machine identifier, or vmid option.
(2)查看类装载信息
jstat -class PID 1000 10 查看某个java进程的类装载信息,每1000毫秒输出一次,共输出10次
可以通过上面可以看到有多少个类被装载进来了,也可以看到有多少个类被卸载了
(3)查看垃圾收集信息
jstat -gc PID 1000 10
可以看到s0/s1什么一堆信息
四、jstack
前面我们看到了进程、类和gc的信息了,接下来要看的就是要看线程的信息了。
(1)查看线程堆栈信息
The jstack command prints Java stack traces of Java threads for a specified Java process, core file, or remote debug server.
(2)用法
jstack PID
这里面有很多线程,有业务线程和GC线程,查到这些有什么用呢,其实这个其实用处很多,比喻排查死锁情况
(4)排查死锁案例
public class DeadLockDemo { public static void main(String[] args) { DeadLock d1=new DeadLock(true); DeadLock d2=new DeadLock(false); Thread t1=new Thread(d1); Thread t2=new Thread(d2); t1.start(); t2.start(); } } //定义锁对象 class MyLock{ public static Object obj1=new Object(); public static Object obj2=new Object(); } //死锁代码 class DeadLock implements Runnable{ private boolean flag; DeadLock(boolean flag){ this.flag=flag; } public void run() { if(flag) { while(true) { synchronized(MyLock.obj1) { System.out.println(Thread.currentThread().getName()+"----if 获得obj1锁"); synchronized(MyLock.obj2) { System.out.println(Thread.currentThread().getName()+"--- -if获得obj2锁"); } } } }else { while(true){ synchronized(MyLock.obj2) { System.out.println(Thread.currentThread().getName()+"----否则 获得obj2锁"); synchronized(MyLock.obj1) { System.out.println(Thread.currentThread().getName()+"--- -否则获得obj1锁"); } } } } } }
jsack分析
把打印信息拉到最后可以发现
会发现两个线程发生了死锁
五、jmap
上面这些内容看完后其实还有一个很重要的东西没看,那就是运行时数据区堆的运行情况
(1)生成堆转储快照
The jmap command prints shared object memory maps or heap memory details of a specified process, core file, or remote debug server.
(2)打印出堆内存相关信息
jmap -heap PID
(3)dump出堆内存相关信息
jmap -dump:format=b,file=heap.hprof PID
(4)要是在发生堆内存溢出的时候,能自动dump出该文件就好了
一般在开发中,JVM参数可以加上下面两句,这样内存溢出时,会自动dump出该文件
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof
(5)关于dump下来的文件可以结合工具来分析,这块后面再说。
六、执行引擎
Person.java源码文件是Java这门高级开发语言,对程序员友好,方便我们开发。javac编译器将Person.java源码文件编译成class文件[我们把这里的编译称为前期编译],交给JVM运行,因为JVM只能认识class字节码文件。同时在不同的操作系统上安装对应版本的JDK,里面包含了各自屏蔽操作系统底层细节的JVM,这样同一份class文件就能运行在不同的操作系统平台之上,得益于JVM。这也是Write Once,Run Anywhere的原因所在。
最终JVM需要把字节码指令转换为机器码,可以理解为是0101这样的机器语言,这样才能运行在不同的机器上,那么由字节码转变为机器码是谁来做的呢?说白了就是谁来执行这些字节码指令的呢?这就是执行引擎。
6.1、解释执行
Interpreter,解释器逐条把字节码翻译成机器码并执行,跨平台的保证。刚开始执行引擎只采用了解释执行的,但是后来发现某些方法或者代码块被调用执行的特别频繁时,就会把这些代码认定为“热点代码”。
6.2 即时编译器
Just-In-Time compilation(JIT),即时编译器先将字节码编译成对应平台的可执行文件,运行速度快。即时编译器会把这些热点代码编译成与本地平台关联的机器码,并且进行各层次的优化,保存到内存中。
6.3 JVM采用哪种方式
JVM采取的是混合模式,也就是解释+编译的方式,对于大部分不常用的代码,不需要浪费时间将其编译成机器码,只需要用到的时候再以解释的方式运行;对于小部分的热点代码,可以采取编译的方式,追求更高的运行效率。
6.4 即使编译器类型
(1)HotSpot虚拟机里面内置了两个JIT:C1和C2
- C1也称为Client Compiler,适用于执行时间短或者对启动性能有要求的程序
- C2也称为Server Compiler,适用于执行时间长或者对峰值性能有要求的程序
(2)Java7开始,HotSpot会使用分层编译的方式
也就是会结合C1的启动性能优势和C2的峰值性能优势,热点方法会先被C1编译,然后热点方法中的热点会被C2再次编译
6.5 AOT和Graal VM
6.5.1 AOT
在Java9中,引入了AOT(Ahead-Of-Time)编译器,即时编译器是在程序运行过程中,将字节码翻译成机器码。而AOT是在程序运行之前,将字节码转换为机器码
优势 :这样不需要在运行过程中消耗计算机资源来进行即时编译
劣势 :AOT 编译无法得知程序运行时的信息,因此也无法进行基于类层次分析的完全虚方法内联,或者基于程序 profifile 的投机性优化(并非硬性限制,我们可以通过限制运行范围,或者利用上一次运行的程序 profifile 来绕开这两个限制)
6.5.2 Graal VM
在Java10中,新的JIT编译器Graal被引入它是一个以Java为主要编程语言,面向字节码的编译器。跟C++实现的C1和C2相比,模块化更加明显,也更加容易维护。Graal既可以作为动态编译器,在运行时编译热点方法;也可以作为静态编译器,实现AOT编译。除此之外,它还移除了编程语言之间的边界,并且支持通过即时编译技术,将混杂了不同的编程语言的,代码编译到同一段二进制码之中,从而实现不同语言之间的无缝切换。
这短短的一生我们最终都会失去,不妨大胆一点,爱一个人,攀一座山,追一个梦