JVM 相關知識整理和學習

dmc3 9年前發布 | 158K 次閱讀 JVM Java開發
原文  http://blog.csdn.net/mshootingstar/article/details/44783227
 

JVM是虛擬機,也是一種規范 他遵循著馮·諾依曼體系結構的設計原理。 馮·諾依曼體系結構中,指出計算機處理的數據和指令都是二進制數,采用存儲程序方式不加區分的存儲在同一個存儲器里,并且順序執行,指令由操作碼和地址碼 組成,操作碼決定了操作類型和所操作的數的數字類型,地址碼則指出地址碼和操作數。從dos到window8,從unix到ubuntu和CentOS, 還有MAC OS等等,不同的操作系統指令集以及數據結構都有著差異,而JVM通過在操作系統上建立虛擬機,自己定義出來的一套統一的數據結構和操作指令,把同一套語 言翻譯給各大主流的操作系統,實現了跨平臺運行,可以說 JVM是java的核心,是java可以一次編譯到處運行的本質所在

我研究學習了JVM的組成和運行原理,JVM的統一數據格式規范、字節碼文件結構,JVM關于內存的管理。

一、JVM的組成和運行原理

     JVM的畢竟是個虛擬機 是一種規范,雖說符合馮諾依曼的計算機設計理念,但是他并不是實體計算機,所以他的組成也不是什么存儲器,控制器 運算器 輸入輸出設備。在我看來,JVM放在運行在真實的操作系統中表現的更像應用或者說是進程 他的組成可以理解為JVM這個進程有哪些功能模塊,而這些功能模塊的運作可以看做是JVM的運行原理。JVM有多種實現,例如Oracle的JVM,HP的JVM和IBM的JVM等,而在本文中研究學習的則是使用最廣泛的Oracle的HotSpot JVM。

     1.JVM在JDK中的位置

     JDK是java開發的必備工具箱,JDK其中有一部分是JRE,JRE是JAVA運行環境,JVM則是JRE最核心的部分。我從oracle.com截取了一張關于JDK Standard Edtion的組成圖,

