從JVM內存管理的角度談談靜態方法和靜態屬性 和 java對象引用與JVM自動內存管理

試着從JVM的內存管理原理的角度來談一下靜態方法和靜態屬性的問題,不對的地方請指正。 (joezheng123.javaeye.com/blog/264695)
JVM的內存分爲兩部分:stack和heap : 

stack(棧)是JVM的內存指令區。stack管理很簡單,push一定長度字節的數據或者指令,stack指針壓棧相應的字節位移;pop一定字節長度數據或者指令,stack指針彈棧。stack的速度很快,管理很簡單,並且每次操作的數據或者指令字節長度是已知的。所以Java 基本數據類型,Java 指令代碼,常量都保存在stack中。 

heap (堆)是JVM的內存數據區。heap 的管理很複雜,每次分配不定長的內存空間,專門用來保存對象的實例。在heap 中分配一定的內存來保存對象實例,實際上也只是保存對象實例的屬性值,屬性的類型和對象本身的類型標記等,並不保存對象的方法(方法是指令,保存在stack中),在heap 中分配一定的內存保存對象實例和對象的序列化比較類似。而對象實例在heap 中分配好以後,需要在stack中保存一個4字節的heap 內存地址,用來定位該對象實例在heap 中的位置,便於找到該對象實例。 

由於stack的內存管理是順序分配的,而且定長,不存在內存回收問題;而heap 則是隨機分配內存,不定長度,存在內存分配和回收的問題;因此在JVM中另有一個GC進程,定期掃描heap ,它根據stack中保存的4字節對象地址掃描heap ,定位heap 中這些對象,進行一些優化(例如合併空閒內存塊什麼的),並且假設heap 中沒有掃描到的區域都是空閒的,統統refresh(實際上是把stack中丟失了對象地址的無用對象清除了),這就是垃圾收集的過程。 

我們首先要搞清楚的是什麼是數據,什麼是指令?然後要搞清楚對象的方法和對象的屬性分別保存在哪裏? 

爲了便於描述,我簡單的統稱: 

1)方法本身是指令的操作碼部分,保存在stack中; 

2)方法內部變量作爲指令的操作數部分,跟在指令的操作碼之後,保存在stack中(實際上是簡單類型保存在stack中,對象類型在stack中保存地址,在heap 中保存值); 

上述的指令操作碼和指令操作數構成了完整的Java 指令。 

3)對象實例包括其屬性值作爲數據,保存在數據區heap 中。 

非靜態的對象屬性作爲對象實例的一部分保存在heap 中,而對象實例必須通過stack中保存的地址指針才能訪問到。因此能否訪問到對象實例以及它的非靜態屬性值完全取決於能否獲得對象實例在stack中的地址指針。 

先分析一下非靜態方法和靜態方法的區別: 

非靜態方法有一個和靜態方法很重大的不同:非靜態方法有一個隱含的傳入參數,該參數是JVM給它的,和我們怎麼寫代碼無關,這個隱含的參數就是對象實例在stack中的地址指針。因此非靜態方法(在stack中的指令代碼)總是可以找到自己的專用數據(在heap 中的對象屬性值)。當然非靜態方法也必須獲得該隱含參數,因此非靜態方法在調用前,必須先new一個對象實例,獲得stack中的地址指針,否則JVM將無法將隱含參數傳給非靜態方法。 

而靜態方法無此隱含參數,因此也不需要new對象,只要class文件被ClassLoader load進入JVM的stack,該靜態方法即可被調用。當然此時靜態方法是存取不到heap 中的對象屬性的。 

總結一下該過程:當一個class文件被ClassLoader load進入JVM後,方法指令保存在stack中,此時heap 區沒有數據。然後程序技術器開始執行指令,如果是靜態方法,直接依次執行指令代碼,當然此時指令代碼是不能訪問heap 數據區的;如果是非靜態方法,由於隱含參數沒有值,會報錯。因此在非靜態方法執行前,要先new對象,在heap 中分配數據,並把stack中的地址指針交給非靜態方法,這樣程序技術器依次執行指令,而指令代碼此時能夠訪問到heap 數據區了。 

再說一下靜態屬性和動態屬性: 

前面提到對象實例以及動態屬性都是保存在heap 中的,而heap 必須通過stack中的地址指針才能夠被指令(類的方法)訪問到。因此可以推斷出:靜態屬性是保存在stack中的,而不同於動態屬性保存在heap 中。正因爲都是在stack中,而stack中指令和數據都是定長的,因此很容易算出偏移量,也因此不管什麼指令(類的方法),都可以訪問到類的靜態屬性。也正因爲靜態屬性被保存在stack中,所以具有了全局屬性。 

總結一下:靜態屬性保存在stack指令內存區,動態屬性保存在heap 數據內存區。

