1.JVM
JVM是Java Virtual Machine(Java虛擬機)的縮寫酣栈,JVM是一種用于計算設(shè)備的規(guī)范,它是一個虛構(gòu)出來的計算機蝠嘉,是通過在實際的計算機上仿真模擬各種計算機功能來實現(xiàn)的灯萍。
Java語言的一個非常重要的特點就是與平臺的無關(guān)性。而使用Java虛擬機就是實現(xiàn)這一特點的關(guān)鍵端铛。
2.JVM內(nèi)存模型
PC程序計數(shù)器
記錄正在執(zhí)行的虛擬機字節(jié)碼指令的地址泣矛,當(dāng)線程在執(zhí)行的是 Native 方法(調(diào)用本地操作系統(tǒng)方法)時,該計數(shù)器的值為空禾蚕。另外您朽,該內(nèi)存區(qū)域是唯一一個在 Java 虛擬機規(guī)范中么有規(guī)定任何 OOM(內(nèi)存溢出:OutOfMemoryError)情況的區(qū)域。
Java虛擬機棧
每個 Java 方法在執(zhí)行的同時會創(chuàng)建一個棧幀用于存儲局部變量表换淆、操作數(shù)棧哗总、動態(tài)連接几颜、方法返回地址和一些額外的附加信息。從方法調(diào)用直至執(zhí)行完成的過程讯屈,對應(yīng)著一個棧幀在 Java 虛擬機棧中入棧和出棧的過程蛋哭。
可以通過 -Xss 這個虛擬機參數(shù)來指定每個線程的 Java 虛擬機棧內(nèi)存大小,在 JDK 1.4 中默認(rèn)為 256K涮母,而在 JDK 1.5+ 默認(rèn)為 1M谆趾。
- 局部變量表 是一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量叛本,其中存放的數(shù)據(jù)的類型是編譯期可知的各種基本數(shù)據(jù)類型沪蓬、對象引用(reference)和 returnAddress 類型(它指向了一條字節(jié)碼指令的地址)。
- 操作數(shù)棧又常被稱為操作棧炮赦,操作數(shù)棧的最大深度也是在編譯的時候就確定了怜跑。32 位數(shù)據(jù)類型所占的棧容量為 1,64 位數(shù)據(jù)類型所占的棧容量為 2。當(dāng)一個方法開始執(zhí)行時吠勘,它的操作棧是空的性芬,在方法的執(zhí)行過程中,會有各種字節(jié)碼指令(比如:加操作剧防、賦值元算等)向操作棧中寫入和提取內(nèi)容植锉,也就是入棧和出棧操作。
- 動態(tài)連接 每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用峭拘,持有這個引用是為了支持方法調(diào)用過程中的動態(tài)連接俊庇。Class 文件的常量池中存在有大量的符號引用,字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號引用為參數(shù)鸡挠。這些符號引用辉饱,一部分會在類加載階段或第一次使用的時候轉(zhuǎn)化為直接引用(如 final、static 域等)拣展,稱為靜態(tài)解析彭沼,另一部分將在每一次的運行期間轉(zhuǎn)化為直接引用,這部分稱為動態(tài)連接备埃。
- 方法返回地址 當(dāng)一個方法被執(zhí)行后姓惑,有兩種方式退出該方法:執(zhí)行引擎遇到了任意一個方法返回的字節(jié)碼指令或遇到了異常,并且該異常沒有在方法體內(nèi)得到處理按脚。方法退出的過程實際上等同于把當(dāng)前棧幀出站于毙,因此退出時可能執(zhí)行的操作有:恢復(fù)上層方法的局部變量表和操作數(shù)棧,如果有返回值辅搬,則把它壓入調(diào)用者棧幀的操作數(shù)棧中唯沮,調(diào)整 PC 計數(shù)器的值以指向方法調(diào)用指令后面的一條指令。
該區(qū)域可能拋出以下異常:
當(dāng)線程請求的棧深度超過最大值,會拋出 StackOverflowError 異常介蛉;
棧進行動態(tài)擴展時如果無法申請到足夠內(nèi)存夯缺,會拋出 OutOfMemoryError 異常。
本地方法棧
本地方法棧與 Java 虛擬機棧類似甘耿,只不過本地方法棧是為本地方法服務(wù)。
堆
所有對象都在這里分配內(nèi)存竿滨,是垃圾收集的主要區(qū)域佳恬。堆分為新生代(Young Generation)和老年代(Old Generation)。新生代又可分為伊甸園區(qū)(Eden space)和兩個幸存者區(qū)(Survivor pace)于游。
可以通過 -Xms 和 -Xmx 這兩個虛擬機參數(shù)來指定一個程序的堆內(nèi)存大小毁葱,第一個參數(shù)設(shè)置初始值,第二個參數(shù)設(shè)置最大值贰剥。
方法區(qū)
用于存放已被加載的類信息倾剿、常量、靜態(tài)變量蚌成、即時編譯器JIT編譯后的代碼等數(shù)據(jù)前痘。對這塊區(qū)域進行垃圾回收的主要目標(biāo)是對常量池的回收和對類的卸載,但是一般比較難實現(xiàn)担忧。
HotSpot 虛擬機把它當(dāng)成永久代來進行垃圾回收芹缔。但很難確定永久代的大小,因為它受到很多因素影響瓶盛,并且每次 Full GC 之后永久代的大小都會改變最欠,所以經(jīng)常會拋出 OutOfMemoryError 異常。為了更容易管理方法區(qū)惩猫,從 JDK 1.8 開始芝硬,移除永久代,并把方法區(qū)移至元空間轧房,它位于本地內(nèi)存中拌阴,而不是虛擬機內(nèi)存中。方法區(qū)是一個 JVM 規(guī)范锯厢,永久代與元空間都是其一種實現(xiàn)方式皮官。
-XX:PermSize設(shè)置永久代初始大小,-XX:MaxPermSize設(shè)置永久代最大值实辑。
針對HotSpot 虛擬機
jdk1.6及之前: 有永久代捺氢, 方法區(qū)的數(shù)據(jù)存放在永久代中
jdk1.7: 有永久代,靜態(tài)變量和常量池存放在堆中;其它剩下的存放在永久代中
jdk1.8及之后: 無永久代剪撬,靜態(tài)變量和常量池存放在堆中摄乒;有元空間 ,儲存元信息等數(shù)據(jù)
運行時常量池
運行時常量池是方法區(qū)的一部分。Class 文件中的常量池(編譯器生成的字面量和符號引用)會在類加載后被放入這個區(qū)域馍佑。除了在編譯期生成的常量斋否,還允許動態(tài)生成,例如 String 類的 intern()拭荤。
- 字面量 指字符串字面量和聲明為 final 的(基本數(shù)據(jù)類型)常量值茵臭,這些字符串字面量除了類中所有雙引號括起來的字符串(包括方法體內(nèi)的),還包括所有用到的類名舅世、方法的名字和這些類與方法的描述符旦委、字段(成員變量)的名稱和描述符、聲明為final的成員變量雏亚。這些都在常量池的表中缨硝。
- 符號引用 指指向表中的這些字面量的引用,包括類和接口的全限定名罢低、字段的名稱和描述符查辩、方法的名稱和描述符。只不過是以一組符號來描述所引用的目標(biāo)网持,和內(nèi)存并無關(guān)宜岛,所以稱為符號引用,直接指向內(nèi)存中某一地址的引用稱為直接引用翎碑。
直接內(nèi)存
直接內(nèi)存并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分谬返,也不是 Java 虛擬機規(guī)范中定義的內(nèi)存區(qū)域,它直接從操作系統(tǒng)中分配日杈,因此不受 Java 堆大小的限制遣铝。
在 JDK 1.4 中新引入了 NIO 類,它可以使用 Native 函數(shù)庫直接分配堆外內(nèi)存莉擒,然后通過 Java 堆里的 DirectByteBuffer 對象作為這塊內(nèi)存的引用進行操作酿炸。這樣能在一些場景中顯著提高性能,因為避免了在堆內(nèi)存和堆外內(nèi)存來回拷貝數(shù)據(jù)涨冀。
參考資料
周志明. 深入理解 Java 虛擬機 [M]. 機械工業(yè)出版社, 2011.