JVM內存結構、垃圾回收那點事(轉)

翻看電腦的文件夾,無心看到了9月份在公司作的一次分享,瀏覽了一下"婆婆特",發現本身在ppt上的寫的引導性問題本身也不能確切的回答出來,哎,知識這東西,平時不經常使用的沒些日子就生疏了。因而,本小白決定把他整理下來,不敢班門弄斧,對於入門的同窗能夠快速瞭解虛擬機的大概,有錯誤的地方請批評指正。html

 

1、java虛擬機的內存結構java

   

方法區:線程共享,存放已被虛擬機加載的常量,靜態變量,類信息,即時編譯後的代碼等數據。
 
前不久有個同事聊天的時候提及他們也作了個關於虛擬機的分享,順便考考我,他問我Class文件的存放地點,我說不知道,忘了,很差瞎猜,他說在永久代,我當時將信將疑。查了一下書籍和相關資料,不少人把hotSpot虛擬機的方法區叫作永久代。對於其餘虛擬機如BEA JRockit,IBM J9根本不存在永久帶的概念。因此這樣說其實很不精確,恐怕他本身也只是記住了永久代這個詞,但並不知道永久代指什麼。這給個人啓示是不要被別人只知其一;不知其二,似對似錯的知識信覺得真。也不要不是特別明白就加個「好像」無心識的忽悠了別人。更不要被忽悠後不去查證,下次談論起來,人家若是反駁,本身也會猶豫。
 
在後面講到的java堆,能夠看到堆分爲新生代,老年代,和永久代(方法區),跟上圖有衝突(上圖中方法區與永久代是分開的),這是由於java虛擬機把他做爲堆的一個邏輯部分,但實際上他叫Non-Heap(非堆),目的是與堆區分開來。再細說就是方法區有一個運行時常量池,專門存放編譯器生成的字面量和符號引用。
 
虛擬機棧:虛擬機棧和程序計數器同樣是程序私有的,程序計數器下面會講到。它和線程的生命週期相同。每一個方法被執行時都會建立一個新的棧幀,用於存儲本地變量,動態連接,方法出口,等信息。每個方法調用直至執行完成就對應着一個棧幀從入棧到出棧的過程。一般所說的堆棧信息中的棧便是java虛擬機棧。這個內存區會出現OOM異常
 
本地方法棧:本地方法棧與java虛擬機棧類似,但本地方法棧中執行的是本地方法, 啊?啥叫本地方法?就是經過該方法java程序能夠調用非java程序寫的方法,這也是java能夠跨平臺的一個重要緣由,它屏蔽了一個普通程序員對底層的操做的繁瑣,好比java 多線程中存在一個方法能夠調整線程優先級,其實在windows平臺下,它調用的是操做系統的方法,其餘的平臺它則調用其它平臺相關的方法。具體的怎樣寫一個本地方法,怎樣去編譯,網上搜一搜就會有完整的例子,如同「hello world」 同樣簡單。
 
程序計數器:是一小塊內存,當前線程所執行字節碼的行號指示器,字節碼解釋器就是經過改變計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等功能都須要計數器來完成,這是java運行時區惟一沒有定義OutOfMemory方法的區域。
 
堆:堆是java內存中最大的一塊區域,是被全部線程所共享的一塊區域。幾乎全部的建立的對象都分配在堆。java堆分爲新生代和老年代,細分爲Eden空間,From Survior空間和to Survior空間
 
2)對象是如何進行訪問的?
對象訪問有兩種方式:
方式一:使用句柄的訪問方式
 
 
 句柄的訪問方式,java堆中會劃分一塊內存做爲句柄池,java虛擬機棧幀中的引用對象存儲的是句柄池中的引用地址,句柄池中存放着到實例對象的指針和到對象類型數據的指針分別指向堆中的實例,和方法區中對象類型數據。對象的類型數據指對象實現的接口,繼承的父類,對象的類型等
 
方式二:直接指針的方式
 
直接指針的方式:虛擬機棧幀中存儲的是對象實例的地址,而到對象類型數據的指針則存放在對象的實例數據中
 
兩種對象訪問方式的比較:
1使用句柄的方式,不論對象的實例數據仍是類型數據都存在堆中的句柄池,當對象實例位置改變(垃圾回收時常常發生)則須要修改指向實例的指針,修改句柄池中的引用,而虛擬機棧中的引用則不用修改
2從圖中能夠看出,圖一使用3個指針,圖二使用2個指針,明顯少了一次尋址,因此它的明顯優點就是訪問速度快,減小了一次指針定位的時間開銷,程序執行時對象的訪問很是頻繁,微小的開銷將變得很是可觀。HotSpot虛擬機採用的是第二種訪問方式。
 
2、java中的垃圾回收策略
首先要問幾個問題,如何判斷一個對象已經死亡?是否對象只要被引用就沒有死亡就不會被回收?什麼是根搜索算法?
請看下面一段程序:

public class ReferenceCountGc {程序員

    public Object field = null;算法

    public static void main(String[] args) {windows

        ReferenceCountGc objA = new ReferenceCountGc();數組

        ReferenceCountGc objB = new ReferenceCountGc();安全

        objA.field = objB;多線程

        objB.field = objA; app

        objA = null;性能

        objB = null;    

        System.gc();

    }

}