補充:1.在java中有一個字符常量的池, 專用來存儲常量

       2.基本類型是保存在棧中,例如:

       int a=9; 先在棧中尋找是否有這個值,有的話將引用指向它,沒有的話在上面創建這個值,接着把引用指向它
       3.

      棧的存取速度較快 僅次於位於cpu中的寄存器 

      但位於其中的數據大小和生存期必須是確定的 缺乏了靈活性 一般用於存儲聲明的變量

      堆由於可動態分配內存 使其速度慢於棧 一般用於開闢對象空間 

      還由於生存期不必事先告訴編譯器 事後得由gc收集

 

 

Java中非靜態方法是否共用同一塊內存? 

將某 class 產生出一個 instance 之後,此 class 所有的 instance field 都會新增一份,那麼所有的 instance method 是否也會新增一份?答案是不會,我們用field表示字段,用method表示方法,那麼加上static區分後就 有四種:

class field:有用static修飾的field 
class method:有用static修飾的method 
instance field:沒有用static修飾的field 
instance method:沒有用static修飾的method

那麼他們在內存中的表示爲:

class field:共用一塊記憶體 
class method:共用一塊記憶體 
instance field:隨着每個instance各有一塊記憶體 
instance method:共用一塊記憶體

如果instance method也隨着instance增加而增加的話,那內存消耗也太大了,爲了做到共用一小段內存,Java是根據this關鍵字做到的,比如:instance1.instanceMethod(); instance2.instanceMethod(); 在傳遞給對象參數的時候,Java編譯器自動先加上了一個this參數,它表示傳遞的是這個對象引用,雖然他們兩個對象共用一個方法,但是他們的方法中所產生的數據是私有的,這是因爲參數被傳進來變成call stack內的entry,而各個對象都有不同call stack,所以不會混淆。其實調用每個非static方法時,Java編譯器都會自動的先加上當前調用此方法對象的參數,有時候在一個方法調用另一個方法,這時可以不用在前面加上this的,因爲要傳遞的對象參數就是當前執行這個方法的對象。

爲什麼靜態方法中不能調用非靜態方法?這是因爲靜態方法直接跟class相關,調用此方法的時候是類直接調用的,而不是對象,所以Java編譯器就沒有對象參數可以傳遞,這樣,如果你在靜態方法內部調用非靜態方法,那麼Java編譯器怎麼判斷這個非靜態方法是哪個對象調用的?對吧,所以Java編譯器就會報錯,但是也不是絕對的,Java編譯器是隱式的傳遞對象參數,那麼我們總可以顯示的傳遞對象參數吧,如果我們把某個對象的引用傳遞到static方法裏,然後通過這個引用就可以調用非靜態方法和訪問非靜態數據成員了。

 

解析Java對象引用與JVM自動內存管理

對象引用應用程序設計接口是JDKTM1.2中新定義的。該應用程序設計接口允許應用程序以對象引用的方式與JVM的內存管理器進行交互。當應用程序需管理大量內存對象或者在新的Java對象創建之前需刪除原有對象時,Java對象引用應用程序設計接口具有相當大的用途,例如: 

● 基於Web的應用程序常常要求顯示大量圖片,當用戶離開某一Web頁時,往往不能確定是否能夠順利的返回。在這種程序中,應用Java對象引用API可以創建這樣一個環境,即當堆內存以最小程度運行時,內存管理器創建對象。當用戶返回時,應用程序就會重新載入已經創建的圖片。 

● 應用對象引用隊列可以創建這樣一個環境,當通過對象引用獲得某一對象時,應用程序得到通知。然後,應用程序就可以對相關對象進行清除操作,同時使這些對象在內存管理器中合法化。 


內存管理器的工作機制


下面將首先介紹未嵌入引用對象時內存管理器的工作機制,然後討論引用對象加入之後Java堆發生的變化。 

內存管理器的作用就是識別程序中不再使用的對象,並且回收其內存。 

一個Java應用程序由一系列線程組成,每個線程執行一系列方法,而每個方法通過參數或局部變量來引用對象。這些引用屬於引用集合中的一部分,直接進入應用程序。另外,引用集合中還包括類庫中定義的靜態引用變量,以及通過Java本地接口(JNI)API獲得的引用。引用集合中的所有引用對象都可以被當前應用程序獲取,而不必被回收。同樣地,這些對象可能包含對其它對象的引用,也可以被應用程序獲取,依此類推。Java堆中的其它對象視爲不可獲取的,而所有這些不可獲取的對象在內存管理中也是合法的。如果一個不可獲取的對象使用finalize()方法,任務就交給了對象所調用的收尾器(finalizer)。在內存回收期間,不具有收尾器的不可獲取對象和已經調用收尾器的對象被簡單回收。 

內存回收的算法是不斷變化的,共性的方面是從引用集合中識別可獲取的對象以及回收被其它對象佔據的內存空間。 

