一. 概述
對於C++程序開發人員來說,每一塊內存都需要自己來進行維護,需要管理每個對象的生老病死。對於每個對象的new操作,都需要
有對應的Delete/free操作來保證內存的釋放,否則有可能就會產生內存泄漏。
對於java程序的開發人員則不需要這樣具體關心內存的分配和使用。java自帶了內存管理機制,會自動的對內存進行管理,不易出
現內存泄漏的情況。但是程序開發人員需要對java的內存機制有所瞭解,才能在出現內存問題的時候有效進分析。
二. 運行數據模型
JAVA虛擬機在運行時會將內存分爲若干個區域,每個區域用來存儲特定功能的數據,主要分爲下述幾個區域:
2.1 程序計數器
程序計數器是一塊較小的區域,用來作爲當前線程執行的編碼行號指示器。在虛擬機的概念模型裏(僅是概念模型,各種虛擬
機可能會通過一些更高效的方式去實現),字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指
令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。程序技術器是每個線程所專有的,在併發
多線程的執行過程中,每個線程都有一個程序計數器來指定當前線程該執行那行的代碼。
2.2 java虛擬機棧
2.3 本地方法棧
本地方法棧和虛擬機棧基本上一樣,只不過虛擬機棧是用來存儲java本地方法的調用模型,而本地方法棧使用了管理native方
法的調用模型。虛擬機規範中對本地方法棧中的方法使用的語言、使用方式與數據結構並沒有強制規定,因此具體的虛擬機可以自
由實現它。甚至有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二爲一。與虛擬機棧一樣,本地方法棧區
域也會拋出StackOverflowError和OutOfMemoryError異常。
2.4 虛擬機堆
虛擬機堆和上述幾個模型都不太一樣,它在虛擬機運行內存模型中只有一個,所有線程所共用的。代碼所涉及的內存回收和分配
操作都是在堆中進行的。
java虛擬機對內存的回收操作主要是通過GC操作進行的,這個過程也主要發生在堆中,因此虛擬機堆有被稱爲GC堆。目前主流的
java虛擬機都是通過分代回收的方式對堆的內存進行回收的。
在java虛擬機中將堆分爲新生代和老生代兩個主要的區,新生代主要用來存放剛產生的對象,老生代用來存放存活了很長時間的
對象,一般是經過幾次新生代回收還存在的對象就會放到老生代。
新生代按照8:1:1的比例分爲一個eden區和兩個survivor(survivor0,survivor1)區,新創建的對象都是存在eden區,如果eden區
存放滿則會進行回收,將存活的對象放到survivor0區,清空eden區。而當survivor0區存放滿的時候,進行回收,將還存活的對象
放到survivor1區,將survivor0區清空,如果回收了好幾次還存活的對象則會放到老生代區。如果老生代去存滿則會觸發FullGC,
對全局的內存進行回收,FullGC會暫停程序的運行,對程序的性能影響較大,應該儘量避免FullGC的發生。
2.5 方法區
方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、
即時編譯器編譯後的代碼等數據。雖然Java虛擬機規範把方法區描述爲堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非
堆),目的應該是與Java堆區分開來。
對於習慣在HotSpot虛擬機上開發和部署程序的開發者來說,很多人願意把方法區稱爲“永久代”(Permanent Generation),本質
上兩者並不等價,僅僅是因爲HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至方法區,或者說使用永久代來實現方法區而已。於
其他虛擬機(如BEA JRockit、IBM J9等)來說是不存在永久代的概念的。即使是HotSpot虛擬機本身,根據官方發佈的路線圖信
息,現在也有放棄永久代並“搬家”至Native Memory來實現方法區的規劃了。