1. 程式人生 > >Java基礎之JVM解析第一篇

Java基礎之JVM解析第一篇

一 JVM概述

對於 Java 程式設計師來說,在虛擬機器自動記憶體管理機制下,不再需要像C/C++程式開發程式設計師這樣為內一個 new 操作去寫對應的 delete/free 操作,不容易出現記憶體洩漏和記憶體溢位問題。正是因為 Java 程式設計師把記憶體控制權利交給 Java 虛擬機器,一旦出現記憶體洩漏和溢位方面的問題,如果不瞭解虛擬機器是怎樣使用記憶體的,那麼排查錯誤將會是一個非常艱鉅的任務。

所以,學習Java虛擬機器其實是學習Java的記憶體分配和管理。

 

二  Java記憶體區域

Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域,每個區域都有各自的用途,以及建立和銷燬的時間。

2.1 程式計數器

程式計數器是一塊較小的記憶體空間,它的作用可以看做是當前執行緒所執行的位元組碼的行號指示器。通過改變這個計數器的值選取下一條需要執行的位元組碼指令操作;
由於Java虛擬機器的多執行緒是通過執行緒輪流切換並分配處理器執行時間的方法來實現的,在一個確定的時刻,一個處理器只會執行一條執行緒的指令。因此,為了執行緒切換後能恢復到正確的執行位置,每條執行緒都需要一個獨立的程式計數器。

2.2 Java虛擬機器棧

生命週期與執行緒相同
每一個方法被呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。經常有人把Java記憶體區分為堆記憶體和棧記憶體,棧就是虛擬機器棧,或者說是虛擬機器棧中的區域性變量表部分。區域性變量表所需的記憶體空間在編譯期間完成分配。

2.3 本地方法棧

本地方法棧與虛擬機器棧所發揮的作用是相似的,其區別不過是虛擬機器棧為虛擬機器執行Java方法(也就是位元組碼)服務,而本地方法棧則是為虛擬機器使用到的Native方法服務。有的虛擬機器直接就把本地方法棧和虛擬機器棧合二為一。

2.4 Java堆

Java堆是被所有執行緒共享的一塊記憶體區域,在虛擬機器啟動時建立。此記憶體區域唯一目的就是存放物件例項,幾乎所有的物件例項都在這裡分配記憶體。Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱作“GC堆”。如果從記憶體回收的角度看,由於現在收集器基本採用分代收集演算法,所以Java堆中還可以細分為小的區域。Java堆可以處在物理上不連續的記憶體空間中,只要邏輯上是連續的即可。

Java 虛擬機器所管理的記憶體中最大的一塊,Java 堆是所有執行緒共享的一塊記憶體區域,在虛擬機器啟動時建立。此記憶體區域的唯一目的就是存放物件例項,幾乎所有的物件例項以及陣列都在這裡分配記憶體。

Java 堆是垃圾收集器管理的主要區域,因此也被稱作GC堆(Garbage Collected Heap).從垃圾回收的角度,由於現在收集器基本都採用分代垃圾收集演算法,所以Java堆還可以細分為:新生代和老年代:再細緻一點有:Eden空間、From Survivor、To Survivor空間等。進一步劃分的目的是更好地回收記憶體,或者更快地分配記憶體。

2.5 方法區

方法區與 Java 堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。雖然Java虛擬機器規範把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做 Non-Heap(非堆),目的應該是與 Java 堆區分開來。

2.6 執行時常量池

執行時常量池是方法區的一部分。Class 檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有常量池資訊(用於存放編譯期生成的各種字面量和符號引用)

既然執行時常量池時方法區的一部分,自然受到方法區記憶體的限制,當常量池無法再申請到記憶體時會丟擲 OutOfMemoryError 異常。利用如String類的intern方法

2.7 直接記憶體

直接記憶體並不是虛擬機器執行時資料區的一部分,也不是虛擬機器規範中定義的記憶體區域,但是這部分記憶體也被頻繁地使用。而且也可能導致OutOfMemoryError異常出現。

JDK1.4中新加入的 NIO(New Input/Output) 類,引入了一種基於通道(Channel) 與快取區(Buffer) 的 I/O 方式,它可以直接使用Native函式庫直接分配堆外記憶體,然後通過一個儲存在 Java 堆中的 DirectByteBuffer 物件作為這塊記憶體的引用進行操作。這樣就能在一些場景中顯著提高效能,因為避免了在 Java 堆和 Native 堆之間來回複製資料。

 

三 重點補充內容

 String類和常量池

String str1 = "abcd";
String str2 = new String("abcd");
System.out.println(str1 == str2); //false

這兩種不同的建立方法是有差別的,第一種方式是在常量池中拿物件,第二種方式是直接在堆記憶體空間建立一個新的物件。

String常量池使用方法有兩種:1是直接引用,2是使用intern方法,如果執行時常量池中已經包含一個等於此 String 物件內容的字串,則返回常量池中該字串的引用;如果沒有,則在常量池中建立與此 String 內容相同的字串,並返回常量池中建立的字串的引用。

 

 

 

 

參考文獻:

1. 周志明. 深入理解 Java 虛擬機器: JVM 高階特性與最佳實踐. 機械工業出版社, 2013.