JVM記憶體結構、記憶體回收那點事,jvm記憶體回收

來源:互聯網
上載者:User

JVM記憶體結構、記憶體回收那點事,jvm記憶體回收

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

 

一、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虛擬機器棧幀中的引用Object Storage Service的是控制代碼池中的引用地址,控制代碼池中存放著到執行個體對象的指標和到物件類型資料的指標分別指向堆中的執行個體,和方法區中物件類型資料。對象的類型資料指對象實現的介面,繼承的父類,對象的類型等 方式二:直接指標的方式 直接指標的方式:虛擬機器棧幀中儲存的是對象執行個體的地址,而到物件類型資料的指標則存放在對象的執行個體資料中 兩種對象訪問方式的比較:1使用控制代碼的方式,不論對象的執行個體資料還是類型資料都存在堆中的控制代碼池,當對象執行個體位置改變(記憶體回收時經常發生)則需要修改指向執行個體的指標,修改控制代碼池中的引用,而虛擬機器棧中的引用則不用修改2可以看出,圖一使用3個指標,圖二使用2個指標,明顯少了一次定址,所以它的明顯優勢就是訪問速度快,減少了一次指標定位的時間開銷,程式執行時對象的訪問非常頻繁,微小的開銷將變得非常可觀。HotSpot虛擬機器採用的是第二種訪問方式。  二、java中的記憶體回收策略首先要問幾個問題,如何判斷一個對象已經死亡?是否對象只要被引用就沒有死亡就不會被回收?什麼是根搜尋演算法?請看下面一段程式:

public class ReferenceCountGc {

    public Object field = null;

    public static void main(String[] args) {

        ReferenceCountGc objA = new ReferenceCountGc();

        ReferenceCountGc objB = new ReferenceCountGc();

        objA.field = objB;

        objB.field = objA; 

        objA = null;

        objB = null;    

        System.gc();

    }

}

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

引用計數方法:給對象一個計數器,當對象被引用時就+1,釋放掉引用-1,當為0時及不會再被引用。但引用計數方法的Bug是無法解決對象循環參考的問題,但並不是此演算法沒有用武之地,在很多情境下會使用到這個演算法。但java的記憶體回收並沒有使用。上面的程式如果使用的是引用計數演算法則不會被回收,但虛擬機器卻使用根搜尋演算法。

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

由此來看看,對象死後,記憶體回收演算法;

標記-清除演算法                                  複製演算法
         

灰色矩形框為可回收對象,標記-清除演算法就是把可回收的對象進行標記,標記到一定次數則清除掉。可以明顯看出,該演算法的弊端是會產生大量的磁碟片段,沒有一整片連續的空間,當遇到佔用連續的記憶體空間較多的對象時,由於記憶體放不下該對象,會提前進行記憶體回收,致使虛擬機器記憶體回收頻繁,影響效能

為了規避標記清除演算法的弊端,出現了複製演算法,複製演算法將記憶體一份為二,記憶體回收時將使用的記憶體中的存活對象,拷貝到另一半記憶體中,然後把左側記憶體地區完全清除掉,只是示範了複製演算法,但並非一分為二,使用和保留的空間是1:1,可以根據實際情況對虛擬機器參數進行調整。此演算法的弊端是要保留記憶體空間,會將可用記憶體變少。

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

     

標記整理演算法:綠色和藍色地區都代表存活對象,當進行記憶體回收時把存活對象依次移到最左邊,移動後將其餘記憶體空間清空。

分代收集演算法:,其實就是沒有演算法。。。把以上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進行設定,不論是調整空間大小,還是設定對象的年齡進入老年代,如

 

三、我們開發中應注意的問題

從虛擬機器上可以看出,主要是避免full GC的次數,減少朝生夕死的大對象,對虛擬機器記憶體進行最佳化,在日常開發中寫程式的主要注意的是

不要使用長字串 如:String x = new String(“XXXXXXXXXXXX”) StringBuffer stringBuffer = new StringBuffer()StringBuffer對象的append()方法。當然,考慮到安全執行緒問題,使用StringBuilder.

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.