這篇文章主要為大家展示了“java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的示例分析”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的示例分析”這篇文章吧。
站在用戶的角度思考問(wèn)題,與客戶深入溝通,找到饒平網(wǎng)站設(shè)計(jì)與饒平網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類(lèi)型包括:成都做網(wǎng)站、成都網(wǎng)站建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、國(guó)際域名空間、雅安服務(wù)器托管、企業(yè)郵箱。業(yè)務(wù)覆蓋饒平地區(qū)。JVMmemorymodel
在JVM規(guī)范中描述的運(yùn)行時(shí)數(shù)據(jù)區(qū)(RuntimeDataAreas)。這些區(qū)域設(shè)計(jì)用來(lái)存儲(chǔ)被JVM自身或者在JVM上運(yùn)行的程序所是用的數(shù)據(jù)。
我們先總覽JVM,然后介紹下字節(jié)碼,最后介紹不同的數(shù)據(jù)區(qū)域。
總覽
JVM作為操作系統(tǒng)的抽象,保證同樣的代碼在不同的硬件或操作系統(tǒng)上的行為一致。
比如:
對(duì)于基本類(lèi)型int,無(wú)論在16位/32位/64位操作系統(tǒng)上,都是一個(gè)32位有符號(hào)整數(shù)。范圍從-2^31到2^31-1
無(wú)論操作系統(tǒng)或者硬件是大字節(jié)序還是小字節(jié)序,保證JVM存儲(chǔ)和使用的內(nèi)存中的數(shù)據(jù)都是大字節(jié)序(先讀高位字節(jié))
不同的JVM實(shí)現(xiàn)可能會(huì)有些區(qū)別,但大體上是相同的。
上圖是一個(gè)JVM的總覽
JVM解釋編譯器生成的字節(jié)碼。雖然JVM是Java虛擬機(jī)的縮寫(xiě),但是只要是能夠編譯為字節(jié)碼的語(yǔ)言,都可以基于JVM運(yùn)行,比如 scala、groovy<喎?"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPs6qwcux3MPixrW3sbXEtMXFzEkvT6Os19a92sLru+Gxu2NsYXNzbG9hZGVyvNPU2LKiu7q05rW91MvQ0Mqxyv2+3cf41tC1xNK7uPbH+NPyo6zWqrXAvNPU2Mv8tcRjbGFzc2xvYWRlcrG7z/q72bvy1d9KVk3No9a51MvQ0KGjPC9wPg0KPHA+vNPU2LXE19a92sLrzai5/da00NDS/cfmKGV4ZWN1dGlvbiBlbmdpbmUpvfjQ0L3iys26zda00NA8L3A+DQo8cD7WtNDQ0v3H5tDo0qq05rSis8zQ8snPz8LOxKOsscjI57PM0PLWtNDQtb3ExNK70NCjrLvy1d/K/b7dvMbL47XE1tC85L3hufs8L3A+DQo8cD7WtNDQ0v3H5tKyuLrU8LSmwO3T67XXsuOy2df3z7XNs7XEvbu7pTwvcD4NCjxwPioquty24EpWTba8yrXP1sHLvLTKsbHg0uu5psTcKEpJVD1qdXN0IGluIHRpbWUpoaNKSVS+zcrHsNG+rbOj1rTQ0LXEtPrC6yjIyLXjtPrC6ymx4NLrs8mxvrXYtPrC6yhOYXRpdmUgQ29kZSmho7Tmt8VKSVSx4NLryfqzybT6wuu1xMf40/Kzxs6qPC9wPg0KPHA+tPrC67u6tObH+ChDb2RlIENhY2gpoaO8tMqxseDS67y8yvUoSklUKby2tPO1xMzhuN/By0pWTbXE0NTE3CoqPC9wPg0KPGgyIGlkPQ=="基于棧stack的架構(gòu)">基于棧(stack)的架構(gòu)
JVM使用基于棧的架構(gòu)。雖然棧對(duì)于開(kāi)發(fā)者是透明的,但是棧對(duì)于生成的字節(jié)碼和JVM都有很重要的作用或者說(shuō)影響。
我們開(kāi)發(fā)的程序,會(huì)轉(zhuǎn)換位低級(jí)別的操作,存于字節(jié)碼中。在JVM中通過(guò)操作數(shù)(operand)映射到操作指令。按照J(rèn)VM規(guī)范,操作指令需要的參數(shù)是從操作數(shù)棧獲得的(the operand stack)。
舉個(gè)兩個(gè)數(shù)相加的例子。這這個(gè)操作稱(chēng)為 iadd 。下面是在字節(jié)碼中 3+4 的過(guò)程
首先把3和4壓入操作數(shù)棧
調(diào)用 iadd 指令
iadd 指令會(huì)從操作數(shù)棧頂彈出2個(gè)數(shù)
3+4的結(jié)果壓入操作數(shù)棧,供后面使用
這種方式被稱(chēng)為基于棧的架構(gòu)。還有其他的方式可以處理低級(jí)別操作,比如基于寄存器的架構(gòu)(register based architecture)。
字節(jié)碼
java字節(jié)碼是java源碼轉(zhuǎn)換為一系列低級(jí)別操作的結(jié)果。每個(gè)操作由一個(gè)字節(jié)長(zhǎng)度的操作碼(opcode or operation code)和零或多個(gè)字節(jié)長(zhǎng)度的參數(shù)(但是大多數(shù)操作使用的參數(shù)都是通過(guò)操作數(shù)棧獲取的)組成。一個(gè)字節(jié)可以表示256個(gè)數(shù),從0x00到0xff,目前到j(luò)ava8,共使用了204個(gè)。
下面列出不同種類(lèi)的字節(jié)碼操作碼以及其范圍和簡(jiǎn)單的描述
Constants: 將常量池的值或者已知的值壓入操作數(shù)棧。 0x00 - 0x14
Loads: 將局部變量值壓入操作數(shù)棧。 0x15 - 0x35
Stores: 從操作數(shù)棧加載值賦給局部變量 0x36 - 0x56
Stack: 處理操作數(shù)棧 0x57 - 0x5f
Math: 從操作數(shù)棧獲取值進(jìn)行基本的數(shù)學(xué)計(jì)算 0x60 - 0x84
Conversions: 進(jìn)行類(lèi)型之間的轉(zhuǎn)換 0x85 - 0x 93
Comaprisons: 兩個(gè)值的比較操作 0x94 - 0xa6
Controls: 執(zhí)行g(shù)oto、return、循環(huán)等等控制操作 0xa7 - 0xb1
References: 執(zhí)行分配對(duì)象或數(shù)組,獲取或檢查 對(duì)象、方法、靜態(tài)方法的引用。也可以調(diào)用靜態(tài)方法。 0xb2 - oxc3
Extended: Extended: operations from the others categories that were added after. From value 0xc4 to 0xc9
(這句說(shuō)不好什么意思。。。)
Reserved: JVM實(shí)現(xiàn)內(nèi)部是用的槽子0xca,oxfe,oxff
這204個(gè)操作都很簡(jiǎn)單,舉幾個(gè)例子
ifeq(0x99) 判斷兩個(gè)值是否相等
iadd(0x60) 把兩個(gè)數(shù)相加
i2l (0x85) 把一個(gè)int 轉(zhuǎn)換位 long
arraylength (0xbe) 返回?cái)?shù)組長(zhǎng)度
pop (0x57) 從操作數(shù)棧頂彈出一個(gè)值
我們需要編譯器來(lái)創(chuàng)建字節(jié)碼文件,標(biāo)準(zhǔn)的java編譯器就是jdk中的 javac。
public class Test { public static void main(String[] args) { int a =1; int b = 15; int result = add(a,b); } public static int add(int a, int b){ int result = a + b; return result; } }
通過(guò)“javac Test.java” 可以得到 “Test.class”的字節(jié)碼文件。字節(jié)碼文件是2進(jìn)制的,我們可以通過(guò)javap,把二進(jìn)制的字節(jié)碼文件轉(zhuǎn)換成文本形式
java -verbose Test.class
Classfile /C:/TMP/Test.class Last modified 1 avr. 2015; size 367 bytes MD5 checksum adb9ff75f12fc6ce1cdde22a9c4c7426 Compiled from "Test.java" public class com.codinggeek.jvm.Test SourceFile: "Test.java" minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #4.#15 // java/lang/Object."":()V #2 = Methodref #3.#16 // com/codinggeek/jvm/Test.add:(II)I #3 = Class #17 // com/codinggeek/jvm/Test #4 = Class #18 // java/lang/Object #5 = Utf8 #6 = Utf8 ()V #7 = Utf8 Code #8 = Utf8 LineNumberTable #9 = Utf8 main #10 = Utf8 ([Ljava/lang/String;)V #11 = Utf8 add #12 = Utf8 (II)I #13 = Utf8 SourceFile #14 = Utf8 Test.java #15 = NameAndType #5:#6 // " ":()V #16 = NameAndType #11:#12 // add:(II)I #17 = Utf8 com/codinggeek/jvm/Test #18 = Utf8 java/lang/Object { public com.codinggeek.jvm.Test(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object." ":()V 4: return LineNumberTable: line 3: 0 public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: iconst_1 1: istore_1 2: bipush 15 4: istore_2 5: iload_1 6: iload_2 7: invokestatic #2 // Method add:(II)I 10: istore_3 11: return LineNumberTable: line 6: 0 line 7: 2 line 8: 5 line 9: 11 public static int add(int, int); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=2 0: iload_0 1: iload_1 2: iadd 3: istore_2 4: iload_2 5: ireturn LineNumberTable: line 12: 0 line 13: 4 }
可以看出字節(jié)碼不只是java代碼的簡(jiǎn)單翻譯,它包括:
類(lèi)的常量池(cosntant pool)描述。常量池是用于存儲(chǔ)類(lèi)元數(shù)據(jù)的JVM數(shù)據(jù)區(qū)域,比如類(lèi)內(nèi)部的方法名,參數(shù)列表,等等。當(dāng)JVM加載一個(gè)類(lèi)的時(shí)候,這些元數(shù)據(jù)就會(huì)加載到常量池
通過(guò)行號(hào)表和或局部變量表來(lái)提供函數(shù)和天貓的變量在字節(jié)碼中的具體位置信息
java代碼的翻譯(包括隱藏的父類(lèi)構(gòu)造)
提供更具體的對(duì)于操作數(shù)棧的操作和更完整的傳遞和獲取參數(shù)的方式
下面是一個(gè)簡(jiǎn)單的字節(jié)碼文件存儲(chǔ)信息的描述
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
運(yùn)行時(shí)數(shù)據(jù)區(qū)
運(yùn)行時(shí)數(shù)據(jù)區(qū)是存儲(chǔ)數(shù)據(jù)的內(nèi)存區(qū)域設(shè)計(jì)。這些數(shù)據(jù)供開(kāi)發(fā)者或JVM內(nèi)部使用。
堆(Heap)
堆 在JVM啟動(dòng)的時(shí)候創(chuàng)建,由所有的JVM線程所共享。所有的類(lèi)實(shí)例、數(shù)組都分配到堆(由new所創(chuàng)建的)。
堆必須由一個(gè)垃圾收集器來(lái)管理,垃圾收集器負(fù)責(zé)釋放被開(kāi)發(fā)者所創(chuàng)建,并且不會(huì)再被使用到的對(duì)象。
至于垃圾收集的策略由JVM實(shí)現(xiàn)決定(比如HotSpot提供了多種算法).
堆內(nèi)存有一個(gè)大值限制,如果超過(guò)這個(gè)值 JVM會(huì)拋出一個(gè) OutOfMemroy異常
方法區(qū)(Method area)
方法區(qū)也是被JVM的所有線程所共享。同樣的隨JVM啟動(dòng)被創(chuàng)建。方法區(qū)存儲(chǔ)的數(shù)據(jù)由classloader從字節(jié)碼中加載,這些數(shù)據(jù)會(huì)在應(yīng)用運(yùn)行過(guò)程中一致存在,除非加載它們的classloader被銷(xiāo)毀或者JVM停止。
方法區(qū)存儲(chǔ)如下數(shù)據(jù):
類(lèi)信息(屬性名、方法名、父類(lèi)名、借口名、版本、等等)
方法和構(gòu)造的字節(jié)碼
加載每個(gè)類(lèi)時(shí)創(chuàng)建的運(yùn)行時(shí)常量池
JVM規(guī)范并不強(qiáng)迫在堆中實(shí)現(xiàn)方法區(qū)。在java7以前,HotSpot 使用一個(gè)稱(chēng)為永久帶(PermGen)的區(qū)域?qū)崿F(xiàn)方法區(qū)。永久帶與堆相鄰(和堆一樣進(jìn)行內(nèi)存管理),默認(rèn)位64MB
從java8開(kāi)始,HptSpot使用分離的本地內(nèi)存實(shí)現(xiàn)方法區(qū),起名元數(shù)據(jù)區(qū)(Metaspace)。元數(shù)據(jù)區(qū)大可用空間即整個(gè)系統(tǒng)的可用內(nèi)存。
如果方法去申請(qǐng)不到可用內(nèi)存,JVM也會(huì)拋出OutOfMemoryError.
運(yùn)行時(shí)常量池(Runtime constant pool)
運(yùn)行時(shí)常量池是方法區(qū)的一部分。因?yàn)檫\(yùn)行吃常量池對(duì)于元數(shù)據(jù)的重要性,java規(guī)范中在方法區(qū)之外單獨(dú)對(duì)其進(jìn)行了描述。運(yùn)行時(shí)常量池會(huì)隨著加載的類(lèi)和接口而增長(zhǎng)。
常量池有點(diǎn)想傳統(tǒng)語(yǔ)言中的語(yǔ)法表。換句話說(shuō),當(dāng)調(diào)用一個(gè)類(lèi)、方法或?qū)傩詴r(shí),JVM通過(guò)運(yùn)行時(shí)常量池來(lái)尋找這些數(shù)據(jù)在內(nèi)存中的真實(shí)地址。運(yùn)行時(shí)常量池也包含字符串字面值或基本類(lèi)型的常量
Stirng myString="This is a string litteral" static final int MY_CONSTANT = 2 ;
pc(程序計(jì)數(shù)器)寄存器(每個(gè)線程) The pc Register (Per Thread)
每個(gè)線程有自己的pc(程序計(jì)數(shù)器)寄存器,與線程創(chuàng)建是一同創(chuàng)建。每個(gè)線程在一個(gè)時(shí)間點(diǎn)上只能執(zhí)行一個(gè)方法,稱(chēng)為該線程的當(dāng)前方法(current method)。pc寄存器包含JVM當(dāng)前在執(zhí)行指令(在方法區(qū))的地址。
如果當(dāng)前執(zhí)行的方法是本地方法(native),pc寄存器的值是undefined
虛擬機(jī)棧每個(gè)線程-java-virtual-machine-stacks-per-thread">虛擬機(jī)棧(每個(gè)線程) java virtual machine stacks (per thread)
虛擬機(jī)棧存儲(chǔ)多個(gè)幀,因此在描述棧前,我們先來(lái)看下幀
幀(frames)
幀是一個(gè)數(shù)據(jù)結(jié)構(gòu),幀包含表示線程正在執(zhí)行的當(dāng)前方法狀態(tài)的多個(gè)數(shù)據(jù):
操作數(shù)棧(Operand Stack): 之前已經(jīng)提到過(guò),字節(jié)碼指令使用操作數(shù)棧來(lái)傳遞參數(shù)
局部變量數(shù)組(Local variable array): 這個(gè)數(shù)組包含當(dāng)前執(zhí)行方法的一個(gè)作用域內(nèi)的所有局部變量。這個(gè)數(shù)組可以包含基本類(lèi)型、引用或者返回地址。局部變量數(shù)組的大小在編譯時(shí)就已經(jīng)確定。jvm在方法調(diào)用時(shí)使用局部變量傳遞參數(shù),被調(diào)方法的局部變量數(shù)組通過(guò)調(diào)用方法的操作數(shù)棧創(chuàng)建。
運(yùn)行時(shí)常量池引用: 引用當(dāng)前類(lèi)當(dāng)前被執(zhí)行方法的常量池。JVM使用常量池引用傳遞信號(hào)給真正的內(nèi)存引用。
棧(stack)
每個(gè)JVM線程都有一個(gè)私有的JVM棧,與線程同時(shí)創(chuàng)建。java虛擬機(jī)棧存儲(chǔ)幀。每次調(diào)用一個(gè)方法時(shí),都會(huì)創(chuàng)建一個(gè)幀,并且壓入虛擬機(jī)棧。當(dāng)這個(gè)方法執(zhí)行完成時(shí),這個(gè)幀也會(huì)銷(xiāo)毀(無(wú)論方法是正常執(zhí)行完成,還是拋出異常)
在一個(gè)線程執(zhí)行的過(guò)程中只有一個(gè)幀是可用的。這個(gè)幀稱(chēng)為當(dāng)前幀(current frame)。
對(duì)局部變量和操作數(shù)棧的操作通常和當(dāng)前幀的引用一起。
我們?cè)倏匆粋€(gè)加法的例子
public int add(int a, int b){ return a + b; } public void functionA(){ // some code without function call int result = add(2,3); //call to function B // some code without function call }
在方法A內(nèi)部,A幀是當(dāng)前幀,位于虛擬機(jī)棧頂。在調(diào)用add方法開(kāi)始時(shí),創(chuàng)建一個(gè)新的幀B,并且壓入虛擬機(jī)棧。幀B成為新的當(dāng)前幀。
幀B的局部變量數(shù)組通過(guò)幀A的操作數(shù)棧中的數(shù)據(jù)填充。當(dāng)add方法結(jié)束在,幀B被銷(xiāo)毀,幀A重新成為當(dāng)前幀。add方法的結(jié)果壓入A幀的操作數(shù)棧,這樣方法A可以通過(guò)幀A的操作數(shù)棧獲取add 的結(jié)果.
以上是“java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!