JVM 相關知識整理和學習

     從最底層的位置可以看出來JVM有多重要,而實際項目中JAVA應用的性能優化,OOM等異常的處理最終都得從JVM這兒來解決。 HotSpot是Oracle關于JVM的商標,區別于IBM,HP等廠商開發的JVM。Java HotSpot Client VM和Java HotSpot Server VM是JDK關于JVM的兩種不同的實現,前者可以減少啟動時間和內存占用,而后者則提供更加優秀的程序運行速度(參考自: http://docs.oracle.com/javase/8/docs/technotes/guides/vm/index.html 該文檔有關于各個版本的JVM的介紹 )。在命令行,通過java -version可以查看關于當前機器JVM的信息,下面是我在Win8系統上執行命令的截圖,

JVM 相關知識整理和學習

     可以看出我裝的是build 20.13-b02版本,HotSpot 類型Server模式的JVM。

     2.JVM的組成

     JVM由4大部分組成:ClassLoader,Runtime Data Area,Execution Engine,Native Interface。

     我從CSDN找了一張描述JVM大致結構的圖:

JVM 相關知識整理和學習

     2.1. ClassLoader 是負責加載class文件 class文件在文件開頭有特定的文件標示 并且ClassLoader只負責class文件的加載 至于它是否可以運行,則由Execution Engine決定。

     2.2.Native Interface 是負責調用本地接口的。他的作用是調用不同語言的接口給JAVA用 他會在Native Method Stack中記錄對應的本地方法 然后調用該方法時就通過Execution Engine加載對應的本地lib。原本多于用一些專業領域 如JAVA驅動 地圖制作引擎等 現在關于這種本地方法接口的調用已經被類似于Socket通信 WebService等方式取代。

     2.3.Execution Engine 是執行引擎 也叫Interpreter。Class文件被加載后 會把指令和數據信息放入內存中,Execution Engine則負責把這些命令解釋給操作系統。

     2.4.Runtime Data Area 則是存放數據的, 分為五部分:Stack,Heap,Method Area,PC Register,Native Method Stack。幾乎所有的關于java內存方面的問題 都是集中在這塊。下圖是javapapers.com上關于Run-time Data Areas的描述:

JVM 相關知識整理和學習

     可以看出它把Method Area化為了Heap的一部分,javapapers.com中認為Method Area是Heap的邏輯區域,但這取決于JVM的實現者,而HotSpot JVM中把Method Area劃分為非堆內存,顯然是不包含在Heap中的。下圖是javacodegeeks.com中,2014年9月刊出的一片博文中關于Runtime Data Area的劃分,其中指出,NonHeap包含PermGen和Code Cache,PermGen包含Method Area,而且PermGen在JAVA SE 8中已經不再用了。查閱資料( https://abhirockzz.wordpress.com/2014/03/18/java-se-8-is-knocking-are-you-there/ )得知,java8中PermGen已經從JVM中移除并被MetaSpace取代,java8中也不會見到OOM:PermGen Space的異常。目前Runtime Data Area可以用下圖描述它的組成:

JVM 相關知識整理和學習

     2.4.1. Stack 是java棧內存,它等價于C語言中的棧, 棧的內存地址是不連續的, 每個線程都擁有自己的棧。 棧 里面存儲著的是StackFrame,在《JVM Specification》中文版中被譯作java虛擬機框架,也叫做棧幀。StackFrame包含三類信息:局部變量,執行環境,操作數棧。局部變 量用來存儲一個類的方法中所用到的局部變量。執行環境用于保存解析器對于java字節碼進行解釋過程中需要的信息,包括:上次調用的方法、局部變量指針和 操作數棧的棧頂和棧底指針。操作數棧用于存儲運算所需要的操作數和結果。StackFrame在方法被調用時創建,在某個線程中,某個時間點上,只有一個 框架是活躍的,該框架被稱為Current Frame,而框架中的方法被稱為Current Method,其中定義的類為Current Class。局部變量和操作數棧上的操作總是引用當前框架。當Stack Frame中方法被執行完之后,或者調用別的StackFrame中的方法時,則當前棧變為另外一個StackFrame。Stack的大小是由兩種類 型,固定和動態的,動態類型的棧可以按照線程的需要分配。 下面兩張圖是關于棧之間關系以及棧和非堆內存的關系基本描述(來自 http://www.programering.com/a/MzM3QzNwATA.html ):

JVM 相關知識整理和學習 JVM 相關知識整理和學習

     2.4.2. Heap 是用來存放對象信息的,和Stack不同,Stack代表著一種運行時的狀態。換句話說,棧是運行時單位,解決程序該如何執行的問題,而堆是存儲的單位, 解決數據存儲的問題。Heap是伴隨著JVM的啟動而創建,負責存儲所有對象實例和數組的。堆的存儲空間和棧一樣是不需要連續的,它分為Young Generation和Old Generation(也叫 Tenured  Generation )兩大部分。Young Generation分為Eden和Survivor,Survivor又分為From Space和 ToSpace。

     和Heap經常一起提及的概念是PermanentSpace,它是用來加載類對象的專門的內存區,是非堆內存,和Heap一起組成JAVA內存, 它包含MethodArea區(在沒有CodeCache的HotSpotJVM實現里,則MethodArea就相當于GenerationSpace)在JVM初始化的時候,我們可以通過參數來分別指定,PermanentSpace的大小、堆的大小、以及Young Generation和Old Generation的比值、Eden區和From Space的比值,從而來細粒度的適應不同JAVA應用的內存需求。

     2.4.3. PC Register 是 程序計數寄存器,每個JAVA線程都有一個單獨的PC Register,他是一個指針,由Execution Engine讀取下一條指令。如果該線程正在執行java方法,則PC Register存儲的是 正在被執行的指令的地址,如果是本地方法,PC Register的值沒有定義。PC寄存器非常小,只占用一個字寬,可以持有一個returnAdress或者特定平臺的一個指針。

     2.4.4. Method Area 在HotSpot JVM的實現中屬于非堆區,非堆區包括兩部分:Permanet Generation和Code Cache,而Method Area屬于Permanert Generation的一部分。Permanent Generation用來存儲類信息,比如說: class definitions,structures,methods, field, method (data and code) 和 constants。Code Cache用來存儲Compiled Code,即編譯好的本地代碼,在HotSpot JVM中通過JIT(Just In Time) Compiler生成,JIT是即時編譯器,他是為了提高指令的執行效率,把字節碼文件編譯成本地機器代碼,如下圖:

JVM 相關知識整理和學習

     引用一個經典的案例來理解Stack,Heap和Method Area的劃分,就是Sring a="xx";Stirng b="xx",問是否a==b? 首先==符號是用來判斷兩個對象的引用地址是否相同,而在上面的題目中,a和b按理來說申請的是Stack中不同的地址,但是他們指向Method Area中Runtime Constant Pool的同一個地址,按照網上的解釋,在a賦值為“xx”時,會在Runtime Contant Pool中生成一個String Constant,當b也賦值為“xx”時,那么會在常量池中查看是否存在值為“xx”的常量,存在的話,則把b的指針也指向“xx”的地址,而不是新生 成一個 String Constant 。我查閱了網絡上大家關于 String Constant 的存儲的說說法,存在略微差別的是,它存儲在哪里,有人說Heap中會分配出一個常量池,用來存儲常量,所有線程共享它。而有人說常量池是Method Area的一部分,而Method Area屬于非堆內存,那怎么能說常量池存在于堆中?

     我認為,其實兩種理解都沒錯。Method Area的確從邏輯上講可以是Heap的一部分,在某些JVM實現里從堆上開辟一塊存儲空間來記錄常量是符合JVM常量池設計目的的,所以前一種說法沒問 題。對于后一種說法,HotSpot JVM的實現中的確是把方法區劃分為了非堆內存,意思就是它不在堆上。 我在HotSpot JVM做了個簡單的實驗,定義多個常量之后,程序拋出OOM:PermGen Space異常,印證了JVM實現中常量池是在Permanent Space中的說法。但是,我的JDK版本是1.6的。查閱資料,JDK1.7中InternedStrings已經不再存儲在 PermanentSpace中,而是放到了Heap中;JDK8中PermanentSpace已經被完全移除,InternedStrings也被放 到了MetaSpace中(如果出現內存溢出,會報OOM:MetaSpace,這里有個關于兩者性能對比的文章: http://blog.csdn.net/zhyhang/article/details/17246223 )。 所 以,仁者見仁,智者見智,一個饅頭足以引發血案,就算是同一個商家的JVM,畢竟JDK版本在更新,或許正如StackOverFlow上大神們所說,對 于理解JVM Runtime Data Area這一部分的劃分邏輯,還是去看對應版本的JDK源碼比較靠譜,或者是參考不同的版本JVM Specification( http://docs.oracle.com/javase/specs/ )。

     2.4.5. Native Method Stack 是供本地方法(非java)使用的棧。每個線程持有一個Native Method Stack。

     3.JVM的運行原理簡介

     Java 程序被javac工具編譯為.class字節 碼文件之后,我們執行java命令,該class文件便被JVM的Class Loader加載,可以看出JVM的啟動是通過JAVA Path下的java.exe或者java進行的。JVM的初始化、運行到結束大概包括這么幾步:

     調用操作系統API判斷系統的CPU架構,根據對應CPU類型尋找位于JRE目錄下的/lib/jvm.cfg文件,然后通過該配置文件找到對應的 jvm.dll文件(如果我們參數中有-server或者-client, 則加載對應參數所指定的jvm.dll,啟動指定類型的JVM),初始化jvm.dll并且掛接到JNIENV結構的實例上,之后就可以通過JNIENV 實例裝載并且處理class文件了。class文件是字節碼文件,它按照JVM的規范,定義了變量,方法等的詳細信息,JVM管理并且分配對應的內存來執 行程序,同時管理垃圾回收。直到程序結束,一種情況是JVM的所有非守護線程停止,一種情況是程序調用System.exit(),JVM的生命周期也結 束。

     關于JVM如何管理分配內存,我通過class文件和垃圾回收兩部分進行了學習。

二、JVM的內存管理和垃圾回收

     JVM中的內存管理主要是指JVM對于Heap的管理,這是因為Stack,PC Register和Native Method Stack都是和線程一樣的生命周期,在線程結束時自然可以被再次使用。雖然說,Stack的管理不是重點,但是也不是完全不講究的。

     1.棧的管理

     JVM允許棧的大小是固定的或者是動態變化的。在Oracle的關于參數設置的官方文檔中有關于Stack的設置( http://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/jrdocs/refman/optionX.html#wp1024112 ),是通過-Xss來設置其大小。關于Stack的默認大小對于不同機器有不同的大小,并且不同廠商或者版本號的jvm的實現其大小也不同,如下表是HotSpot的默認大小:

Platform
Default
Windows IA32
64 KB
Linux IA32
128 KB
Windows x86_64
128 KB
Linux x86_64
256 KB
Windows IA64
320 KB
Linux IA64
1024 KB (1 MB)
Solaris Sparc
512 KB

我們一般通過減少常量,參數的個數來減少棧的增長,在程序設計時,我們把一些常量定義到一個對象中,然后來引用他們可以體現這一點。另外,少用遞歸調用也可以減少棧的占用。

棧是不需要垃圾回收的,盡管說垃圾回收是java內存管理的一個很熱的話題,棧中的對象如果用垃圾回收的觀點來看,他永遠是live狀態,是可以reachable的,所以也不需要回收,他占有的空間隨著Thread的結束而釋放。(參考自: http://stackoverflow.com/questions/20030120/java-default-stack-size

關于棧一般會發生以下兩種異常:

     1.當線程中的計算所需要的棧超過所允許大小時,會拋出StackOverflowError。

     2.當Java棧試圖擴展時,沒有足夠的存儲器來實現擴展,JVM會報OutOfMemoryError。

     我針對棧進行了實驗,由于遞歸的調用可以致使棧的引用增加,導致溢出,所以設計代碼如下:

JVM 相關知識整理和學習

     我的機器是x86_64系統,所以Stack的默認大小是128KB,上述程序在運行時會報錯:

JVM 相關知識整理和學習

     而當我在eclipse中調整了-Xss參數到3M之后,該異常消失。

JVM 相關知識整理和學習

     另外棧上有一點得注意的是,對于本地代碼調用,可能會在棧中申請內存,比如C調用malloc(),而這種情況下,GC是管不著的,需要我們在程序中,手動管理棧內存,使用free()方法釋放內存。

     2.堆的管理

     堆的管理要比棧管理復雜的多,我通過堆的各部分的作用、設置,以及各部分可能發生的異常,以及如何避免各部分異常進行了學習。

JVM 相關知識整理和學習

    上圖是 Heap和PermanentSapce的組合圖 ,其中 Eden區里面存著是新生的對象,From Space和To Space中存放著是每次垃圾回收后存活下來的對象 ,所以每次垃圾回收后,Eden區會被清空。 存 活下來的對象先是放到From Space,當From Space滿了之后移動到To Space。當To Space滿了之后移動到Old Space。Survivor的兩個區是對稱的,沒先后關系,所以同一個區中可能同時存在從Eden復制過來 對象,和從前一個Survivor復制過來的對象,而復制到年老區的只有從第一個Survivor復制過來的對象。而且,Survivor區總有一個是空 的。同時,根據程序需要,Survivor區是可以配置為多個的(多于兩個),這樣可以增加對象在年輕代中的存在時間,減少被放到年老代的可能。

     Old Space中則存放生命周期比較長的對象,而且有些比較大的新生對象也放在Old Space中。

     堆的大小通過-Xms和-Xmx來指定最小值和最大值,通過-Xmn來指定Young Generation的大小(一些老版本也用-XX:NewSize指定), 即上圖中的Eden加FromSpace和ToSpace的總大小。然后通過-XX:NewRatio來指定Eden區的大小,在Xms和Xmx相等的情 況下,該參數不需要設置。通過-XX:SurvivorRatio來設置Eden和一個Survivor區的比值。(參考自博文: http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html

     堆異常分為兩種,一種是Out of Memory(OOM) 一種是Memory Leak(ML)。Memory Leak最終將導致OOM。實際應用中表現為:從Console看,內存監控曲線一直在頂部,程序響應慢,從線程看,大部分的線程在進行GC,占用比較多 的CPU,最終程序異常終止,報OOM。OOM發生的時間不定,有短的一個小時,有長的10天一個月的。關于異常的處理,確定OOM/ML異常后,一定要 注意保護現場,可以dump heap,如果沒有現場則開啟GCFlag收集垃圾回收日志,然后進行分析,確定問題所在。如果問題不是ML的話,一般通過增加Heap,增加物理內存來 解決問題,是的話,就修改程序邏輯。

     3.垃圾回收

     JVM中會在以下情況觸發回收:對象沒有被引用 作用域發生未捕捉異常 程序正常執行完畢 程序執行了System.exit() 程序發生意外終止。

     JVM中標記垃圾使用的算法是一種根搜索算法。簡單的說,就是從一個叫GC Roots的對象開始 向下搜索 如果一個對象不能達到GC Roots對象的時候 說明它可以被回收了。這種算法比一種叫做引用計數法的垃圾標記算法要好,因為它避免了當兩個對象啊互相引用時無法被回收的現象。

     JVM中對于被標記為垃圾的對象進行回收時又分為了一下3種算法:

     1.標記清除算法 ,該算法是從根集合掃描整個空間,標記存活的對象,然后在掃描整個空間對沒有被標記的對象進行回收,這種算法在存活對象較多時比較高效,但會產生內存碎片。

     2.復制算法 ,該算法是從根集合掃描,并將存活的對象復制到新的空間,這種算法在存活對象少時比較高效。

     3.標記整理算法 ,標記整理算法和標記清除算法一樣都會掃描并標記存活對象,在回收未標記對象的同時會整理被標記的對象,解決了內存碎片的問題。

     JVM中,不同的 內存區域作用和性質不一樣,使用的垃圾回收算法也不一樣,所以JVM中又定義了幾種不同的垃圾回收器(圖中連線代表兩個回收器可以同時使用):

JVM 相關知識整理和學習

     1.Serial GC 。從名字上看,串行GC意味著是一種單線程的,所以它要求收集的時候所有的線程暫停。這對于高性能的應用是不合理的,所以串行GC一般用于Client模式的JVM中。

     2.ParNew GC 。是在SerialGC的基礎上,增加了多線程機制。但是如果機器是單CPU的,這種收集器是比SerialGC效率低的。

     3.Parrallel Scavenge GC 。這種收集器又叫吞吐量優先收集器,而吞吐量=程序運行時間/(JVM執行回收的時間+程序運行時間),假設程序運行了100分鐘,JVM的垃圾回收占用 1分鐘,那么吞吐量就是99%。Parallel Scavenge GC由于可以提供比較不錯的吞吐量,所以被作為了server模式JVM的默認配置。

     4.ParallelOld 是老生代并行收集器的一種,使用了標記整理算法,是JDK1.6中引進的,在之前 老生代 只能使用串行回收收集器。

     5.Serial Old 是老生代client模式下的默認收集器,單線程執行,同時也作為CMS收集器失敗后的備用收集器。

     6.CMS 又稱響應時間優先回收器,使用標記清除算法。他的回收線程數為(CPU核心數+3)/4,所以當CPU核心數為2時比較高效些。CMS分為4個過程:初始標記、并發標記、重新標記、并發清除。

     7.GarbageFirst(G1) 。比較特殊的是G1回收器既可以回收Young Generation,也可以回收Tenured Generation。它是在JDK6的某個版本中才引入的,性能比較高,同時注意了吞吐量和響應時間。

     對于垃圾收集器的組合使用可以通過下表中的參數指定:

JVM 相關知識整理和學習

     默認的GC種類可以通過jvm.cfg或者通過jmap dump出heap來查看,一般我們通過jstat -gcutil [pid] 1000可以查看每秒gc的大體情況,或者可以在啟動參數中加入:-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:./gc.log來記錄GC日志。

     GC中有一種情況叫做Full GC,以下幾種情況會觸發Full GC:

     1.Tenured Space空間不足以創建打的對象或者數組,會執行FullGC,并且當FullGC之后空間如果還不夠,那么會OOM:java heap space。

     2.Permanet Generation的大小不足,存放了太多的類信息,在非CMS情況下回觸發FullGC。如果之后空間還不夠,會OOM:PermGen space。

     3.CMS GC時出現promotion failed和concurrent mode failure時,也會觸發FullGC。promotion failed是在進行Minor GC時,survivor space放不下、對象只能放入舊生代,而此時舊生代也放不下造成的;concurrent mode failure是在執行CMS GC的過程中同時有對象要放入舊生代,而此時舊生代空間不足造成的。

     4.判斷MinorGC后,要晉升到TenuredSpace的對象大小大于TenuredSpace的大小,也會觸發FullGC。

     可以看出,當FullGC頻繁發生時,一定是內存出問題了。

三、JVM的數據格式規范和Class文件

     1.數據類型規范

     依據馮諾依曼的計算機理論,計算機最后處理的都是二進制的數,而JVM是怎么把java文件最后轉化成了各個平臺都可以識別的二進制呢?JVM自己定義了 一個抽象的存儲數據單位,叫做Word。一個字足夠大以持有byte、char、short、int、float、reference或者 returnAdress的一個值,兩個字則足夠持有更大的類型long、double。它通常是主機平臺一個指針的大小,如32位的平臺上,字是32 位。

     同時JVM中定義了它所支持的基本數據類型,包括兩部分:數值類型和returnAddress類型。數值類型分為整形和浮點型。

     整形:

byte
值是8位的有符號二進制補碼整數
short

值是16位的有符號二進制補碼整數

int

值是32位的有符號二進制補碼整數

long

值是64位的有符號二進制補碼整數

char

值是表示Unicode字符的16位無符號整數

     浮點:
float

值是32位IEEE754浮點數

double
值是64位IEEE754浮點數
     returnAddress類型的值是Java虛擬機指令的操作碼的指針。

     對比java的基本數據類型,jvm的規范中沒有boolean類型。這是因為jvm中堆boolean的操作是通過int類型來進行處理的,而boolean數組則是通過byte數組來進行處理。

     至于String,我們知道它存儲在常量池中,但他不是基本數據類型,之所以可以存在常量池中,是因為這是JVM的一種規定。如果查看String源碼,我們就會發現,String其實就是一個基于基本數據類型char的數組。如圖:

JVM 相關知識整理和學習

     2.字節碼文件

     通過字節碼文件的格式我們可以看出jvm是如何規范數據類型的。下面是ClassFile的結構:

JVM 相關知識整理和學習

     關于各個字段的定義(參考自JVM Specification 和 博文: http://www.cnblogs.com/zhuYears/archive/2012/02/07/2340347.html ),

magic:

魔數,魔數的唯一作用是確定這個文件是否為一個能被虛擬機所接受的Class文件。魔數值固定為0xCAFEBABE,不會改變。

minor_version、major_version:

分別為Class文件的副版本和主版本。它們共同構成了Class文件的格式版本號。不同版本的虛擬機實現支持的Class文件版本號也相應不同,高版本號的虛擬機可以支持低版本的Class文件,反之則不成立。

constant_pool_count:

常量池計數器,constant_pool_count的值等于constant_pool表中的成員數加1。

constant_pool[]:

常量池,constant_pool是一種表結構,它包含Class文件結構及其子結構中引用的所有字符串常量、類或接口名、字段名和其它常量。常量池不同于其他,索引從1開始到constant_pool_count -1。

access_flags:

訪問標志,access_flags是一種掩碼標志,用于表示某個類或者接口的訪問權限及基礎屬性。access_flags的取值范圍和相應含義見下表:

JVM 相關知識整理和學習

this_class:

類索引,this_class的值必須是對constant_pool表中項目的一個有效索引值。constant_pool表在這個索引處的項必須為CONSTANT_Class_info類型常量,表示這個Class文件所定義的類或接口。

super_class:

父類索引,對于類來說,super_class的值必須為0或者是對constant_pool表中項目的一個有效索引值。如果它的值不為0,那 constant_pool表在這個索引處的項必須為CONSTANT_Class_info類型常量,表示這個Class文件所定義的類的直接父類。當 然,如果某個類super_class的值是0,那么它必定是java.lang.Object類,因為只有它是沒有父類的。

interfaces_count:

接口計數器,interfaces_count的值表示當前類或接口的直接父接口數量。

interfaces[]:

接口表,interfaces[]數組中的每個成員的值必須是一個對constant_pool表中項目的一個有效索引值,它的長度為interfaces_count。每個成員interfaces[i] 必須為CONSTANT_Class_info類型常量。

fields_count:

字段計數器,fields_count的值表示當前Class文件fields[]數組的成員個數。

fields[]:

字段表,fields[]數組中的每個成員都必須是一個fields_info結構的數據項,用于表示當前類或接口中某個字段的完整描述。

methods_count:

方法計數器,methods_count的值表示當前Class文件methods[]數組的成員個數。

methods[]:

方法表,methods[]數組中的每個成員都必須是一個method_info結構的數據項,用于表示當前類或接口中某個方法的完整描述。

attributes_count:

屬性計數器,attributes_count的值表示當前Class文件attributes表的成員個數。

attributes[]:

屬性表,attributes表的每個項的值必須是attribute_info結構。

四、一個java類的實例分析

     為了了解JVM的數據類型規范和內存分配的大體情況,我新建了MemeryTest.java:

JVM 相關知識整理和學習

     編譯為 MemeryTest.class后 通過WinHex查看該文件,對應字節碼文件各個部分不同的定義,我了解了下面16進制數值的具體含義,盡管不清楚ClassLoader的具體實現邏輯,但是可以想象這樣一個嚴謹格式的文件給JVM對于內存管理和執行程序提供了多大的幫助。

JVM 相關知識整理和學習

運行程序后,我在windows資源管理器中找到對應的進程ID.

JVM 相關知識整理和學習

并且在控制臺通過jmap -heap 10016查看堆內存的使用情況:

JVM 相關知識整理和學習

     輸出結果中表示當前java進程啟動的JVM是通過4個線程進行Parallel GC,堆的最小 FreeRatio 是40%,堆的最大FreeRatio是70%,堆的大小是4090M,新對象占用1.5M,Young Generation可以擴展到最大是1363M, Tenured Generation的大小是254.5M,以及NewRadio和SurvivorRadio中,下面更是具體給出了目前Young Generation中1.5M的劃分情況,Eden占用1.0M,使用了5.4%,Space占了0.5M,使用了93%,To Space占了0.5M,使用了0%。

     下面我們通過jmap dump把heap的內容打印打文件中:

JVM 相關知識整理和學習

     使用Eclipse的MAT插件打開對應的文件:

JVM 相關知識整理和學習

     選擇第一項內存泄露分析報告打開test.bin文件,展示出來的是MAT關于內存可能泄露的分析。

JVM 相關知識整理和學習

JVM 相關知識整理和學習

     從結果來看,有3個地方可能存在內存泄露,他們占據了Heap的22.10%,13.78%,14.69%,如果內存泄露,這里一般會有一個比值非常高的對象。打開第一個Probem Suspect,結果如下:

JVM 相關知識整理和學習

     ShallowHeap是對象本身占用的堆大小,不包含引用,RetainedHeap是對象所持有的Shallowheap的大小,包括自己 ShallowHeap和可以引用的對象的ShallowHeap。垃圾回收的時候,如果一個對象不再引用后被回收,那么他的RetainedHeap是 能回收的內存總和。通過上圖可以看出程序中并沒有什么內存泄露,可以放心了。如果還有什么不太確定的對象,則可以通過多個時間點的 HeapDumpFile來研究某個對象的變化情況。

五、小結

     以上便是我最近幾天對JVM相關資料的整理,主要圍繞他的基本組成和運行原理等,內存管理,節本數據類型和字節碼文件。JVM是一個非常優秀的JAVA程序,也是個不錯的規范,這次整理學習讓我對他有了更加清晰的認知,對Java語言的理解也更加加深。 

     這次學習過程,堅定了我對程序員發展的認知。知識一定要精,下一步我將邊工作邊仔細閱讀Oracle的3個版本的《JVM Specification》,并且結合實踐讓自己的Java基礎素養更上一個層次。( http://docs.oracle.com/javase/specs/ )

 本文由用戶 dmc3 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!