《深入理解Java虚拟机》读后总结(一)JVM内存模型
基于Sun HotSpot JVM?
?直接上圖:
?
從圖中看到,JVM內存分為兩個主要區域,一個是所有線程共享的數據區,一個是線程隔離數據區(線程私有)
線程隔離數據區
程序計數器(Program Counter Register):
一小塊內存空間,單前線程所執行的字節碼行號指示器。字節碼解釋器工作時,通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。
JVM虛擬機棧(Java Virtual Machine Stacks):
Java方法執行內存模型,用于存儲局部變量,操作數棧,動態鏈接,方法出口等信息。是線程私有的。
本地方法棧(Native Method Stacks):
為JVM用到的Native方法服務,Sun HotSpot 虛擬機把本地方法棧和JVM虛擬機棧合二為一。是線程私有的。
線程共享的數據區
方法區(Method Area):
用于存儲JVM加載的類信息、常量、靜態變量、即使編譯器編譯后的代碼等數據。
運行時常量池(Runtime Constant Pool):
是方法區的一部分,用于存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載后存放到方法取得運行時常量池中。具備動態性,用的比較多的就是String類的intern()方法。
JVM堆( Java Virtual Machine Heap):
存放所有對象實例的地方。
新生代,由Eden Space 和大小相同的兩塊Survivor組成
舊生待,存放經過多次垃圾回收仍然存活的對象
如圖:
直接內存(Direct Memory):
它并不是虛擬機運行時數據區的一部分,也不是JAVA虛擬機規范中定義的內存區域。在JDK1.4中加入了NIO類,引入了一種基于通道(Channel)于緩沖區(Buffer)的I/O方式,他可以使用Native函數庫直接分配堆外內存,然后通過一個存儲在JAVA堆里面的DirectByteBuffer對象作為這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在JAVA堆中和Native堆中來回復制數據。
基于Sun HotSpot JVM?
請先了解JVM內存模型在來看此篇文章
使用對JVM不同內存區域灌入數據,導致相關區域內存溢出,來驗證JVM內存分配
先看一個經典問題:
Java代碼?
String?s1?=?"小金子(aub)";?? String?s2?=?"小金子(aub)";?? String?s3?=?"小金子"?+?"(aub)";?? String?s4?=?new?String("小金子(aub)");?? String?s5?=?"小金子"?+?new?String("(aub)");?? String?s6?=?s4.intern();?? System.out.println("s1?==?s2:?"?+?(s1?==?s2));//true;?? System.out.println("s1?==?s3:?"?+?(s1?==?s3));//true;?? System.out.println("s2?==?s3:?"?+?(s2?==?s3));//true;?? System.out.println("s1?==?s4:?"?+?(s1?==?s4));//false;?? System.out.println("s1?==?s5:?"?+?(s1?==?s5));//false;?? System.out.println("s4?==?s5:?"?+?(s4?==?s5));//false;?? System.out.println("s1?==?s6:?"?+?(s1?==?s6));//true;???原因就在與String對象特殊的內存分配方式:(Strings pool是JVM內存中運行時常量池的一部分)
1.String s1 = new String("小金子(aub)");
2.String s2 = "小金子(aub)";
3.String s3 = "小金子" + "(aub)";
雖然兩個語句都是返回一個String對象的引用,但是jvm對兩者的處理方式是不一樣的。
對于第一種,jvm會馬上在heap中創建一個String對象,然后將該對象的引用返回給用戶。
對于第二種,jvm首先會在內部維護的strings pool中通過String的 equels 方法查找是對象池中是否存放有該String對象,如果有,則返回已有的String對象給用戶,而不會在heap中重新創建一個新的String對象;如果對象池中沒有該String對象,jvm則在heap中創建新的String對象,將其引用返回給用戶,同時將該引用添加至strings pool中。
注意:使用第一種方法創建對象時,jvm是不會主動把該對象放到strings pool里面的,除非程序調用 String的intern方法
對于第三種,jvm會進行“+”運算符號的優化,兩遍都是字符串常量會做類似于第二種的處理,如果“+”任意一邊是一個變量,就會做類似第一種的處理。
JVM棧和Native Method棧內存分配:
JAVA中八個基本類型數據,在運行時都是分配在棧中的。在棧上分配的內存,隨著數據的進棧出棧,方法運行完畢,或則線程結束時,自動被回收掉了。
測試代碼如下:
Java代碼?
?
public?class?JvmStackOOM?{??private?int?stackLength?=?1;??public?void?execute()?{??try?{??stackLeak();??}?catch?(Throwable?e)?{??System.out.println("stackLength?:?"?+?stackLength);??e.printStackTrace();??}??}??private?void?stackLeak()?{??stackLength++;??stackLeak();??}?? }??用一個遞歸不斷地對實例變量stackLength進行自增操作,當JVM在擴展棧時無法申請到足夠的空間,將產生StackOverflowError
可以使用Jvm 參數-Xss配置棧大小,例如:-Xss2M,棧內存越大,可的棧深度越大,在內存不變的情況下,jvm可創建的線程就越少,需要合理設置。
方法區內存分配:
?類信息和運行時常量將會分配到此區域。
測試代碼如下:
Java代碼?
public?class?JvmRuntimeConstantPoolOOM?{??private?int?runtimeConstantCount?=?1;??public?void?execute()?{??try?{??runtimeConstantLeak();??}?catch?(Throwable?e)?{??System.out.println("runtimeConstantCount?:?"?+?runtimeConstantCount);??e.printStackTrace();??}??}??private?void?runtimeConstantLeak()?{??List<String>?list?=?new?ArrayList<String>();??while?(true)?{??list.add(String.valueOf(runtimeConstantCount++).intern());??}??}?? }??使用String的intern()方法向方法區中灌入數據,當方法區內存不足時,拋出OutOfMemoryError: PermGen space,
也可以加載過多的類的方式,測試是否有OutOfMemoryError: PermGen space異常,如果有說明類信息也是存放在方法區中的可以
使用Jvm 參數-XX:PermSize和-XX:MaxPermSize配置棧大小,例如:-XX:PermSize=10M -XX:MaxPermSize=10M
堆內存分配:
所有對象實例及數組都會在堆上分配。
堆分為新生代和老年代。新生代分為3個區域:一個eden區,和兩個survivor區(互為From、To,相對的),
新建對象時首先想eden區申請分配空間,如果空間夠,就直接進行分配,否則進行一次Minor GC(新生代垃圾回收)。
Minor GC后再次嘗試將對象放到eden區,如果空間仍然不夠,直接在老年代創建對象。
測試代碼如下:
Java代碼?
?
public?class?JvmHeapOOM?{??private?int?bojectCount?=?1;??public?void?execute()?{??try?{??heapLeak();??}?catch?(Throwable?e)?{??System.out.println("bojectCount?:?"?+?bojectCount);??e.printStackTrace();??}??}??private?void?heapLeak()?{??List<OOMObject>?list?=?new?ArrayList<OOMObject>();??while?(true)?{??list.add(new?OOMObject());??bojectCount++;??}??}??private?class?OOMObject?{??}?? }??創建多個OOMObject對象放到List中,當堆內存不足時,產生OutOfMemoryError:Java Heap space
使用Jvm 參數-Xm -Xmx -Xmn -XX:SurvivorRatio配置堆,例如:-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
本地直接內存分配:?
堆外內存,NIO相關操作將在此分配內存
使用Jvm 參數-XX:MaxDirectMemorySize配置,例如:-XX:MaxDirectMemorySize=10M
所有用到的JVM啟動參數:
-Xss2M?????? 設置JVM棧內存大小
-Xms20M ???設置堆內存初始值
-Xmx20M ???設置堆內存最大值
-Xmn10M ???設置堆內存中新生代大小
-XX:SurvivorRatio=8??設置堆內存中新生代Eden 和 Survivor 比例
-XX:PermSize=10M??設置方法區內存初始值
-XX:MaxPermSize=10M??設置方法區內存最大值
-XX:MaxDirectMemorySize=10M?設置堆內存中新生代大小
基于Sun HotSpot JVM?
這里將介紹幾款sun hotspot jvm 自帶的監控工具:
請確保java_home/bin配置到path環境變量下,因為這些工具都在jdk的bin目錄下
jps(JVM Process Status Tool):JVM機進程狀況工具
用來查看基于HotSpot?JVM里面所有進程的具體狀態,?包括進程ID,進程啟動的路徑等等。與unix上的ps類似,用來顯示本地有權限的java進程,可以查看本地運行著幾個java程序,并顯示他們的進程號。使用jps時,不需要傳遞進程號做為參數。
Jps也可以顯示遠程系統上的JAVA進程,這需要遠程服務上開啟了jstat服務,以及RMI注及服務,不過常用都是對本對的JAVA進程的查看。
命令格式:jps?[?options?]?[?hostid?]
常用參數說明:
-m?輸出傳遞給main方法的參數,如果是內嵌的JVM則輸出為null。
-l?輸出應用程序主類的完整包名,或者是應用程序JAR文件的完整路徑。
-v?輸出傳給JVM的參數。
例如:
Cmd命令行代碼?
C:\Users\Administrator>jps?-lmv?? 1796??-Dosgi.requiredJavaVersion=1.5?-Xms40m?-Xmx512m?-XX:MaxPermSize=256m?? 7340?sun.tools.jps.Jps?-lmv?-Denv.class.path=.;D:\DevTools\VM\jdk1.6.0_31\\lib\dt.jar;D:\DevTools\VM\jdk1.6.0_31\\lib\tools.jar;?-Dapplication.home=D:\DevTools\VM\jdk1.6.0_31?-Xms8m??其中pid為1796的是我的eclipse進程,pid為7340的是jps命令本身的進程
jinfo(Configuration Info for Java):JVM配置信息工具
可以輸出并修改運行時的java 進程的opts。用處比較簡單,用于輸出JAVA系統參數及命令行參數
命令格式:jinfo [ options ] [ pid ]
常用參數說明:
-flag ?輸出,修改,JVM命令行參數
例如:
Cmd命令行代碼?
C:\Users\Administrator>jinfo?1796???將會打印出很多jvm運行時參數信息,由于比較長這里不再打印出來,可以自己試試,內容一目了然
Jstack(Stack Trace for Java):JVM堆棧跟蹤工具
jstack用于打印出給定的java進程ID或core file或遠程調試服務的Java堆棧信息,如果是在64位機器上,需要指定選項"-J-d64“
命令格式:jstack [ option ] pid
常用參數說明:
-F 當’jstack [-l] pid’沒有相應的時候強制打印棧信息
-l ?長列表. 打印關于鎖的附加信息,例如屬于java.util.concurrent的ownable synchronizers列表.
-m 打印java和native c/c++框架的所有棧信息.
-h | -help打印幫助信息
?例如:
Cmd命令行代碼?
C:\Users\Administrator>jstack?1796?? 2013-05-22?11:42:38?? Full?thread?dump?Java?HotSpot(TM)?Client?VM?(20.6-b01?mixed?mode):??"Worker-30"?prio=6?tid=0x06514c00?nid=0x1018?in?Object.wait()?[0x056af000]??java.lang.Thread.State:?TIMED_WAITING?(on?object?monitor)??at?java.lang.Object.wait(Native?Method)??at?org.eclipse.core.internal.jobs.WorkerPool.sleep(WorkerPool.java:188)??-?locked?<0x1ad84a90>?(a?org.eclipse.core.internal.jobs.WorkerPool)??at?org.eclipse.core.internal.jobs.WorkerPool.startJob(WorkerPool.java:220)??at?org.eclipse.core.internal.jobs.Worker.run(Worker.java:50)?? ......?? ......?? ......?? ......?jstat(JVM statistics Monitoriing Tool):JVM統計信息監視工具
對Java應用程序的資源和性能進行實時的命令行的監控,包括了對Heap?size和垃圾回收狀況的監控
命令格式:jstat?[?option? pid [interval [ s | ms ] [count] ] ]?
常用參數說明:
???? -gcutil? 輸出已使用空間占總空間的百分比
???? -gccapacity 輸出堆中各個區域使用到的最大和最小空間
?例如:每隔1秒監控jvm內存一次,共監控5次
Cmd命令行代碼?
?
C:\Users\Administrator>jstat?-gccapacity??1796??1s??5??NGCMN????NGCMX?????NGC?????S0C???S1C???????EC??????OGCMN??????OGCMX???????OGC?????????OC??????PGCMN????PGCMX?????PGC???????PC?????YGC????FGC??13632.0?174720.0??40896.0?4032.0?4032.0??32832.0????27328.0???349568.0????81684.0????81684.0??12288.0?262144.0??80640.0??80640.0?????42????96??13632.0?174720.0??40896.0?4032.0?4032.0??32832.0????27328.0???349568.0????81684.0????81684.0??12288.0?262144.0??80640.0??80640.0?????42????96??13632.0?174720.0??40896.0?4032.0?4032.0??32832.0????27328.0???349568.0????81684.0????81684.0??12288.0?262144.0??80640.0??80640.0?????42????96??13632.0?174720.0??40896.0?4032.0?4032.0??32832.0????27328.0???349568.0????81684.0????81684.0??12288.0?262144.0??80640.0??80640.0?????42????96??13632.0?174720.0??40896.0?4032.0?4032.0??32832.0????27328.0???349568.0????81684.0????81684.0??12288.0?262144.0??80640.0??80640.0?????42????97??Cmd命令行代碼?
?
C:\Users\Administrator>jstat?-gcutil??1796??1s??5??S0?????S1?????E??????O??????P?????YGC?????YGCT????FGC????FGCT?????GCT??0.00???0.00???0.52??53.35??99.77?????42????0.513????99???38.119???38.632??0.00???0.00???0.52??53.35??99.77?????42????0.513????99???38.119???38.632??0.00???0.00???0.52??53.35??99.77?????42????0.513????99???38.119???38.632??0.00???0.00???0.52??53.35??99.77?????42????0.513????99???38.119???38.632??0.00???0.00???0.52??53.35??99.77?????42????0.513????99???38.119???38.632??? 一些術語的中文解釋:
???????? S0C:年輕代中第一個survivor(幸存區)的容量 (KB)
???????? S1C:年輕代中第二個survivor(幸存區)的容量 (KB)
???????? S0U:年輕代中第一個survivor(幸存區)目前已使用空間 (KB)
???????? S1U:年輕代中第二個survivor(幸存區)目前已使用空間 (KB)
?????????? EC:年輕代中Eden(伊甸園)的容量 (KB)
?????????? EU:年輕代中Eden(伊甸園)目前已使用空間 (KB)
?????????? OC:Old代的容量 (KB)
?????????? OU:Old代目前已使用空間 (KB)
?????????? PC:Perm(持久代)的容量 (KB)
?????????? PU:Perm(持久代)目前已使用空間 (KB)
???????? YGC:從應用程序啟動到采樣時年輕代中gc次數
?????? YGCT:從應用程序啟動到采樣時年輕代中gc所用時間(s)
???????? FGC:從應用程序啟動到采樣時old代(全gc)gc次數
?????? FGCT:從應用程序啟動到采樣時old代(全gc)gc所用時間(s)
???????? GCT:從應用程序啟動到采樣時gc用的總時間(s)
??? NGCMN:年輕代(young)中初始化(最小)的大小 (KB)
??? NGCMX:年輕代(young)的最大容量 (KB)
??????? NGC:年輕代(young)中當前的容量 (KB)
?? OGCMN:old代中初始化(最小)的大小 (KB)?
?? OGCMX:old代的最大容量 (KB)
?????? OGC:old代當前新生成的容量 (KB)
?? PGCMN:perm代中初始化(最小)的大小 (KB)?
?? PGCMX:perm代的最大容量 (KB)???
?????? PGC:perm代當前新生成的容量 (KB)
????? ? ? S0:年輕代中第一個survivor(幸存區)已使用的占當前容量百分比
???????? S1:年輕代中第二個survivor(幸存區)已使用的占當前容量百分比
? ? ? ?? ? E:年輕代中Eden(伊甸園)已使用的占當前容量百分比
? ? ? ?? ? O:old代已使用的占當前容量百分比
? ? ? ???? P:perm代已使用的占當前容量百分比
? S0CMX:年輕代中第一個survivor(幸存區)的最大容量 (KB)
?S1CMX :年輕代中第二個survivor(幸存區)的最大容量 (KB)
??? ECMX:年輕代中Eden(伊甸園)的最大容量 (KB)
?????? DSS:當前需要survivor(幸存區)的容量 (KB)(Eden區已滿)
??????? ? TT: 持有次數限制
???? ? MTT : 最大持有次數限制
??
jmap( Memory Map for Java):JVM內存映像工具
打印出某個java進程(使用pid)內存內的所有‘對象’的情況(如:產生那些對象,及其數量)
命令格式:jmap [ option ] pid
常用參數說明:
??????? -dump:[live,]format=b,file=<filename>?使用二進制形式輸出jvm的heap內容到文件中,?live子選項是可選的,假如指定live選項,那么只輸出活的對象到文件.?
??????? -histo[:live]?打印每個class的實例數目,內存占用,類全名信息.?VM的內部類名字開頭會加上前綴”*”.?如果live子參數加上后,只統計活的對象數量.?
????????-F?強迫.在pid沒有相應的時候使用-dump或者-histo參數.?在這個模式下,live子參數無效.?
例如:以二進制形式輸入當前堆內存映像到文件data.hprof中
Cmd命令行代碼?
?
生成的文件可以使用jhat工具進行分析,在OOM(內存溢出)時,分析大對象,非常有用
通過使用如下參數啟動JVM,也可以獲取到dump文件:
????? -XX:+HeapDumpOnOutOfMemoryError
????? -XX:HeapDumpPath=./java_pid<pid>.hprof
????? 在jvm發生內存溢出時生成內存映像文件
jhat(JVM Heap Analysis Tool):JVM堆轉儲快照分析工具
用于對JAVA?heap進行離線分析的工具,他可以對不同虛擬機中導出的heap信息文件進行分析,如LINUX上導出的文件可以拿到WINDOWS上進行分析,可以查找諸如內存方面的問題。
命令格式:jhat dumpfile(jmap生成的文件)
例如:分析jmap導出的內存映像
Cmd命令行代碼?
?
?執行成功后,訪問http://localhost:7000即可查看內存信息,
MAT(Memory Analyzer Tool):一個基于Eclipse的內存分析工具
官網:?http://www.eclipse.org/mat/
update:http://download.eclipse.org/mat/1.2/update-site/
這是eclipse的一個插件,安裝后可以打開xxx.hprof文件,進行分析,比jhat更方便使用,有些時候由于線上xxx.hprof文件過大,直接使用jhat進行初步分析了,可以的話拷貝到本地分析效果更佳。
圖形化監控工具:
在JDK安裝目錄bin下面有兩個可視化監控工具
1. JConsole(Java Monitoring and Management Console) 基于JMX的可視化管理工具。
2. VisualVM(All-in-one Java Troubleshooting Tool)隨JDK發布的最強大的運行監視和故障處理程序。
推薦使用VisualVM,他有很多插件,可以更方便的監控運行時JVM
?
總結
以上是生活随笔為你收集整理的《深入理解Java虚拟机》读后总结(一)JVM内存模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: charles 手机抓包设置
- 下一篇: Java线程池如何合理配置核心线程数