(尊重劳动成果,转载请注明出处:https://blog.csdn.net/qq_25827845/article/details/83758124冷血之心的博客)
目录
jps
显示当前所有java进程pid
jinfo
观察进程运行环境参数
jstack
显示jvm中当前所有线程的运行情况和线程当前状态
jstat
利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控
jmap
用于打印指定Java进程(或核心文件、远程调试服务器)的共享对象内存映射或堆内存细节
总结:
做为一名优秀的Java开发工程师,我们必不可少的会用到以下几个命令:
这些命令都在我们的Java安装目录的/bin/下边,如图所示:
jps可以使用的参数如下:
-q:仅输出VM标识符,不包括class name,jar name,arguments in main method
-m:输出main method的参数
-l:输出完全的包名,应用主类名,jar的完全路径名
-v:输出jvm参数
-V:输出通过flag文件传递到JVM中的参数(.hotspotrc文件或-XX:Flags=所指定的文件
-Joption:传递参数到vm,例如:-J-Xms48m
示例如下:
接下来我们分析一个问题:某个java进程已经启动,用jps却显示不了该进程进程号
现象:
用ps -ef|grep java能看到启动的java进程,但是用jps查看却不存在该进程的id。待会儿解释过之后就能知道在该情况下,jconsole、jvisualvm可能无法监控该进程,其他java自带工具也可能无法使用
分析:
java程序启动后,默认(请注意是默认)会在/tmp/hsperfdata_userName目录下以该进程的id为文件名新建文件,并在该文件中存储jvm运行的相关信息,其中的userName为当前的用户名,/tmp/hsperfdata_userName目录会存放该用户所有已经启动的java进程信息。对于windows机器/tmp用Windows存放临时文件目录代替。
而jps、jconsole、jvisualvm等工具的数据来源就是这个文件(/tmp/hsperfdata_userName/pid)。所以当该文件不存在或是无法读取时就会出现jps无法查看该进程号,jconsole无法监控等问题
原因:
(1)、磁盘读写、目录权限问题
若该用户没有权限写/tmp目录或是磁盘已满,则无法创建/tmp/hsperfdata_userName/pid文件。或该文件已经生成,但用户没有读权限
(2)、临时文件丢失,被删除或是定期清理
对于linux机器,一般都会存在定时任务对临时文件夹进行清理,导致/tmp目录被清空。常用的可能定时删除临时目录的工具为crontab、redhat的tmpwatch、ubuntu的tmpreaper等。这个导致的现象可能会是这样,用jconsole监控进程,发现在某一时段后进程仍然存在,但是却没有监控信息了。
(3)、java进程信息文件存储地址被设置,不在/tmp目录下
上面我们在介绍时说默认会在/tmp/hsperfdata_userName目录保存进程信息,但由于以上1、2所述原因,可能导致该文件无法生成或是丢失,所以java启动时提供了参数(-Djava.io.tmpdir),可以对这个文件的位置进行设置,而jps、jconsole都只会从/tmp目录读取,而无法从设置后的目录读物信息。
其他:
/tmp/hsperfdata_userName/pid文件会在对应java进程退出后被清除。如果java进程非正常退出(如kill -9),那么pid文件会被保留,直到执行一次java命令或是加载了jvm程序的命令(如jps、javac、jstat),会将所有无用的pid文件都清除掉
当我们想要观察某个进程的运行环境参数时输入: jinfo 172634,然而遗憾的是,可能会报错如下:
Attaching to process ID 172634, please wait...
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at sun.tools.jinfo.JInfo.runTool(JInfo.java:79)
at sun.tools.jinfo.JInfo.main(JInfo.java:53)
Caused by: java.lang.RuntimeException: Type "Klass*", referenced in VMStructs::localHotSpotVMStructs in the remote VM, was not present in the remote VMStructs::localHotSpotVMTypes table (should have been caught in the debug build of that VM). Can not continue.
at sun.jvm.hotspot.HotSpotTypeDataBase.lookupOrFail(HotSpotTypeDataBase.java:362)
at sun.jvm.hotspot.HotSpotTypeDataBase.readVMStructs(HotSpotTypeDataBase.java:253)
at sun.jvm.hotspot.HotSpotTypeDataBase.(HotSpotTypeDataBase.java:87)
at sun.jvm.hotspot.bugspot.BugSpotAgent.setupVM(BugSpotAgent.java:568)
at sun.jvm.hotspot.bugspot.BugSpotAgent.go(BugSpotAgent.java:494)
at sun.jvm.hotspot.bugspot.BugSpotAgent.attach(BugSpotAgent.java:332)
at sun.jvm.hotspot.tools.Tool.start(Tool.java:163)
at sun.jvm.hotspot.tools.JInfo.main(JInfo.java:128)
... 6 more
出现这个问题的原因是当前服务器上的默认JDK版本和该进程使用的JDK版本不一致导致的,我们通过命令whereis java 看看java在什么路径下安装的。
[root@c5-ac-mt01 ~]# whereis java
java: /opt/soft/jdk/bin/java
打开该目录,我们果然发现了多个JDK版本:
我们的进程使用了jdk1.8.0_60,所以我们需要使用决定路径来运行jinfo命令:
[root@c5-ac-mt01 bin]# /opt/soft/jdk1.8.0_181/bin/jinfo 172634
Attaching to process ID 172634, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.181-b13
Java System Properties:
java.vendor = Oracle Corporation
sun.java.launcher = SUN_STANDARD
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
sun.nio.ch.bugLevel =
os.name = Linux
sun.boot.class.path = /opt/soft/jdk1.8.0_181/jre/lib/resources.jar:/opt/soft/jdk1.8.0_181/jre/lib/rt.jar:/opt/soft/jdk1.8.0_181/jre/lib/sunrsasign.jar:/opt/soft/jdk1.8.0_181/jre/lib/jsse.jar:/opt/soft/jdk1.8.0_181/jre/lib/jce.jar:/opt/soft/jdk1.8.0_181/jre/lib/charsets.jar:/opt/soft/jdk1.8.0_181/jre/lib/jfr.jar:/opt/soft/jdk1.8.0_181/jre/classes
java.vm.specification.vendor = Oracle Corporation
app.home = /home/work/bin/passport-user-rights
java.runtime.version = 1.8.0_181-b13
app.repo = /home/work/bin/passport-user-rights/lib
user.name = work
app.pid = 172634
user.rights.data.cache.effective.time = 5
user.language = en
sun.boot.library.path = /opt/soft/jdk1.8.0_181/jre/lib/amd64
India.proxy.env = AZMBCLOUDSRV
java.version = 1.8.0_181
user.timezone = Asia/Shanghai
sun.arch.data.model = 64
java.endorsed.dirs = /opt/soft/jdk1.8.0_181/jre/lib/endorsed
sun.cpu.isalist =
sun.jnu.encoding = UTF-8
com.xiaomi.passport.service.thrift.pool = 10
file.encoding.pkg = sun.io
file.separator = /
java.specification.name = Java Platform API Specification
java.class.version = 52.0
user.country = US
java.home = /opt/soft/jdk1.8.0_181/jre
thrift.runner.zookeeper.config = /services/com.xiaomi.passport.user.rights.PassportUserRightsService/Configuration/Default
java.vm.info = mixed mode
os.version = 3.18.6-2.el7.centos.x86_64
Russia.proxy.env = KSMOSCLOUDSRV
server.service.level = 5
path.separator = :
java.vm.version = 25.181-b13
China.proxy.env = LUGU
java.awt.printerjob = sun.print.PSPrinterJob
sun.io.unicode.encoding = UnicodeLittle
awt.toolkit = sun.awt.X11.XToolkit
sql.socket.connect.timeout.millisecond = 400
user.home = /home/work
java.specification.vendor = Oracle Corporation
client.service.level = 5
xiaomi.db.connpool.maxsize = 50
java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
java.vendor.url = http://java.oracle.com/
java.vm.vendor = Oracle Corporation
java.runtime.name = Java(TM) SE Runtime Environment
............
............
............
此处输出太多,大家可以自己尝试。
jstack的常用参数如下:
-l:长列表. 打印关于锁的附加信息
[work@c5-ac-mt01 ~]$ jstack -l 28025|more
2018-11-05 10:56:40
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.25-b02 mixed mode):
"Attach Listener" #652 daemon prio=9 os_prio=0 tid=0x00007f9bac003000 nid=0x2436 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"thrift-worker-4" #405 prio=5 os_prio=0 tid=0x00007f9a08002800 nid=0x1bfab waiting on condition [0x00007f98ecad3000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x0000000702183d90> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
"thrift-worker-3" #403 prio=5 os_prio=0 tid=0x00007f9a08001800 nid=0x1b8a1 waiting on condition [0x00007f98ec9d2000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x0000000702183d90> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
"thrift-worker-2" #399 prio=5 os_prio=0 tid=0x00007f9a04001800 nid=0xbadd waiting on condition [0x00007f98ecdd6000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x0000000702183d90> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
从jstack的输出中我们可以看到每一个线程当前所处的状态以及其当前所占用的锁和等待的锁,还可以检测是否存在死锁。
常用参数如下:
-class 显示ClassLoad的相关信息
-compiler 显示JIT编译的相关信息
-gc 显示和gc相关的堆信息
-gccapacity 显示各个代的容量以及使用情况
-gcmetacapacity 显示metaspace的大小
-gcnew 显示新生代信息
-gcnewcapacity 显示新生代大小和使用情况
-gcold 显示老年代和永久代的信息
-gcoldcapacity 显示老年代的大小
-gcutil 显示垃圾收集信息
-gccause 显示垃圾回收的相关信息(通-gcutil),同时显示最后一次或当前正在发生的垃圾回收的诱因
-printcompilation 输出JIT编译的方法信息
我们来看看 jstat -gc pid 的输出:
接下来解释各个出现的参数代表的含义:
在JDK1.8中永久代消失,出现了元空间,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
为什么要做 JDK 8 中永久代向元空间的这个转换?
常用参数如下:
如果使用不带选项参数的jmap打印共享对象映射,将会打印目标虚拟机中加载的每个共享对象的起始地址、映射大小以及共享对象文件的路径全称。
-heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况。
-histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量。
-dump:[live,]format=b,file= 使用hprof二进制形式,输出jvm的heap内容到文件, live子选项是可选的,假如指定live选项,那么只输出活的对象到文件。 dump文件可以通过MemoryAnalyzer分析查看,网址:http://www.eclipse.org/mat/,可以查看dump时对象数量,内存占用,线程情况等。
看看 jmap -heap pid的输出信息:
从这里可以看到JVM堆内存的一些具体信息.
在这篇博客里我们学习了常见的JDK自带的JVM相关的命令,有了这些命令我们就可以知道JVM的具体信息,帮助我们在实际项目中排查问题,当然这是一个熟能生巧的地方,需要我们记住常用的命令和参数.
如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,可以进群366533258一起交流学习哦~
本群给大家提供一个学习交流的平台,内设菜鸟Java管理员一枚、精通算法的金牌讲师一枚、Android管理员一枚、蓝牙BlueTooth管理员一枚、Web前端管理一枚以及C#管理一枚。欢迎大家进来交流技术。