1. JVM的運行參數
1.1 三種參數類型
-
標準參數
- help
- -version
-
-X參數 (非標準參數)
- -Xint
- -Xcomp
-
-XX參數(使用率比較高, 常用語jvm調優)
- -XX:newSize
- -XX:+UseSeriaIGC
1.2 標準參數
-D 設置系統屬性: java -Dstr=hello
String str = System.getProperty("str")
println(str)
輸出結果: hello
JVM的兩個啟動模式: server和client
JVM在啟動的時候回根據硬件和操作系統自動選擇是使用Server還是Client類型的JVM
64位操作系統只有Server類型,沒有Client
1.3 非標準參數
JVM的運行模式:
-Xint: 解釋模式, 強制要求JVM執行所有的字節碼(interpreted mode)
-Xcomp: 編譯模式,JVM在第一次使用時會把所有字節碼編譯成本地代碼(compiled mode)
-Xmixed:混合模式,將解釋模式和編譯模式混合使用, JVM默認的模式, 也是推薦的模式(mixed mode)
1.4 -XX 參數
-XX參數也是非標準參數, 主要用于JVM的調優和debug操作
兩種使用方式:
- boolean類型
格式: -XX:[+-]<name> 表示啟用或者禁用name指令
-XX:+DisableExplicitGC表示禁用手動調用gc操作, System.gc()無效 - 非boolean類型
格式: -XX<name>=<value> 表示name的屬性值為value
-XX:NewRatie=1 表示新生代和老年代的比值
1.5 -Xms, -Xmx
-Xms和-Xmx分別是設置jvm的堆內存的初始大小和最大大小
-Xms512m相當于: -XX:InitialHeapSize=512m
-Xms2048m相當于:-XX:MaxHeapSize=2048m
JVM啟動時會自動設置Heap size的值, -Xms初始空間是物理內存的1/64, -Xmx最大值時物理空間的1/4。
進行JVM優化時, 可將-Xms和-Xmx設置值相同, 最大值不超過物理內存的80%
1.6 查看jvm運行參數:
運行java命令時打印參數: -XX:+PrintFlagsFinal
參數列表中: = 表示默認值, :=表示值被修改過
1.7 查看正在運行的進程的jvm信息
jps -l
查看當前系統中所有運行的java項目的進程id及詳細包信息
jinfo -flags 29200
查看進程id為29200進程的所有jvm參數信息
jinfo -flag MaxheapSize 29200
查看pid下的具體某一jvm信息
2. jvm的內存模型
jvm的內存模型1.7和1.8有較大的區別
2.1 jdk1.7的堆內存模型:
- 年輕區: 新new的一些對象會在這個區域,young區被分為:Eden區和兩個大小嚴格相同的Survivor區, 當Eden區變滿時, 數據會移到Survior中,幾次jvm垃圾收集后, 依然存活的Survivor會知道老年區
- 老年區:tenured區主要保存生命周期長的對象, 一般是一些老的對象
- 永久區: 主要保存class, method, field對象, 這部分空間一般不會溢出
2.2 jdk1.8的堆內存模型
年輕區:Eden+2*Survivor
老年區:OldGen
元數據空間(Metaspace):Matespace是不是在虛擬機內部, 而是占用服務器的內存空間,這是和1.7最大的區別
2.3 為什么廢除了1.7的永久區
現實中是因為永久代內存總會發生不夠用或者內存泄漏, 基于此將永久區廢棄, 改為使用本地的內存空間
2.4 通過jstat命令進行查看堆內存的使用情況
jstat 參數指令 進程id 時間間隔 查詢次數
jstat -class 29200
查看類加載情況
loaded: 加載類的數量
Bytes:類占得空間
uloaded:未加載類的數量
Time: 加載占用的時間
jstat -compiler 29200
查看編譯情況
查看垃圾回收器的使用情況
jstat -gc 29200 1000 10
每1000毫秒(1秒)鐘打印一次gc使用情況, 總共打印10次
3. jmap的使用以及內存溢出分析
3.1 查看內存使用情況
jmap -heap 29200
3.2 查看內存中對象數量及大小:
jmap -histo <pid> | more
查看所有對象的
jmap -histo:live <pid> | more
查看活躍對象的
[B-> byte
[I -> int
[C->char
3.3 將內存使用情況dump到文件中, jhat對快照文件分析
jmap -dump:format=b,file=filename <pid>
例如 jmap -dump:format=b,file=dumptest.dat 29200 會生成一份29200內存使用情況的二進制文件dumptest.dat
使用jhat對dump的二進制文件分析:jhat port 9999 dumptest.dat
瀏覽器訪問7000端口:
4. 內存溢出的定位與分析
內存溢出在生產環境中經常會遇到, 比如不斷地將數據寫入到一個集合中,出現了死循環, 讀取超大文件等等,都可能造成內存溢出。
4.1模擬內存溢出:
設置jvm參數: -Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
jvm初始內存8m, 最大內存8m, 內存溢出時dump內存快照
編寫代碼:
public static void main(String[] args) {
ArrayList<String> strings = new ArrayList<>();
for (int i = 0; i <= 1000000000; i++){
String id = "";
for (int j = 0; j < 1000; j++){
id += UUID.randomUUID().toString();
}
strings.add(id);
}
}
mat工具分析內存
4.2 jstack的使用:
有時候我們需要查看jvm中的線程執行情況, 比如發現CPU的負載突然增高,出現了死鎖,死循環等, 由于程序正常運行的,沒有任何的輸出, 從日志方面也看不出什么問題, 需要從jvm的內部線程的執行情況查找并且分析原因。
jstack <pid>
可以看到當前pid進程中所有線程的執行情況。
5. jstack中Java中線程的狀態
- 初始狀態(NEW): 創建一個thread對象, 但還未調用start啟動線程, 則處于初始狀態
- 運行狀態(RUNNABLE):
- 就緒狀態: 等待CPU分配執行權, 放在就緒隊列中
- 運行狀態:獲得CPU的執行權, 一個CPU在同一時間只能執行一個線程,所以每個CPU在每個時刻都只有一條運行態的線程。
- 阻塞狀態(BLOCKED):java中指請求某一鎖失敗時, 線程會進入阻塞態, 阻塞態會不斷地請求資源, 一旦請求成功就會進入就緒隊列, 等待CPU分配。
- 等待狀態(WAITING):無線等待, 當線程中調用 wait, join, park等函數時,線程進入等待狀態,等待線程會釋放CPU以及鎖資源,進入等待隊列, 需要其他線程指示才能繼續運行。
- 超時等待態 (TIMED_WAITING): 有限等待,與等待態的區別是, 到了超時時間會進入阻塞隊列, 開始競爭鎖。
- 終止態
5.1死鎖問題
模擬死鎖問題:
private static Object obj1 = new Object();
private static Object obj2 = new Object();
public static void main(String[] args) {
new Thread(new Thread1()).start();
new Thread(new Thread2()).start();
}
private static class Thread1 implements Runnable{
@Override
public void run() {
synchronized (obj1){
System.out.println("Thread1 拿到了 obj1 的鎖!");
try {
// 停頓2秒的意義在于,讓Thread2線程拿到obj2的鎖
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
System.out.println("Thread1 拿到了 obj2 的鎖!");
}
}
}
}
private static class Thread2 implements Runnable{
@Override
public void run() {
synchronized (obj2){
System.out.println("Thread2 拿到了 obj2 的鎖!");
try {
// 停頓2秒的意義在于,讓Thread1線程拿到obj1的鎖
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
System.out.println("Thread2 拿到了 obj1 的鎖!");
}
}
}
}
線程1和線程2相互持續持有對方需要的鎖資源, 造成死鎖問題。
使用jstack查看死鎖進程:
jstack查找除了發生死鎖進程的原因, 以及對應的堆棧信息。
6. 使用JDK子代的 Java VisualVm
綜合的結合了以上介紹jvm性能查看的jdk自帶可視化工具:
既可以查看本地java進程, 也可以查看遠程進程
配置遠程java進程如tomcat的啟動文件catalina.sh, 允許jmx遠程監控
JAVA_OPTS="
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false"
開放9999端口, 不需要認證,關閉ssl認證