JVM執行時資料區探索與直接記憶體的使用
話不多說,先上簡圖(<JDK1.8):
從圖中可以看出JVM的執行時資料區大致可以分為資料和指令兩塊內容,指令這塊本質上也屬於資料,不過大部分資料跟指令有關係。右邊有3個部分都是執行緒私有的,計數器儲存了當前執行緒執行的位元組碼指令的地址,不過這僅限於java方法,如果時native方法那這個計數器時為null的。(檢視位元組碼可以在命令列使用javap -v class檔名),而在虛擬機器棧裡面,一個java方法對應一個棧幀,每個棧幀儲存了該方法的區域性變數表和運算元棧以及動態連結等等,因此執行緒執行的過程相當於棧幀出棧的過程。在方法執行的過程中,資料在棧幀中時如何運轉的呢,下面來舉個簡單的例子:
public void add(int i){
int a=0;
int b=1;
a = b+i;
}
在執行該方法前,區域性變數表先把變數i,a,b(位置為0,1,2)儲存,然後執行相關的命令時就從變數表移到運算元棧,每次指令到來的時候就從運算元棧彈出資料並運算,再將結果壓入運算元棧。具體的位元組碼如下:
stack=2, locals=3, args_size=1
0: iconst_0 //把整數 0 壓入運算元棧
1: istore_1
2: iconst_1 //把整數 1 壓入運算元棧
3: istore_2 // 把棧頂的內容放入區域性變數表中索引為 2 的 slot 中
4: iload_2 // 把區域性變數表索引為 2 的 slot 中存放的變數值(b)載入至運算元棧
5: iload_0 // 把區域性變數表索引為 0 的 slot 中存放的變數值(i)載入至運算元棧
6: iadd //棧頂的兩個數出棧後相加,結果入棧
7: istore_1
8: return //有興趣的同學可以去查詢一下相關指令的說明
本地方法棧與虛擬機器棧的作用類似。方法區又被稱為堆的邏輯部分(JDK1.8移除了它),它主要儲存靜態變數以及已載入的類資訊還有一些編譯後的程式碼等等。堆空間是我們比較關心的部分,也是GC工作的主要部分,它主要存放了例項物件以及陣列物件和常量池。
還有一部分記憶體是直接記憶體,但是它並不是JVM執行時資料區的一部分,所以它並不在GC收集器的執行範圍內。下面我們來追蹤這部分記憶體的建立和回收過程。建立的語句如下:
ByteBuffer a = ByteBuffer.allocate(1024);
不能直接使用DirectByteBuffer的原因是它所有的構造器都沒有字首,也就是訪問許可權是default,這裡我們來複習一下java的訪問修飾符
訪問許可權 類 包 子類 其他包
public 1 1 1 1 (對任何人都是可用的)
protect 1 1 1 0 (繼承的類可以訪問以及和private一樣的許可權)
default 1 1 0 0 (包訪問許可權,即在整個包內均可被訪問)
private 1 0 0 0 (除型別建立者和型別的內部方法之外的任何人都不能訪問的元素)
只有該類的物件和同包下的其他類可以訪問到。下面是它的構造器,當然它的構造器有多個,原理都差不多:
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
base = unsafe.allocateMemory(size);// 分配堆外記憶體(由C的malloc實現),並返回堆外記憶體的地址
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); //為它構建一個Cleaner物件用於跟蹤DirectByteBuffer物件的垃圾回收
att = null;
}
Cleaner物件的宣告如下:
public class Cleaner extends PhantomReference<Object>
因此可以確定在回收堆外記憶體的時候是使用了虛引用的方式,我們知道虛引用是作用和名字描述一樣,一旦GC碰到了就會將其回收並加入ReferenceQueue,通常是用來追蹤GC回收的過程的。構建這個Cleaner物件的時候還引入了一個Deallocator物件,它的實現是一個執行緒Runnable物件,它的run方法如下:
public void run() {
if (address == 0) {
// Paranoia
return;
}
unsafe.freeMemory(address); //使用本地方法將該地址指向的記憶體釋放
address = 0;
Bits.unreserveMemory(size, capacity);//在系統中儲存總分配記憶體(按頁分配)的大小和實際記憶體的大小。
}
在Cleaner類的方法裡面存在clean方法:
if (remove(this)) {
try {
this.thunk.run(); //這個thunk就是傳入的Deallocator物件
} catch (final Throwable var2) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (System.err != null) {
(new Error("Cleaner terminated abnormally", var2)).printStackTrace();
}
System.exit(1);
return null;
}
});
}
綜上可以得出結論,直接記憶體是通過本地方法建立,由GC回收例項引用,再由Cleaner的呼叫clean方法釋放記憶體。下篇將會介紹JDK1.8與JDK1.7的資料區差異以及GC回收演算法
相關文章
- JVM執行時記憶體資料區域JVM記憶體
- JVM——記憶體區域:執行時資料區域詳解JVM記憶體
- 【JVM之記憶體與垃圾回收篇】執行時資料區概述及執行緒JVM記憶體執行緒
- Java記憶體區域與記憶體溢位異常 - 執行時資料區Java記憶體溢位
- JAVA-大白話探索JVM-執行時記憶體(三)JavaJVM記憶體
- Java 執行時資料區和記憶體模型Java記憶體模型
- Java記憶體區域(執行時資料區域)和記憶體模型(JMM)Java記憶體模型
- JVM筆記【1】-- 執行時資料區JVM筆記
- JVM執行時資料區JVM
- JVM執行時資料區概述JVM
- JVM執行時資料區域JVM
- Java 虛擬機器:看完就懂 JVM 架構和執行時資料區 (記憶體區域)Java虛擬機JVM架構記憶體
- Hotspot VM 執行時資料區記憶體結構劃分HotSpot記憶體
- 自動記憶體管理機制_執行時資料區域記憶體
- Java-JVM-執行時資料區JavaJVM
- JVM——04執行時資料區(2)JVM
- JVM(二)-記憶體區域之執行緒私有區域JVM記憶體執行緒
- JVM詳解(三)——執行時資料區JVM
- JVM學習-執行時資料區域JVM
- JVM-記憶體區域與OOMJVM記憶體OOM
- 探索JVM的垃圾回收(堆記憶體)JVM記憶體
- JVM的記憶體區域JVM記憶體
- JVM詳解(四)——執行時資料區-堆JVM
- JVM——【執行時資料區】程式計數器JVM
- JVM 記憶體區域JVM記憶體
- 【JVM記憶體區域】JVM記憶體
- JVM(2)-Java記憶體區域與記憶體溢位異常JVMJava記憶體溢位
- 深入理解JVM虛擬機器-JVM記憶體區域與記憶體溢位JVM虛擬機記憶體溢位
- 虛擬機器系列 | JVM執行時資料區虛擬機JVM
- JVM虛擬機器-執行時資料區概述JVM虛擬機
- JVM系列(二) - JVM記憶體區域JVM記憶體
- JVM系列之一 JVM的基礎概念與記憶體區域JVM記憶體
- 從零開始JVM(一):初探JVM執行時資料區域JVM
- [轉載] Java直接記憶體與堆記憶體Java記憶體
- JVM的記憶體區域劃分JVM記憶體
- Java 執行時的記憶體劃分Java記憶體
- JVM-執行時資料區之PC暫存器JVM
- JVM的特性,透過程式碼來揭秘執行時資料區JVM