標籤:java 記憶體 虛擬機器 最佳化 程式
自工作以上,程式因為代碼越寫越多,變得越來越臃腫,效率也會變得越來越低,於是我總喜歡不斷去最佳化程式結構外,記憶體最佳化和效能調優。
要對Java程式進行記憶體最佳化和效能調優,需要瞭解虛擬機器的內部原理,瞭解Java虛擬機器的好處除了上述提及兩點好處。從更深一點的技術層面上看,瞭解Java虛擬機器的規範和實現,將更加有助於我們編寫高效、穩定的Java代碼。比如,假如瞭解Java虛擬機器的記憶體模型,瞭解虛擬機器的記憶體回收機制,那麼我們就不會過分依賴它,而會在需要的時候顯式的"釋放記憶體"(Java代碼不能顯式釋放記憶體,但是可以通過釋放對象引用告知記憶體回收行程回收該對象需要被回收),以降低不必要的記憶體消耗;假如我們瞭解Java棧的工作原理,那麼我們就可以通過減少遞迴層數,減少迴圈次數來降低堆疊溢位的風險。可能對於應用開發人員來說,可能不會直接去涉及這些Java虛擬機器底層實現的工作,但是瞭解這些背景知識,或多或少,都會對我們寫的程式產生潛移默化的好的影響。
本篇文章,將簡明扼要的說明Java虛擬機器的體繫結構和記憶體模型,如有用詞不妥或解釋不準確之處,請不吝指正,深感榮幸!
JAVA 虛擬機器體繫結構
650) this.width=650;" src="http://s3.51cto.com/wyfs02/M02/6E/0B/wKioL1VyotSAjt7TAAD7miYMhjA634.jpg" title="11.jpg" alt="wKioL1VyotSAjt7TAAD7miYMhjA634.jpg" />
類裝載子系統
Java虛擬機器有兩種類裝載器,分別是啟動類裝載器和使用者自訂裝載器。
通類裝載子系統通過類的全限定名(包名和類名,網路裝載還包括 URL)將 Class 裝載進運行時資料區。對於每一個被裝載的類型,Java虛擬機器都會建立一個java.lang.Class類的執行個體來代表該類型,該執行個體被放在記憶體中的堆區,而裝載的類型資訊則位於方法區,這一點和所有其他對象都是一樣的。
類裝載子系統在裝載一個類型前,除了要定位和匯入對應的二進位class檔案外,還要驗證匯入類的正確性,為類變數分配並初始化記憶體,以及解析符號引用為直接引用,這些動作嚴格按照以下順序進行:
裝載——尋找並裝載類型的位元據;
串連——執行驗證,準備以及解析(可選)
驗證 確保被匯入類型的正確性
準備 為類變數分配記憶體,並將其初始化為預設值
解析 把類型中的符號引用轉換為直接應用
方法區
對於每一個被類裝載子系統裝載的類型,虛擬機器都會儲存下列資料到方法區:
類型的全限定名
類型超類的全限定名(java.lang.Object沒有超類)
類型是類類型還是介面類型
類型的存取修飾詞
任何直接超介面的全限定名有序列表
除了上述基本類型資訊,還將儲存如下資訊:
類型的常量池
欄位資訊(包括欄位名、欄位類型、欄位修飾符)
方法資訊(包括方法名、傳回型別、參數的數量和類型、方法修飾符,如果方法不是抽象和本地的,還將儲存方法的位元組碼、運算元棧和該方法棧幀中的局部變數區的大小和異常表)
常量以外的所有類變數(其實就是類的靜態變數,因為靜態變數是所有執行個體共用的,且與類型直接相關,所以他們是類一級的變數,作為類的成員被儲存在方法區)
一個到類ClassLoader的引用
Java代碼 650) this.width=650;" class="star" src="http://yshjava.iteye.com/images/icon_star.png" alt="收藏代碼" style="border:0px;" />
//返回的就是剛才儲存的ClassLoader引用
String.class.getClassLoader();
一個到Class類的引用
Java代碼 650) this.width=650;" class="star" src="http://yshjava.iteye.com/images/icon_star.png" alt="收藏代碼" style="border:0px;" />
//將返回剛才儲存的Class類的引用
String.class;
注意,方法區也是可以被記憶體回收行程回收的,當一個類型不再被引用且方法區記憶體不足時,虛擬機器將卸載該類型,回收記憶體。
堆
Java程式在運行時建立的所有類執行個體或數組都放在同一個堆中,而每一個Java虛擬機器也只有一個堆空間,所有線程將共用這一個堆(這就是一個多線程的Java程式會產生對象訪問的同步問題的原因了)。
由於每一種Java虛擬機器都有對虛擬機器規範的不同實現,所以我們可能不知道每一種Java虛擬機器在堆中是以何種形式表示對象執行個體的,不過我們可以通過下面這可能的實現來一窺端倪:
650) this.width=650;" src="http://s3.51cto.com/wyfs02/M00/6E/0B/wKioL1VyovezeDdpAAFNZwXAWOo243.jpg" title="22.jpg" alt="wKioL1VyovezeDdpAAFNZwXAWOo243.jpg" />
程式計數器
對於運行中的Java程式而言,每一個線程都有自己的PC(程式計數器)寄存器,它是在該線程啟動時建立的,大小為一個字長,用來儲存需要被執行的下一行代碼的位置。
Java棧
每一個線程都有一個Java棧,以棧幀為單位儲存線程的運行狀態。虛擬機器對Java棧的操作有兩種:壓棧和出棧,二者都已幀為單位。棧幀儲存了傳入參數、局部變數、中間運算結果等資料,在方法完成時被彈出,然後釋放。
看一下兩個局部變數相加時棧幀的記憶體快照
650) this.width=650;" src="http://s3.51cto.com/wyfs02/M01/6E/0F/wKiom1VyoZ_QrIQrAADSRQFh6Mg605.jpg" title="33.jpg" alt="wKiom1VyoZ_QrIQrAADSRQFh6Mg605.jpg" />
本地方法棧
這是 Java 叫用作業系統本地庫的地方,用來實現 JNI(Java Native Interface,Java 本地介面)
執行引擎
Java虛擬機器的核心,控制裝入 Java 位元組碼並解析;對於運行中的Java程式而言,每一個線程都是一個獨立的虛擬機器執行引擎的執行個體,從線程生命週期的開始到結束,他要麼在執行位元組碼,要麼在執行本地方法。
本地介面
串連了本地方法棧和作業系統庫。
注:文中所有提到"Java虛擬機器"的地方都是指"JavaEE和JavaSE平台的Java虛擬機器規範"。
本文出自 “JAVA學習視頻教程” 部落格,請務必保留此出處http://10239772.blog.51cto.com/10229772/1659194
探討Java虛擬機器之虛擬機器體繫結構