1. 程式人生 > >全面解讀java虛擬機器(面試考點大全)

全面解讀java虛擬機器(面試考點大全)

 學習java以來,jvm的原理已經看過好多遍了,但是許多知識點都串不起來。今天我把jvm相關知識整理了一下,看完之後肯定會對JVM非常的清楚。


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有多重要,而實際專案中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系統上執行命令的截圖,





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


2.JVM的組成


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


我從CSDN找了一張描述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的描述:



可以看出它把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可以用下圖描述它的組成:


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 ):








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區(在沒有Code Cache的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是即時編譯器,他是為了提高指令的執行效率,把位元組碼檔案編譯成本地機器程式碼,如下圖:



引用一個經典的案例來理解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,這裡有個關於兩者效能對比的文章
。 所以,仁者見仁,智者見智,一個饅頭足以引發血案,就算是同一個商家的JVM,畢竟JDK版本在更新,或許正如StackOverFlow上大神們所說,對於理解JVM Runtime Data Area這一部分的劃分邏輯,還是去看對應版本的JDK原始碼比較靠譜,或者是參考不同的版本JVM Specification


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。


我針對棧進行了實驗,由於遞迴的呼叫可以致使棧的引用增加,導致溢位,所以設計程式碼如下:



我的機器是x86_64系統,所以Stack的預設大小是128KB,上述程式在執行時會報錯:





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





另外棧上有一點得注意的是,對於原生代碼呼叫,可能會在棧中申請記憶體,比如C呼叫malloc(),而這種情況下,GC是管不著的,需要我們在程式中,手動管理棧記憶體,使用free()方法釋放記憶體


2.堆的管理


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



上圖是 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區的比值。(參考自博文:)


堆異常分為兩種,一種是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 ROOT節點主要在全域性性的引用(例如常量或靜態屬性)與執行上下文(例如棧幀中的本地變量表)中),向下搜尋,如果一個物件不能達到GC Roots物件的時候,說明它可以被回收了。這種演算法比一種叫做引用計數法的垃圾標記演算法要好,因為它避免了當兩個物件啊互相引用時無法被回收的現象。


注意:1. 如果在節點搜尋中從ROOT不能到達這個物件,並不是一定會被回收,因為JVM給了這些物件第二次機會,這些物件會被第一次標記(“緩刑”)並且會進行一次篩選,篩選條件就是此物件是否有必要執行finalize()方法,(當物件覆蓋finalized方法或者已經被執行過一次,都視為沒必要執行finalize),通過篩選的物件放入F-Queue佇列,低優先順序的finalize執行緒會執行這個方法,這裡的“執行”是說虛擬機器會觸發這個方法,但並不承諾會等到這個方法執行完成,因為finalize方法中可能有死迴圈,如果在這次執行中,能將自己拯救(將自身(this)與引用鏈上的任何一個物件關聯即可(比如吧自己的this賦值給某個類變數或者成員變數)),那麼JVM在進行第二次標記的時候就會將他移除即將回收的集合。


2. Elden沒有足夠的記憶體時會MInor GC,可以通過 -XX:PreteureSizeThreshold引數設定當物件 >=這個值時,會直接放入老年代。


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


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


2.複製演算法,該演算法是從根集合掃描,並將存活的物件複製到新的空間,這種演算法在存活物件少時比較高效。(適合新生代每次生存的物件很少)


3.標記整理演算法,標記整理演算法和標記清除演算法一樣都會掃描並標記存活物件,在回收未標記物件的同時會整理被標記的物件,解決了記憶體碎片的問題,(適合老年代:沒有過多記憶體)


4.分代收集。


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的某個版本中才引入的,效能比較高,同時注意了吞吐量和響應時間。


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



預設的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位無符號整數 ,注意Java中
是Unicode字元,佔兩個位元組  ,ASCii佔8位但是他沒有中文字元


浮點:

float 值是32位IEEE754浮點數
double 值是64位IEEE754浮點數 


returnAddress型別的值是Java虛擬機器指令的操作碼的指標。


對比java的基本資料型別,jvm的規範中沒有boolean型別。這是因為jvm中對boolean的操作是通過int型別來進行處理的,而boolean陣列則是通過byte陣列來進行處理。


至於String,我們知道它儲存在常量池中,但他不是基本資料型別,之所以可以存在常量池中,是因為這是JVM的一種規定。如果檢視String原始碼,我們就會發現,String其實就是一個基於基本資料型別char的陣列。如圖:






2.位元組碼檔案


通過位元組碼檔案的格式我們可以看出jvm是如何規範資料型別的。下面是ClassFile的結構:



關於各個欄位的定義(參考自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的取值範圍和相應含義見下表:



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:



編譯為MemeryTest.class後,通過WinHex檢視該檔案,對應位元組碼檔案各個部分不同的定義,我瞭解了下面16進位制數值的具體含義,儘管不清楚ClassLoader的具體實現邏輯,但是可以想象這樣一個嚴謹格式的檔案給JVM對於記憶體管理和執行程式提供了多大的幫助。





執行程式後,我在windows資源管理器中找到對應的程序ID.





並且在控制檯通過jmap
-heap 10016檢視堆記憶體的使用情況:






輸出結果中表示當前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的內容列印打檔案中:



使用Eclipse的MAT外掛開啟對應的檔案:





選擇第一項記憶體洩露分析報告開啟test.bin檔案,展示出來的是MAT關於記憶體可能洩露的分析。





從結果來看,有3個地方可能存在記憶體洩露,他們佔據了Heap的22.10%,13.78%,14.69%,如果記憶體洩露,這裡一般會有一個比值非常高的物件。開啟第一個Probem
Suspect,結果如下:






ShallowHeap是物件本身佔用的堆大小,不包含引用,RetainedHeap是物件所持有的Shallowheap的大小,包括自己ShallowHeap和可以引用的物件的ShallowHeap。垃圾回收的時候,如果一個物件不再引用後被回收,那麼他的RetainedHeap是能回收的記憶體總和。通過上圖可以看出程式中並沒有什麼記憶體洩露,可以放心了。如果還有什麼不太確定的物件,則可以通過多個時間點的HeapDumpFile來研究某個物件的變化情況。


五、小結


以上便是我最近幾天對JVM相關資料的整理,主要圍繞他的基本組成和執行原理等,記憶體管理,節本資料型別和位元組碼檔案。JVM是一個非常優秀的JAVA程式,也是個不錯的規範,這次整理學習讓我對他有了更加清晰的認知,對Java語言的理解也更加加深。

這裡補充一點 :java的過載與多型其實是與虛擬機器相關的,過載是靜態分派(編譯時決定執行哪個方法),多型是動態分派(執行時決定執行哪個方法)。


下面給出過載程式碼:

[java] view plain copy print?
  1. publicclass JVM {  
  2.     staticabstractclass A{  
  3.     }  
  4.     staticclass B extends A{  
  5.     }  
  6.     staticclass C extends A{  
  7.     }//去掉此方法 會編譯出錯
  8.     publicvoid say(A a){  
  9.         System.out.println("a");  
  10.     }  
  11.     publicvoid say(B b){  
  12.         System.out.println("b");  
  13.     }  
  14.     publicvoid say(C c){  
  15.         System.out.println("c");  
  16.     }  
  17.     publicstaticvoid main(String args[]){  
  18.         JVM jvm = new JVM();  
  19.         A b = new B();  
  20.         A c = new C();  
  21.         jvm.say(b);  
  22.         jvm.say(c);  
  23.     }  
public class JVM {
    static abstract  class A{
    	
    }
    static class B extends A{
    	
    }
    static class C extends A{
    	
    }//去掉此方法 會編譯出錯
    public void say(A a){
    	System.out.println("a");
    }
    public void say(B b){
    	System.out.println("b");
    }
    public void say(C c){
    	System.out.println("c");
    }
    public static void main(String args[]){
    	JVM jvm = new JVM();
        A b = new B();
        A c = new C();
        jvm.say(b);
        jvm.say(c);
    }
輸出: [plain] view plain copy print?
  1. a  
  2. a  
a
a
當上面修改為: [java] view plain copy print?
  1. jvm.say((B)b);時 則輸出結果為b,  
jvm.say((B)b);時 則輸出結果為b,
呼叫哪個方法是在編譯時就確定的。
對於基本型別的過載級別:


char->int->long->float->double
char和byte short是同一級別,型別轉換不安全。


比如char的過載
會先看1.char的引數 2.int 3.long 4.float 5.double 6.包裝器型別(character) 7.Serializable或者Comparable介面,他倆優先順序一樣,當同時存在時會提示型別模糊,拒絕編譯。8.Object 9. char.. 可變引數型別

相關推薦

全面解讀java虛擬機器面試考點大全

 學習java以來,jvm的原理已經看過好多遍了,但是許多知識點都串不起來。今天我把jvm相關知識整理了一下,看完之後肯定會對JVM非常的清楚。 JVM是虛擬機器,也是一種規範,他遵循著馮·諾依曼體系結構的設計原理。馮·諾依曼體系結構中,指出計算機處理的資料和指令都

深入理解Java虛擬機器類檔案結構

深入理解Java虛擬機器(類檔案結構) 歡迎關注微信公眾號:BaronTalk,獲取更多精彩好文! 之前在閱讀 ASM 文件時,對於已編譯類的結構、方法描述符、訪問標誌、ACC_PUBLIC、ACC_PRIVATE、各種位元組碼指令等等許多概念聽起來都是雲山霧罩、一知半解,原因就在於對類檔案結構和

面試中關於Java虛擬機器jvm的問題看這篇就夠了

最近看書的過程中整理了一些面試題,面試題以及答案都在我的文章中有所提到,希望你能在以問題為導向的過程中掌握虛擬機器的核心知識。面試畢竟是面試,核心知識我們還是要掌握的,加油。 下面是按jvm虛擬機器知識點分章節總結的一些jvm學習與面試相關的一些東西。一般作為Java程式設

Java虛擬機器JVM原始碼:JDK10對Java虛擬機器執行時資料區的劃分詳細圖解

Java虛擬機器執行時資料區 為什麼要研究這個,因為JDK都已經發布到10了,必須要更新自己對Java虛擬機器新的認識。 一、執行時資料區的劃分 1.1 官方劃分 關於JDK10對執行時資料區的劃分,在官方文件說的非常清楚。 學習技術,一定要學會看第一手資料。 Ja

Java虛擬機器JVM原始碼:搭建OpenJDK(10)原始碼除錯環境

為什麼要自己除錯 在前面的文章,已經介紹了如何編譯自己的OpenJDK。 但是光擁有了自己的JDK版本肯定是不夠的。 為了深入瞭解Java例項的建立、初始化和執行流程以及內部實現原理,DEBUG是必不可少的必殺技。 所以,作為搞技術的有必要學習如何除錯JVM原始碼。

Java虛擬機器JVM原始碼:編譯OpenJDK原始碼

為什麼要自己編譯JDK原始碼 作為一個搞技術的同學,如果想在技術這條路上走下去,還是多研究些本質性的東西,修煉下內功。尤其是現在JDK都出到10了,如果你沒有研究過,還是停留在之前的時代,那麼確實說不過去。做技術還是得有追求。 一、JDK和OpenJDK的異同點 關於兩者之間

Java程式設計師從笨鳥到菜鳥之九十三深入java虛擬機器——類載入器詳解

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

java虛擬機器JVM

1.jvm虛擬機器概述和基本概念  (虛擬機器分為系統虛擬機器-》(VirtualBox   VMware ==)  和 程式虛擬機器-》(JVM  DVM == )   )    1.1什麼是jvm      &n

深入理解java虛擬機器位元組碼指令簡介

Java虛擬機器指令是由(佔用一個位元組長度、代表某種特定操作含義的數字)操作碼Opcode,以及跟隨在其後的零至多個代表此操作所需引數的稱為運算元 Operands 構成的。由於Java虛擬機器是面向運算元棧而不是暫存器的架構,所以大多數指令都只有操作碼,而沒有運算元。 位元組碼指令集是一種具有鮮明特點、

「深入Java虛擬機器6」:Java語法糖

語法糖(Syntactic Sugar),也稱糖衣語法,是由英國計算機學家Peter.J.Landin發明的一個術語,指在計算機語言中新增的某種語法,這種語法對語言的功能並沒有影響,但是更方便程式設計師使用。Java中最常用的語法糖主要有泛型、變長引數、條件編譯、自動拆裝箱、內部類等。虛擬機器並不支援這些語法

JAVA虛擬機器虛擬機器類載入機制

虛擬機器的類載入機制是指 把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗,轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別。類的載入連線和初始化過程都是在程式執行期間完成的。 類的生命週期: 載入->連線(驗證,準備,解析)->初始化->使用

Java程式設計師從笨鳥到菜鳥之九十四深入java虛擬機器——類的生命週期 下類的初始化

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

深入理解java虛擬機器java虛擬機器的記憶體區域

一、 java虛擬機器記憶體區域主要有:方法區、堆、虛擬機器棧、本地方方法棧、程式計數器     按照執行緒私有和共有來分:執行緒私有的有--程式計數器,虛擬機器棧,本地方法棧。共有的有--本地方法區,堆     1、程式計數器:主要功能是控制程式

JAVA虛擬機器JVM劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 虛擬機器物件

本部落格參考《深入理解Java虛擬機器》(第二版)一書,提取重點知識,再加以個人的理解編寫而成。轉載請標明來源。 JAVA虛擬機器(JVM)劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 虛擬機器物件 Java物件的建立 1、類載入過程

JAVA虛擬機器JVM劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 執行時資料區域

本部落格參考《深入理解Java虛擬機器》(第二版)一書,提取重點知識,再加以個人的理解編寫而成。轉載請標明來源。 JVM劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 執行時資料區域 概述 執行時資料區域 程式計數器 Java虛擬機

深入理解Java虛擬機器5Java記憶體模型

深入理解Java虛擬機器(5)Java記憶體模型 Java記憶體模型 主記憶體和工作記憶體 volatile關鍵字 long與double型別的特殊規則 synchronized關鍵字 Java記憶體模

深入理解Java虛擬機器類檔案結構+類載入機制+位元組碼執行引擎

周志明的《深入理解Java虛擬機器》很好很強大,閱讀起來頗有點費勁,尤其是當你跟隨作者的思路一直探究下去,開始會讓你弄不清方向,難免有些你說的啥子的感覺。但知識不得不學,於是天天看,反覆看,就慢慢的理解了。我其實不想說這種硬磨的方法有多好,我甚至不推薦,我建議大家閱讀這本書時,由淺入深,有舍有得,先從巨集觀去

java虛擬機器

1、java如何不關注底層技術細節實現相容性? 使用中間語言 ,通過中間語言實現跨平臺相容的目標。 2、中間語言不是本地機器指令,機器cpu無法直接識別,因此中間語言不能直接在物理cpu上直接執行,怎麼解決? 用虛擬機器來解釋中間語言,將中間語言翻譯成對應的本地機器指令。 3

深入理解java虛擬機器java的記憶體區域

程式計數器:可以看作當前執行緒所執行的位元組碼的行號指示器,位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條 需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來實現。每一個執行緒都有一個獨立的程式計數器,各個執行緒之間的計數器互不影響,獨立

什麼是java虛擬機器JVM

什麼是java虛擬機器(JVM)? Java虛擬機器是一個可以執行Java位元組碼的虛擬機器程序。Java原始檔被編譯成能被Java虛擬機器執行的位元組碼檔案。 為什麼Java可以實現所謂的“一次編寫,到處執行”,主要是因為虛擬機器的存在。Java虛擬機器負責Java程式設計語言