加入引用對象之後的引用與常規引用的區別在於,引用對象中的引用專門由內存管理器來處理。引用對象封裝了其它一些對象的引用,我們稱之爲指示對象。在引用對象創建的同時,也就定義了該引用對象的指示對象。 

根據應用程序要求,對象可以是強引用(strong references)、次引用(soft references)、弱引用(weak references)、虛引用(phantom references)的任意組合。爲了確定對象的可獲取程度,JVM內存管理器從引用集合出發遍尋堆中所有到對象的路徑。當到達某對象的任意路徑都不含有引用對象時,則稱該對象具有強獲取能力;當路徑中含有一個或幾個引用對象時,根據內存管理器所查詢的引用對象的類型分別歸爲次獲取、弱獲取、虛獲取。 

另外,對象引用API中還定義了引用對象隊列(java.lang.ref.ReferenceQueue),這是內存管理器對引用對象進行管理的一種簡單數據結構。值得注意的是,在進行引用對象定義時,要求phantom reference對象必須產生於一個引用對象隊列,而soft reference和weak reference對象則無此限制,如: 

ReferenceQueue queue = new ReferenceQueue();
PhantomReference pr = new PhantomReference(object, queue);

Soft References 應用實例


下面以在基於web的應用程序中使用soft references爲例,來說明Java對象引用與JVM的內存管理器進行交互的原理。 

當用戶打開某一web頁時,applet代碼獲得圖片並且得到顯示。如果在代碼中同時創建了該圖片對象的soft references,那麼當用戶離開該web頁時,內存管理器對圖片所分配的內存是否回收做出選擇。當用戶返回該web頁時,在applet代碼中使用SoftReference.get方法就會得到圖片才內存中是否仍存在的消息。如果在內存管理器中未創建該圖片,在web頁上會很快得到顯示;否則,applet代碼就會重新獲取。 

下面是Example.java的完整源代碼。 



import java.awt.Graphics;import java.awt.Image;import java.applet.Applet;import java.lang.ref.SoftReference;public class Example extends Applet { SoftReference sr = null; public void init() { System.out.println("Initializing"); } public void paint(Graphics g) { Image im = (sr == null) ? null : (Image)(sr.get()); if (im == null) { System.out.println("Fetching image"); im = getImage(getCodeBase(),"yundong.gif"); sr = new SoftReference(im); } System.out.println("Painting"); g.drawImage(im, 25, 25, this); g.drawString("運動之美",20,20); im = null; /* Clear the strong reference to the image */ } public void start() { System.out.println("Starting"); } public void stop() { System.out.println("Stopping"); }}


在上面的代碼中,對象image是一個圖片對象,傳遞給一個SoftReference對象sr。其中image對象是sr的指示對象,sr中的引用域是從次引用(soft reference)到 image。 


Weak References分析


對於一個穩定的對象,比如說線程類對象,當需要獲取外部數據時,在程序中應用weak references是非常理想的。如果利用引用隊列創建了某一線程的weak reference,那麼當線程不再具有強獲取能力時,應用程序得到通知,根據此通知,應用程序才能執行相關數據對象的清除工作。 

當內存管理器未發現strong references 和 soft references 時,我們稱對象具有弱獲取能力,即在到達該對象的路徑中至少包含一個weak reference。程序中weak references被清除一段時間後,弱獲取對象被收尾器收集。由此也可以看出,soft reference和weak reference之間的區別在於,應用soft reference時,內存管理器利用算法決定是否創建弱獲取對象,而應用weak reference時,內存管理器必須創建次獲取對象。 


引用對象鏈


當到達某一對象的路徑中含有多個引用對象時,就構成了引用對象鏈。內存管理器按照由強到弱的順序處理引用對象,具體處理步驟包括:Soft references、 Weak references、Finalization、Phantom references和創建對象五個部分。 

當內存管理器未發現前三種對象引用時,我們稱對象具有虛獲取能力,即在到達該對象的路徑中至少包含一個phantom reference。虛引用對象直接被收尾器收集,而不被重新創建。當內存管理器發現只有phantom references時,對象就將處於等候phantom reference狀態,應用程序向引用隊列發出通知,然後對虛引用對象調用clear()方法,將其引用域設置爲null,最後對不可獲取對象執行收集清除處理任務。 

通常,對象所具有的獲取能力與引用對象集合直接路徑中的最弱連接者相同。據此可以看出: 

虛引用對象具有強獲取能力,其它對象均具虛獲取能力; 

(b)中虛引用對象和弱引用對象均具強獲取能力,故次引用對象和對象集合具有若獲取能力; 

(c)中虛引用對象、弱引用對象和次引用對象均具強獲取能力,那麼對象集合則具次獲取能力。 

● 在程序中使用引用對象API不但可以在一定程度上控制內存管理器,實現內存自動管理,還可以提高程序的穩定性和安全性。 

● 引用對象鏈中各個對象的獲取能力與整個鏈相關。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章