當程序執行到System.gc()時,是否會回收對象objA和objB?

引用計數方法:給對象一個計數器,當對象被引用時就+1,釋放掉引用-1,當爲0時及不會再被引用。但引用計數方法的Bug是沒法解決對象循環引用的問題,但並非此算法沒有用武之地,在不少場景下會使用到這個算法。但java的垃圾回收並無使用。上面的程序若是使用的是引用計數算法則不會被回收,但虛擬機卻使用根搜索算法。

根搜索算法即設定一個對象稱爲GC root ,從這個節點向下進行搜索,搜索所走過的路徑稱爲引用鏈,當GC root沒有任何引用鏈相連即在圖論中不可到達,則證實此對象不可用

由此來看看,對象死後,垃圾回收算法;

標記-清除算法                                  複製算法
         

灰色矩形框爲可回收對象,標記-清除算法就是把可回收的對象進行標記,標記到必定次數則清除掉。從圖中能夠明顯看出,該算法的弊端是會產生大量的磁盤碎片,沒有一整片連續的空間,當遇到佔用連續的內存空間較多的對象時,因爲內存放不下該對象,會提早進行垃圾回收,導致虛擬機垃圾回收頻繁,影響性能

爲了規避標記清除算法的弊端,出現了複製算法,複製算法將內存一份爲二,垃圾回收時將使用的內存中的存活對象,拷貝到另外一半內存中,而後把左側內存區域徹底清除掉,上圖只是演示了複製算法,但並不是一分爲二,使用和保留的空間是1:1,能夠根據實際狀況對虛擬機參數進行調整。此算法的弊端是要保留內存空間,會將可用內存變少。

標記整理算法:                              分代收集算法:

     

標記整理算法:綠色和藍色區域都表明存活對象,當進行垃圾回收時把存活對象依次移到最左邊,移動後將其他內存空間清空。

分代收集算法:如圖,其實就是沒有算法。。。把以上3種算法進行綜合運用,前面說過堆是有劃分的,簡單分爲新生代和老年代,分代收集就是根據不一樣代的特色應用不一樣的垃圾回收算法。

 

3、java內存分配

java的自動內存管理解決了兩個問題,一是給對象分配內存,二是回收分配給對象的內存,前面咱們講了回收分配給對象的內存,下面咱們來看看給對象分配內存

java堆分爲新生代,老年代(終身代)和永久代。

新生代和老年代的默認比值爲1:2即新生代佔堆總內存的1/3,能夠經過 –Xms(初始堆大小)、-Xmx (最大堆大小)來改變。

新生代又分爲Eden區、From survivor區、To survivor區,Eden:From:To 爲 8:1:1,能夠經過–XX:SurvivorRatio 參數設定。

JVM每次只會使用From區和survivor區中的一塊(Form survivor或To survivor),很明顯是爲了在垃圾回收的時候將存活對象移到另一個空閒的survivor區(若是空間足夠,不然直接進入老年代),所以垃圾回收所使用的算法是複製算法。

新生的對象會被分配到新生代,新生代的特色是朝生夕死,對象存活的時間短,迭代快。發生在新生代的垃圾回收叫minor GC,minor Gc進行的相對頻繁,消耗較full GC少,而Full GC是發生在老年代的垃圾回收,採用的是標記-清除算法。老年代進行一次垃圾回收比新生代話費時間長,進行的也沒有老年代頻繁,同時要儘可能減小老年代的垃圾回收,由於回收速度慢且在進行時影響虛擬機性能,使虛擬機響應變慢,最直接的感受是應用程序的響應速度變慢。

什麼狀況下,對象會被分配到老年代?

 從上圖中明顯能夠到3點:

大對象:什麼是大對象,大對象就是須要連續佔用不少內存空間的對象好比很長的字符串和數組。虛擬機經過-XX:PretrnureSizeThreshold設計大過指定大小的對象之間進入老年代,即時沒有超過指定大小,在進行minor GC時一般也會由於survivor區不夠用而被轉移到老年代。

經過設置MaxTenuringThreshold參數: 這個參數是進行年齡設置的,超過這個年齡的會進入老年代。什麼是年齡?在新生代進行minor GC的時候,每進行一次,存活下來的對象年齡+1,默認年齡超過15的會進入老年代。15這個數值也能夠經過MaxTenuringThreshold參數改變。

Survivor空間中相同年齡全部對象大小的總和大於survivor空間的一半,全部大於或者等於該年齡的對象直接進入老年代,這句話比較拗口,但就是這個意思,看不懂的多看幾遍就行了。。。

 

前面講了這麼多參數,如何設置虛擬機參數?能夠經過IDE進行設置,不管是調整空間大小,仍是設置對象的年齡進入老年代,以下圖

 

3、咱們開發中應注意的問題

從虛擬機上能夠看出,主要是避免full GC的次數,減小朝生夕死的大對象,對虛擬機內存進行優化,在平常開發中寫程序的主要注意的是

不要使用長字符串 如:String x = new String(「XXXXXXXXXXXX」) StringBuffer stringBuffer = new StringBuffer()StringBuffer對象的append()方法。固然,考慮到線程安全問題,使用StringBuilder.

http://www.cnblogs.com/jarman/p/5042036.html
相關文章
相關標籤/搜索