文章目錄
-
- JVM(Java virtual machine)
-
- 1.JVM記憶體模型(JDK8)
- 2.記憶體區域分類
- 3.各區域的介紹及其作用詳解
-
- 3.1 程式計數器
- 3.2 虛拟機棧
- 3.3 本地方法棧
- 3.4 堆
- 3.5 方法區
JVM(Java virtual machine)
1.JVM記憶體模型(JDK8)
Java1.8的改動:
将永久代移除,取而代之的是元空間。
元空間不再與堆是連續的實體記憶體,而是改為使用本地記憶體(Native Method)。也就意味着隻要本地記憶體足夠,就不會出現OOM(OutOfMemory)的錯誤。
2.記憶體區域分類
線程私有的區域:程式計數器、虛拟機棧、本地方法棧
線程共享的區域:堆、方法區、直接記憶體
3.各區域的介紹及其作用詳解
3.1 程式計數器
程式計數器是一塊較小的記憶體空間,它的作用可以看做是目前線程所執行的位元組碼的行号的訓示器。
如果線程正在執行的是一個Java方法,程式計數器記錄正在執行的虛拟機位元組碼指令的位址;
如果正在執行的是Native方法,程式計數器的值為Undefined。
程式計數器的主要作用:
- 在單個線程執行的過程中,位元組碼解釋器通過改變程式計數器來依次讀取指令,進而實作代碼的流程控制,如:順序執行、循環、異常執行。
- 在多線程并發運作的情況下,程式計數器用于記錄目前線程執行的位置,進而保證線程切換的時候能夠從上一次的位置繼續工作。
值得注意的是,程式計數器是JVM虛拟機規範中唯一一個沒有規定OOM(OutOfMemory)Error的記憶體區域。
顯然,由于程式計數器儲存的是位元組碼指令的位址,是以它占用的空間為一個很小的定值。是以,這個區域不會出現OOM的情況。
程式計數器的生命周期同它所屬于的線程的生命周期。
3.2 虛拟機棧
虛拟機棧描述的是Java方法執行的記憶體模型:
每個方法在執行的同時都會建立一個棧幀,用于存儲局部變量表、操作數棧、動态連結、方法出口等資訊。每一個方法從調用到執行完成的過程,就是對應的棧幀在虛拟機棧中從入棧到出棧的過程。
局部變量表:
局部變量表是一組變量值存儲空間,用于存放方法參數和方法内定義的局部變量。
一個局部變量可以儲存一個類型為boolean、byte、char、short、int、float和reference類型的資料。reference類型表示對一個對象執行個體的引用。
操作數棧:
操作數棧是資料運算的地方,大多數指令都在操作數棧彈棧運算,然後結果壓棧。操作數棧可了解為虛拟機棧中的一個用于計算的臨時資料存儲區。
:将局部變量表中的i壓入操作數棧,将局部變量表中i自增,然後取棧頂的i使用。
i++
:将局部變量表中的i自增,然後将i壓入操作數棧中,最後取棧頂的i使用。
++i
這兩個操作分為多步,不能保證原子性。
如何使其具有原子性?使用循環CAS操作。
動态連結:
每個棧幀中包含一個在常量池中對目前方法的引用。
方法傳回位址:
方法退出的過程相當于彈出目前棧幀,退出可能有三種方式:
- 傳回值壓入上層調用棧幀。
- 異常資訊抛給能夠處理的棧幀。
- PC計數器指向方法調用後的下一條指令。
虛拟機棧可能會出現兩種錯誤:
StackOverFlowError
和
OutOfMemoryError
。
-
:若虛拟機棧的記憶體大小不允許動态擴充,當線程請求棧的深度超過目前 Java 虛拟機棧的最大深度的時候,就抛出StackOverFlowError
。StackOverFlowError
-
:若虛拟機棧的記憶體大小可以動态擴充,在虛拟機動态擴充棧的時候無法申請到足夠的記憶體空間,就會抛出OutOfMemoryError
。OutOfMemoryError
3.3 本地方法棧
和虛拟機棧所發揮的作用非常相似。
差別是: 虛拟機棧為虛拟機執行 Java 方法服務,而本地方法棧則為虛拟機使用到的 Native 方法服務。
本地方法棧也會出現
StackOverFlowError
和
OutOfMemoryError
。
3.4 堆
Java 虛拟機所管理的記憶體中最大的一塊,Java 堆是所有線程共享的一塊記憶體區域,在虛拟機啟動時建立。
此記憶體區域的唯一目的就是存放對象執行個體,幾乎所有的對象執行個體以及數組都在這裡配置設定記憶體。
JDK1.7之後,字元串常量池從方法區被移動到了堆中。
Java 堆是垃圾收集器管理的主要區域,是以也被稱作GC 堆(Garbage Collected Heap)。
從垃圾回收的角度,由于現在收集器基本都采用分代垃圾收集算法,是以 Java 堆還可以細分為:新生代和老年代。
再細緻一點,新生代又可以分為Eden、From Survivor、To Survivor 空間等。
進一步劃分的目的是更好地回收記憶體,或者更快地配置設定記憶體。
預設新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過參數 –XX:NewRatio 來指定 )
預設eden : from : to = 8 : 1 : 1 ( 可以通過參數 –XX:SurvivorRatio 來設定 )
常見的堆設定:
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:設定年輕代大小
-XX:NewRatio=n:設定年輕代和年老代的比值
-XX:SurvivorRatio=n:年輕代中Eden區與兩個Survivor區的比值
3.5 方法區
方法區與Java堆一樣,是各個線程共享的記憶體區域。
它用于存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯器編譯後的代碼等資料。
雖然Java虛拟機規範把方法區描述為堆的一個邏輯部分,但是它卻有一個别名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。
JDK7之後,廢棄了永久代的概念,改用在本地記憶體中實作的元空間(Metaspace)來代替。
-XX:MetaspaceSize=N //設定 Metaspace 的初始大小
-XX:MaxMetaspaceSize=N //設定 Metaspace 的最大大小
JDK7之後,字元串常量池被從方法區拿到了堆中,但是運作時常量池還在方法區之中。
class常量池和運作時常量池:
class常量池簡介:
我們寫的每一個Java類被編譯後,就會形成一份class檔案;class檔案中除了包含類的版本、字段、方法、接口等描述資訊外,還有一項資訊就是常量池(constant pool table),用于存放編譯器生成的各種字面量(Literal)和符号引用(Symbolic References);
每個class檔案都有一個class常量池。
什麼是字面量和符号引用:
字面量包括:
1.文本字元串
2.八種基本類型的值
3.被聲明為final的常量等;
符号引用包括:
1.類和方法的全限定名
2.字段的名稱和描述符
3.方法的名稱和描述符。
運作時常量池
存在于方法區中,也就是class常量池被加載到記憶體之後的版本,不同之處是:
它的字面量可以動态的添加
(如使用String#intern())
,符号引用可以被解析為直接引用
JVM在執行某個類的時候,必須經過加載、連接配接、初始化,而連接配接又包括驗證、準備、解析三個階段。而當類加載到記憶體中後,jvm就會将class常量池中的内容存放到運作時常量池中,由此可知,運作時常量池也是每個類都有一個。在解析階段,會把符号引用替換為直接引用,解析的過程會去查詢字元串常量池,以保證運作時常量池所引用的字元串與字元串常量池中是一緻的。