搞定JVM系列:JVM原理初識

Java語言的一個非常重要的特點就是與平臺的無關性。而使用Java虛擬機(JVM)是實現這一特點的關鍵。JVM是一種用於計算設備的規範,它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。之前市場上主要有三種主流的JVM,

  1. Sun公司的HotSpot
  2. BEA公司的JRockit
  3. IBM公司的J9 JVM

後來Sun公司和BEA公司都被oracle收購,並且oracle採用Sun公司的HotSpot和BEA公司的JRockit兩個JVM中精華退出了 jdk1.8 的JVM。

下圖是JVM的結構,非常重要,尤其是運行時區域。

搞定JVM系列:JVM原理初識

運行時數據區

1、堆

JVM中最大的一塊,主要用來存放對象實例和數組,幾乎所有的對象實例都在這裡分配內存。線程共享,內部會劃分出多個線程私有的分配緩衝區(TLAB)。可以位於物理上不連續的空間,但是邏輯上要連續。

2、虛擬機棧

每個方法被執行的時候都會同時創建一個棧幀(Stack Frame)用於存儲局部變量表、操作棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。線程私有,生命週期和線程一致。

3、方法區(非堆)

屬於共享內存區域,存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。

4、本地方法棧

本地方法棧(Native MethodStacks)與虛擬機棧所發揮的作用是非常相似的,其區別不過是虛擬機棧為虛擬機執行Java 方法(也就是字節碼)服務,而本地方法棧則是為虛擬機使用到的Native 方法服務。

5、程序計數器

程序計數器(Program CounterRegister)是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的行號指示器。此內存區域是唯一一個在Java 虛擬機規範中沒有規定任何 OutOfMemoryError 情況的區域。

6、直接內存

直接內存(Direct Memory)並不是虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域,但是這部分內存也被頻繁地使用,而且也可能導致OutOfMemoryError 異常出現。在JDK 1.4 中新加入了NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,它可以使用Native函數庫直接分配堆外內存,然後通過一個存儲在Java堆裡面的DirectByteBuffer 對象作為這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在Java 堆和Native 堆中來回複製數據。

搞定JVM系列:JVM原理初識

堆的分代收集

JVM有多種垃圾回收算法,其中目前在用最經典的就是分代收集算法,這裡優先記錄下。

  • 永久代(Perm):主要保存class,method,field等對象,該空間大小,取決於系統啟動加載類的數量,一般該區域內存溢出均是啟動時溢出。java.lang.OutOfMemoryError: PermGen space
  • 老年代(Old):一般是經過多次垃圾回收(GC)沒有被回收掉的對象。
  • 新生代(Eden):新創建的對象。
  • 新生代(Survivor0):經過垃圾回收(GC)後,沒有被回收掉的對象。
  • 新生代(Survivor1):同Survivor0相同,大小空間也相同,同一時刻Survivor0和Survivor1只有一個在用,一個為空。

Java堆是垃圾收集器管理的主要區域,按照分代收集算法的劃分,堆內存空間可以繼續細分為年輕代,老年代。年輕代又可以劃分為較大的Eden區,兩個同等大小的From Survivor,To Survivor區。默認的Eden區和Survivor區的大小比例為8:1:1。在為新創建的對象分配內存的時候先將對象分配到Eden區和From Survivor區,在立即回收時,會將Eden區和Survivor區還存活的對象複製到To Survivor區中,如果To Survivor區的大小不能容納存活的對象,會把存活的對象分配到老年區。總體來說,新創建的小對象會放在年輕代,年輕代的對象大多在下一次垃圾回收時被回收,老年代存儲大的對象和存活時間長的對象。

搞定JVM系列:JVM原理初識

附錄:相關概念

1、基本類型和引用類型

JVM中,數據類型可以分為兩類:基本類型和引用類型。基本類型的變量保存原始值,即:他代表的值就是數值本身;而引用類型的變量保存引用值。“引用值”代表了某個對象的引用,而不是對象本身,對象本身存放在這個引用值所表示的地址的位置。

2、堆和棧

棧是運行時的單位,而堆是存儲的單位。在Java中一個線程就會相應有一個線程棧與之對應,這點很容易理解,因為不同的線程執行邏輯有所不同,因此需要一個獨立的線程棧。而堆則是所有線程共享的。棧因為是運行單位,因此裡面存儲的信息都是跟當前線程(或程序)相關信息的。包括局部變量、程序運行狀態、方法返回值等等;而堆只負責存儲對象信息。

棧代表了處理邏輯,而堆代表了數據。

堆中存的是對象,棧中存的是基本數據類型和堆中對象的引用。

在Java中,Main函數就是棧的起始點,也是程序的起始點。

3、引用類型

對象引用類型分:強引用、軟引用、弱引用和虛引用。

強引用:不會被回收。就是我們一般聲明對象是時虛擬機生成的引用,強引用環境下,垃圾回收時需要嚴格判斷當前對象是否被強引用,如果被強引用,則不會被垃圾回收。

軟引用:內存充足,則不回收;不足則回收。軟引用一般被做為緩存來使用。與強引用的區別是,軟引用在垃圾回收時,虛擬機會根據當前系統的剩餘內存來決定是否對軟引用進行回收。如果剩餘內存比較緊張,則虛擬機會回收軟引用所引用的空間;如果剩餘內存相對富裕,則不會進行回收。換句話說,虛擬機在發生OutOfMemory時,肯定是沒有軟引用存在的。

弱引用:會被回收。弱引用與軟引用類似,都是作為緩存來使用。但與軟引用不同,弱引用在進行垃圾回收時,是一定會被回收掉的,因此其生命週期只存在於一個垃圾回收週期內。


分享到:


相關文章: