Java記憶體相關的常用命令
(尊重勞動成果,轉載請註明出處:https://blog.csdn.net/qq_25827845/article/details/83758124冷血之心的部落格)
目錄
利用JVM內建的指令對Java應用程式的資源和效能進行實時的命令列的監控
用於列印指定Java程序(或核心檔案、遠端除錯伺服器)的共享物件記憶體對映或堆記憶體細節
做為一名優秀的Java開發工程師,我們必不可少的會用到以下幾個命令:
- jps----顯示當前所有java程序pid
- jinfo----觀察程序執行環境引數
- jstack----顯示jvm中當前所有執行緒的執行情況和執行緒當前狀態
- jstat----利用JVM內建的指令對Java應用程式的資源和效能進行實時的命令列的監控
- jmap----用於列印指定Java程序(或核心檔案、遠端除錯伺服器)的共享物件記憶體對映或堆記憶體細節
這些命令都在我們的Java安裝目錄的/bin/下邊,如圖所示:
接下來,我們結合具體的案例來闡述這些常用的命令怎麼使用。
jps
顯示當前所有java程序pid
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
分析:
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
觀察程序執行環境引數
當我們想要觀察某個程序的執行環境引數時輸入: 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.<init>(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在什麼路徑下安裝的。
[[email protected] ~]# whereis java
java: /opt/soft/jdk/bin/java
開啟該目錄,我們果然發現了多個JDK版本:
我們的程序使用了jdk1.8.0_60,所以我們需要使用決定路徑來執行jinfo命令:
[[email protected] 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
顯示jvm中當前所有執行緒的執行情況和執行緒當前狀態
jstack的常用引數如下:
-l:長列表. 列印關於鎖的附加資訊
[[email protected] ~]$ 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的輸出中我們可以看到每一個執行緒當前所處的狀態以及其當前所佔用的鎖和等待的鎖,還可以檢測是否存在死鎖。
jstat
利用JVM內建的指令對Java應用程式的資源和效能進行實時的命令列的監控
常用引數如下:
-class 顯示ClassLoad的相關資訊
-compiler 顯示JIT編譯的相關資訊
-gc 顯示和gc相關的堆資訊
-gccapacity 顯示各個代的容量以及使用情況
-gcmetacapacity 顯示metaspace的大小
-gcnew 顯示新生代資訊
-gcnewcapacity 顯示新生代大小和使用情況
-gcold 顯示老年代和永久代的資訊
-gcoldcapacity 顯示老年代的大小
-gcutil 顯示垃圾收集資訊
-gccause 顯示垃圾回收的相關資訊(通-gcutil),同時顯示最後一次或當前正在發生的垃圾回收的誘因
-printcompilation 輸出JIT編譯的方法資訊
我們來看看 jstat -gc pid 的輸出:
接下來解釋各個出現的引數代表的含義:
- S0C:第一個Survivor區的大小
- S1C:第二個Survivor區的大小
- S0U:第一個Survivor區的使用大小
- S1U:第二個Survivor區的使用大小
- EC:Eden區的大小
- EU:Eden區的使用大小
- OC:老年代大小
- OU:老年代使用大小
- MC:元空間大小
- MU:元空間使用大小
- CCSC:壓縮類空間大小
- CCSU:壓縮類空間使用大小
- YGC:年輕代垃圾回收次數
- YGCT:年輕代垃圾回收消耗時間
- FGC:老年代垃圾回收次數
- FGCT:老年代垃圾回收消耗時間
- GCT:垃圾回收消耗總時間
在JDK1.8中永久代消失,出現了元空間,元空間與永久代之間最大的區別在於:元空間並不在虛擬機器中,而是使用本地記憶體
-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行型別解除安裝,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。
-XX:MaxMetaspaceSize,最大空間,預設是沒有限制的。
除了上面兩個指定大小的選項以外,還有兩個與 GC 相關的屬性:
- -XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少為分配空間所導致的垃圾收集
- -XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少為釋放空間所導致的垃圾收集
為什麼要做 JDK 8 中永久代向元空間的這個轉換?
- 字串存在永久代中,容易出現效能問題和記憶體溢位。
- 類及方法的資訊等比較難確定其大小,因此對於永久代的大小指定比較困難,太小容易出現永久代溢位,太大則容易導致老年代溢位。
- 永久代會為 GC 帶來不必要的複雜度,並且回收效率偏低。
jmap
用於列印指定Java程序(或核心檔案、遠端除錯伺服器)的共享物件記憶體對映或堆記憶體細節
常用引數如下:
<no option> 如果使用不帶選項引數的jmap列印共享物件對映,將會列印目標虛擬機器中載入的每個共享物件的起始地址、對映大小以及共享物件檔案的路徑全稱。
-heap 列印heap的概要資訊,GC使用的演算法,heap的配置及wise heap的使用情況。
-histo[:live] 列印每個class的例項數目,記憶體佔用,類全名資訊. VM的內部類名字開頭會加上字首”*”. 如果live子引數加上後,只統計活的物件數量。
-dump:[live,]format=b,file=<filename> 使用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#管理一枚。歡迎大家進來交流技術。