Java虚拟机内存区域---学习笔记
Java虛擬機
虛擬機:
定義:模擬某種計算機體系結構,執行特定指令集的軟件。
種類:
Java語言虛擬機:可以執行Java語言的高級語言虛擬機。Java語言虛擬機并不以一定就可以稱為JVM,譬如:Apache Harmony
Java虛擬機:
1.必須通過Java TCK(Technology Compatibility Kit)的兼容性測試的Java語言虛擬機才能成為Java虛擬機。
2.Java虛擬機并非一定要執行Java程序
3.業界三大商用JVM:Oracle HotSpot、Oracle JRockit VM、IBM J9 VM
Oracle HotSpot虛擬機是目前OracleJDK和OpenJDK中自帶的虛擬機。
Java虛擬機運行時數據區
Java虛擬機運行時數據區,有一些區域是全局共享的,隨著虛擬機啟動而創建,隨著虛擬機退出而銷毀。有一些區域是線程私有的,隨著線程開始和結束而創建和銷毀。
運行時數據區劃分:
方法區
圖中的方法區和Java堆這兩部分是所有線程所共享的數據區域,Java虛擬機棧、本地方法棧、程序計數器這三部分是線程私有的數據區域。
程序計數器:
一塊較小的內存空間,它的作用可以看作是當前線程所執行的字節碼的行號指示器。可以看作是指針,指示著當前程序(字節碼)正在運行的一行代碼。
由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內核)都只會執行一條線程中的指令。因此,為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲,我們稱這類內存區域為線程私有的內存。
如果線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址:如果正在執行的是Native方法,這個計數器值為空。
程序計數器這塊內存區域是唯一一個在java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。
Java虛擬機棧:
java虛擬機棧也是線程私有的。生命周期與線程相同。
每個方法在執行的同時都會創建一個棧幀,用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機中入棧到出棧的過程。
一個完整的棧幀包含:局部變量表、操作數棧、動態鏈接棧信息、方法正常完成和異常完成信息
1.虛擬機棧中的局部變量表部分:
局部變量表用于方法間參數的傳遞,以及方法執行過程中存儲基礎數據類型的值和對象的引用。
局部變量表存放了編譯期可知的各種基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型)和returnAddress類型(指向了一條字節碼指令的地址,基本不再使用)。
其中64位長度的long、double類型的數據會占用2個局部變量空間(Slot),但是不會像Java內存模型中的非原子性一樣,其余的數據類型只占用1個。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。
2.操作數棧部分:
是一個后進先出的棧,由若干個Entry組成,長度由編譯器決定。
單個Entry即可以存儲一個Java虛擬機中定義的任意數據類型的值,包括long和double類型,但是存儲long和double類型的Entry深度為2,其他類型深度為1。
在方法執行的過程中,棧幀用于存儲計算參數和計算結果;在方法調用時,操作數棧也用來準備調用方法的參數以及接收方法返回結果。
例子:演示棧幀的局部變量表和操作數棧的工作方式
字節碼如圖所示:
方法的執行過程:
《1》當執行第一條字節碼指令時:
程序計數器首先記載了這條指令的偏移量(為0),bipush字節碼指令的作用是將后邊跟的整型數據入棧到操作數棧的棧頂,當著條指令執行完成后,操作數棧的棧頂將存在一個數100。
《2》當執行第二條字節碼指令時:
程序計數器將記載這條指令的偏移量(為2,原因是:前邊bipush指令中,指令占用了一個偏移量,參數100也占用了一個偏移量),istore_1字節碼指令(只有指令,沒有參數,所以只會占用一個偏移量)作用是把操作數棧的棧頂元素出棧,并且將數據存儲到局部變量表索引號為1的solt之中。
《3》當執行偏移為11的那條指令時:執行該指令之前,局部變量表中的1號solt中為100,2號solt中為200,3號solt中為300;iload_1指令的作用是將局部變量表索引號為1的數存儲到操作數棧的棧頂,這條指令完成之后操作數棧的棧頂數位100
《4》當執行偏移為12的那條指令時:iload_2指令將會將局部變量表中索引號為2的數存儲到操作數棧的棧頂,當這條指令完成之后操作數棧的棧頂將會有兩個元素:分別是棧頂元素200和第二個元素100
《5》執行指令iadd,iadd指令是一條整數加法指令,作用是將操作數棧距離棧頂最近的兩個數出棧,然后把這兩個數相加的結果重新存入操作數棧的棧頂,當這條指令執行完之后,操作數棧的深度變為1,并且存入了數據300
《6》執行指令iload_3,將局部變量表索引號為3的數300存入操作數棧的棧頂
《7》執行imul指令,該指令是整數乘法指令,與前邊執行整數加法指令類似,將會把距離操作數棧棧頂最近的兩個數出棧,相乘后的結果存入操作數棧的棧頂
《8》指令ireturn指令的作用是將操作數棧頂的數出棧,將這個值作為方法返回值進行返回。
java虛擬機棧的兩種異常:
1.StackOverflowError異常:如果線程請求的棧容量大于虛擬機棧所允許的最大容量時,Java虛擬機將會拋出該異常。
2.OutOfMemeryError異常:如果虛擬機棧可以動態擴展,并且擴展的動作已經嘗試過,但是目前無法申請到足夠的內存去完成擴展,或者在建立新的線程時沒有足夠的內存去創建對應的虛擬機棧,那Java虛擬機將會拋出該異常。
例子1:模擬StackOverflowError異常
package com.test.JavaVM;/*** 模擬StackOverflowError異常* 2015年9月9日 下午8:09:59* @author 張耀暉**/ public class JavaVMStackSOF {private int stackLength = 1;public static void main(String[] args) {JavaVMStackSOF oom = new JavaVMStackSOF();try {oom.stackLeak();} catch (Throwable e) {System.out.println("棧的深度:"+oom.stackLength);throw e;}}//該方法遞歸調用自己public void stackLeak(){stackLength++;stackLeak();}}運行結果:
例子2:模擬OutOfMemeryError異常
package com.test.JavaVM;/*** 模擬OutOfMemeryError異常* 2015年9月9日 下午8:47:24* @author 張耀暉**/ public class JavaVMStackOOM {public static void main(String[] args) {JavaVMStackOOM oom = new JavaVMStackOOM();oom.stackLeakByThread();}private void dontStop(){while(true){}}public void stackLeakByThread(){while(true){Thread thread = new Thread(new Runnable() {@Overridepublic void run() {dontStop();}});thread.start();}} }運行結果:
這段代碼會導致系統變得很卡,因為這段代碼在不斷的創建線程,直到系統分配給JVM虛擬機的內存不夠時,就會拋出OutOfMemeryError異常。本地方法棧
本地方法棧與虛擬機棧所發揮的作用是非常相似的,它們之間的區別不過是虛擬機棧為虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則為虛擬機使用到的Native方法服務。
Sun HotSpot虛擬機直接就把本地方法棧和虛擬機棧合二為一了。
與虛擬機棧一樣,本地方法棧區域會拋出StackOverflowError和OutOfMemoryError異常。
Java堆
java堆是被java所有線程內存共享的內存區域。
Java堆是Java虛擬機所管理的內存中最大的一塊。
在虛擬機創建的時候創建,在虛擬機銷毀的時候銷毀。
此區域的唯一作用:存放Java對象實例,幾乎所有的對象實例以及數組都要在堆上分配內存。
Java堆是垃圾收集器管理的主要區域,即常說的GC堆,該區域是實現自動內存管理的。
因為Java堆是全局共享的內存區域,所有Java線程所分配的Java對象都存儲在Java堆之中,以為這個數據區域會被Java線程共同使用,為了避免各個Java線程所可能產生的競爭關系,例如,兩個Java線程同時使用Java堆中的一塊內存來分配不同的對象,這時候它們就對Java堆中的空間產生了競爭,為了避免這種競爭關系,Java虛擬機很可能會把Java堆根據不同的各個線程劃分出若干個線程私有的分配緩沖區,這時候各個線程會在自己獨立的分配緩沖區中分配對象,當分配緩沖區的空間用完的時候,才會加鎖,并且向Java堆分配新的分配緩沖區的內存。
Java堆可以處于物理上不連續的內存空間中,只要邏輯上連續就行,就像我們的磁盤空間一樣。在實現時,既可以實現成固定大小的,也可以實現成可擴展的,不過當前主流的虛擬機都是按照可擴展的來實現的。
如果在堆中沒有內存完成實例分配,并且堆也無法在擴展時,將拋出OutOfMemoryError異常。
從棧到堆的關聯過程:
第一種方式:HotSpot虛擬機采用的方式
第二種方式:
模擬Java堆中的OutOfMemoryError異常例子:
package com.test.JavaVM;import java.util.ArrayList; import java.util.List;/*** 模擬Java堆的OutOfMemoryError異常* 2015年9月10日 下午3:06:03* @author 張耀暉**/ public class HeapOOM {static class OOMObject{}public static void main(String[] args) {List<OOMObject> list = new ArrayList<OOMObject>();//會不斷的創建OOMObject的對象,并且創建出來的這些對象都被List所引用,保證了創建的這些對象不被Java虛擬機的自動回收機制所回收while(true){list.add(new OOMObject());}}}運行結果:
方法區:
方法區與Java堆一樣,是各個線程共享的內存區域,它用于存儲已經被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
Java虛擬機規范對方法區的限制相當寬松,除了和Java堆一樣不需要連續的內存和可以選擇固定大小或者可擴展外,還可以選擇不實現垃圾收集。相對而言,垃圾收集行為在這個區域是比較少出現的,但并非數據進入方法區就如永久代的名字一樣永久存在了。這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載,一般來說,這個區域的回收成績比較難以令人滿意,尤其是類型的卸載,條件相當的苛刻,但是這部分區域的回收確實是必要的。在Sun公司的BUG列表中,曾出現過的若干個嚴重的BUG就是由于低版本的HotSpot虛擬機對此區域未完成回收而導致內存泄漏。
當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常。
運行時常量池
運行時常量池是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等信息外,還有一項信息是常量池,用于存放編譯期生成的各種字面量和符號的引用,這部分內容將在類加載后進入方法區的運行時常量池中存放。
運行時常量池相對于Class文件常量池的另外一個重要的特征是具備動態性,Java語言并不要求常量一定只有編譯期才能產生,也就是并非預置入Class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用的比較多的便是String類的intern()方法。
既然運行時常量池是方法區的一部分,自然受到方法區內存的限制,當常量池無法在申請到內存時會拋出OutOfMemoryError異常。
HotSpot虛擬機方法區實現的變遷:
在JDK1.2~JDK6,HotSpot使用永久代實現方法區。
在JDK7開始,HotSpot開始了移除永久代的計劃,符號表被移到了Native Heap中,字符串常量和類的靜態引用被移到Java Heap中。
在JDK8開始,永久代已被元空間所代替。
直接內存
直接內存并不是java虛擬機運行時內存區域的一部分,也不是Java虛擬機規范中定義的一塊內存區域,但是該區域也頻繁的使用,也會導致OutOfMemoryError異常。
直接內存是被java所有線程內存共享的內存區域。
直接內存區域能被Java虛擬機進行自動內存管理,但是檢測手段是有一些簡陋的。
總結
以上是生活随笔為你收集整理的Java虚拟机内存区域---学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Eclipse中Build Path的使
- 下一篇: Java的回调机制--学习笔记