1. 程式人生 > >深入理解java虛擬機器(一)

深入理解java虛擬機器(一)

前言 

本篇主要講述java記憶體區域的劃分。下面直接進入正題。

概述

java虛擬機器就是在真實物理機上虛擬出來的一臺計算機,java語言有一個特點就是可以跨平臺,其中java起著關鍵作用。這是因為它遮蔽與平臺相關的資訊,java原始檔經過編譯程式編譯後生成位元組碼檔案。然後由jvm解釋執行成可以被計算機識別的機器指令。

在jvm執行過程,我們經常會遇到記憶體溢位或者是記憶體洩露的情況,所以有 必要了解jvm的記憶體結構。這樣才能更好分析問題,解決問題。

記憶體溢位:程式向虛擬機器(JVM)申請記憶體,但是虛擬機器(JVM)卻沒有足夠的記憶體分配給申請者,這個時候就會出現記憶體溢位。

記憶體洩露:程式向虛擬機器(JVM)申請記憶體使用,使用完後不歸還,其他程式也無法使用這塊記憶體,最終記憶體洩露會變成記憶體溢位。

正文

按照Java虛擬機器規範的規定,JVM自動管理的記憶體將會包括以下幾個執行時資料區域。

程式計數器

當執行一條指令時,首先需要根據PC中存放的指令地址,將指令由記憶體取到指令暫存器中,此過程稱為“取指令”。與此同時,PC中的地址或自動加1或由轉移指標給出下一條指令的地址。此後經過分析指令,執行指令。完成第一條指令的執行,而後根據PC取出第二條指令的地址,如此迴圈,執行每一條指令。

程式計數器是計算機處理器中的暫存器,它包含當前正在執行的指令的地址(位置)。當每個

指令被獲取,程式計數器的儲存地址加一。在每個指令被獲取之後,程式計數器指向順序中的下一個指令。當計算機重啟或復位時,程式計數器通常恢復到 零。

簡單地的說就是程式計數器是用於存放(記錄)下一條指令所在單元的地址的地方。

如果說執行緒正在執行一個方法,那麼計數器記錄的就是虛擬機器(JVM)正在執行位元組碼指令的地址。程式計數器佔用的記憶體很小,在java執行時資料區是唯一一塊不會出現記憶體溢位的區域。

虛擬機器棧

虛擬機器棧是由幀棧組成。屬於執行緒私有。一個幀棧對應一個方法。每個方法在執行的同時,就會建立一個幀棧,用於儲存區域性變量表,運算元棧,動態連線,返回地址等資訊。每一個方法的呼叫和執行完成的過程對應一個幀棧的入棧和出棧的過程(入棧時,進入棧頂,所以先入棧的會後出來,也就是先進後出)。虛擬機器棧中主要的部分是區域性變量表,用來存放方法引數和方法內部定義的區域性變數,其中存放的資料的型別是編譯期可知的各種基本資料型別、物件引用(reference)和(returnAddress)型別(它指向了一條位元組碼指令的地址)。儲存物件引用時,並不會儲存引用物件本身,而是儲存指向引用物件的指標或者引用物件的控制代碼或者是其他能代表引用的物件的位置的資訊。區域性變量表所需的記憶體大小在編譯時期就已經確認了,不受執行時的影響。

本地方法棧

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

個人理解本地方法棧主要是jvm用來和其他語言來打交道的或者是對java語言進行的一種擴充套件。本地方法棧中使用的語言,使用方式,資料結構沒有強制要求。設計者可以各自的需求進行設計已滿足自己的需求。

對於大多數應用來說,堆是java記憶體區域中最大的一塊,且被所有執行緒共有。虛擬機器(JVM)在啟動的時候就會建立這塊區域。

幾乎所有的建立的物件都是在這塊區域分配記憶體。從記憶體回收的角度來說,java堆還可以進行細分為:新年代和老年代,再細分一點分為:Eden區,From Surivor區,To Survivor區等。

方法區

方法區和堆一樣被所有執行緒共享,用於存放已經被虛擬機器(JVM)載入的類資訊,靜態變數,常量以及編譯器編譯後的程式碼等資訊。對於習慣在HotSpot虛擬機器上開發和部署程式的開發人員來說,很多人把方法區稱為永久代,其實這兩種說法並不是等價的,因為HotSpot虛擬機器設計團隊僅僅是將GC分代收集從堆區擴充套件到了方法區,這樣垃圾收集器就可以像管理堆一樣來管理方法區了,而不用專門設計的程式程式碼來管理方法區的這塊區域。

執行時常量池

執行時常量池屬方法區的一部分,用於存放編譯期生成的各種字面值和符號引用,這部分內容將在類載入的時候進入方法區的執行時常量池。

Java虛擬機器(JVM)對class檔案的內容有嚴格的要求,每個位元組儲存什麼型別的資料必須符合規範才能被虛擬機器認可,載入,執行。而執行時常量池也沒規範,虛擬機器(JVM)供應商可以根據需求來實現這部分記憶體區域。除了儲存class檔案中的符號引用,還會把翻譯出來直接引用也會儲存在執行時的常量池中,而且執行時常量池與class常量池相比還具有動態性,java語言並不要求只有編譯時期才能長生常量,也就是說並非只有預先放入到class檔案常量池中的內容才能放入到常量池,執行期生成的常量也可以進入常量池。其中使用最多的就是java.lang.String 的intern()方法。

直接記憶體(瞭解)

直接記憶體區域並不是執行時資料區的一部分,也不是Java虛擬機器規範定義記憶體區域,但是這塊區域的使用的也比較頻繁。JDK1.4新加入了NIO類,引入了一種基於通道和緩衝的I/O方式,它可以使用本地函式庫直接分配堆外記憶體,然後通過一個存放在堆中的DirectoryByteBuffer物件作為這塊記憶體的引用來進行操作。這樣避免了在java堆和native堆中來回的複製資料。

本篇到此結束了,如果有問題,還望大佬們不吝賜教。

下篇將講解記憶體分配策略以及垃圾回收機制,點選進入——深入理解java虛擬機器(二)