成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

深入理解Java虛擬機(jī)(自動內(nèi)存管理機(jī)制)

yck / 880人閱讀

摘要:看來還是功力不夠,索性拆成了六篇文章,分別從自動內(nèi)存管理機(jī)制類文件結(jié)構(gòu)類加載機(jī)制字節(jié)碼執(zhí)行引擎程序編譯與代碼優(yōu)化高效并發(fā)六個方面來做更加細(xì)致的介紹。本文先說說虛擬機(jī)的自動內(nèi)存管理機(jī)制。在類加載檢查通過后,虛擬機(jī)將為新生對象分配內(nèi)存。

歡迎關(guān)注微信公眾號:BaronTalk,獲取更多精彩好文!

書籍真的是常讀常新,古人說「書讀百遍其義自見」還是蠻有道理的。周志明老師的這本《深入理解 Java 虛擬機(jī)》我細(xì)讀了不下三遍,每一次閱讀都有新的收獲,每一次閱讀對 Java 虛擬機(jī)的理解就更進(jìn)一步。因而萌生了將讀書筆記整理成文的想法,一是想檢驗下自己的學(xué)習(xí)成果,對學(xué)習(xí)內(nèi)容進(jìn)行一次系統(tǒng)性的復(fù)盤;二是給還沒接觸過這部好作品的同學(xué)推薦下,在閱讀這部佳作之前能通過我的文章一窺書中的精華。

原想著一篇文章就夠了,但寫著寫著就發(fā)現(xiàn)篇幅大大超出了預(yù)期??磥磉€是功力不夠,索性拆成了六篇文章,分別從自動內(nèi)存管理機(jī)制類文件結(jié)構(gòu)、類加載機(jī)制、字節(jié)碼執(zhí)行引擎、程序編譯與代碼優(yōu)化、高效并發(fā)六個方面來做更加細(xì)致的介紹。本文先說說 Java 虛擬機(jī)的自動內(nèi)存管理機(jī)制。

一. 運(yùn)行時數(shù)據(jù)區(qū)

Java 虛擬機(jī)在執(zhí)行 Java 程序的過程中會把它所管理的內(nèi)存區(qū)域劃分為若干個不同的數(shù)據(jù)區(qū)域。這些區(qū)域都有各自的用途,以及創(chuàng)建和銷毀的時間,有些區(qū)域隨著虛擬機(jī)進(jìn)程的啟動而存在,有些區(qū)域則是依賴線程的啟動和結(jié)束而建立和銷毀。Java 虛擬機(jī)所管理的內(nèi)存被劃分為如下幾個區(qū)域:

程序計數(shù)器

程序計數(shù)器是一塊較小的內(nèi)存區(qū)域,可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機(jī)的概念模型里,字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成?!笇儆诰€程私有的內(nèi)存區(qū)域」

Java 虛擬機(jī)棧

就是我們平時所說的棧,每個方法被執(zhí)行時,都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息。每個方法從被調(diào)用到執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機(jī)棧中從出棧到入棧的過程。「屬于線程私有的內(nèi)存區(qū)域」

局部變量表:局部變量表是 Java 虛擬機(jī)棧的一部分,存放了編譯器可知的基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference 類型,不等同于對象本身,根據(jù)不同的虛擬機(jī)實現(xiàn),它可能是一個指向?qū)ο笃鹗嫉刂返囊弥羔槪部赡苁侵赶蛞粋€代表對象的句柄或者其他與此對象相關(guān)的位置)和 returnAddress 類型(指向了一條字節(jié)碼指令的地址)。
本地方法棧

和虛擬機(jī)棧類似,只不過虛擬機(jī)棧為虛擬機(jī)執(zhí)行的 Java 方法服務(wù),本地方法棧為虛擬機(jī)使用的 Native 方法服務(wù)?!笇儆诰€程私有的內(nèi)存區(qū)域」

Java 堆

對大多數(shù)應(yīng)用而言,Java 堆是虛擬機(jī)所管理的內(nèi)存中最大的一塊,是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一作用就是存放對象實例,幾乎所有的對象實例都是在這里分配的(不絕對,在虛擬機(jī)的優(yōu)化策略下,也會存在棧上分配、標(biāo)量替換的情況,后面的章節(jié)會詳細(xì)介紹)。Java 堆是 GC 回收的主要區(qū)域,因此很多時候也被稱為 GC 堆。從內(nèi)存回收的角度看,Java 堆還可以被細(xì)分為新生代和老年代;再細(xì)一點(diǎn)新生代還可以被劃分為 Eden Space、From Survivor Space、To Survivor Space。從內(nèi)存回收的角度看,線程共享的 Java 堆可能劃分出多個線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer,TLAB)?!笇儆诰€程共享的內(nèi)存區(qū)域」

方法區(qū)

用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)?!笇儆诰€程共享的內(nèi)存區(qū)域」

運(yùn)行時常量池: 運(yùn)行時常量池是方法區(qū)的一部分,Class 文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池(Constant Pool Table),用于存放在編譯期生成的各種字面量和符號引用。

直接內(nèi)存:直接內(nèi)存(Direct Memory)并不是虛擬機(jī)運(yùn)行時數(shù)據(jù)區(qū)的一部分,也不是 Java 虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域。Java 中的 NIO 可以使用 Native 函數(shù)直接分配堆外內(nèi)存,然后通過一個存儲在 Java 堆中的 DiectByteBuffer 對象作為這塊內(nèi)存的引用進(jìn)行操作。這樣能在一些場景顯著提高性能,因為避免了在 Java 堆和 Native 堆中來回復(fù)制數(shù)據(jù)。直接內(nèi)存不受 Java 堆大小的限制。

二. 對象的創(chuàng)建、內(nèi)存布局及訪問定位

前面介紹了 Java 虛擬機(jī)的運(yùn)行時數(shù)據(jù)區(qū),了解了虛擬機(jī)內(nèi)存的情況。接下來我們看看對象是如何創(chuàng)建的、對象的內(nèi)存布局是怎樣的以及對象在內(nèi)存中是如何定位的。

2.1 對象的創(chuàng)建

要創(chuàng)建一個對象首先得在 Java 堆中(不絕對,后面介紹虛擬機(jī)優(yōu)化策略的時候會做詳細(xì)介紹)為這個要創(chuàng)建的對象分配內(nèi)存,分配內(nèi)存的過程要保證并發(fā)安全,最后再對內(nèi)存進(jìn)行相應(yīng)的初始化,這一系列的過程完成后,一個真正的對象就被創(chuàng)建了。

內(nèi)存分配

先說說內(nèi)存分配,當(dāng)虛擬機(jī)遇到一條 new 指令時,首先將去檢查這個指令的參數(shù)是否能夠在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已被加載、解析和初始化。如果沒有,那必須先執(zhí)行相應(yīng)的類加載過程。在類加載檢查通過后,虛擬機(jī)將為新生對象分配內(nèi)存。對象所需的內(nèi)存大小在類加載完成后便可完全確定,為對象分配內(nèi)存空間的任務(wù)等同于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來。

在 Java 堆中劃分內(nèi)存涉及到兩個概念:指針碰撞(Bump the Pointer)、空閑列表(Free List)。

如果 Java 堆中的內(nèi)存絕對規(guī)整,所有用過的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊,中間放著一個指針作為分界點(diǎn)的指示器,那所分配的內(nèi)存就緊緊是把指針往空閑空間那邊挪動一段與對象大小相等的距離,這種分配方式稱為「指針碰撞」。

如果 Java 堆中的內(nèi)存并不是規(guī)整的,已使用的內(nèi)存和空閑的內(nèi)存相互交錯,那就沒辦法簡單的進(jìn)行指針碰撞了。虛擬機(jī)必須維護(hù)一個列表來記錄哪些內(nèi)存是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,并更新列表上的記錄,這種分配方式稱為「空閑列表」。

選擇哪種分配方式是由 Java 堆是否規(guī)整來決定的,而 Java 堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。

保證并發(fā)安全

對象的創(chuàng)建在虛擬機(jī)中是一個非常頻繁的行為,哪怕只是修改一個指針?biāo)赶虻奈恢茫诓l(fā)情況下也是不安全的,可能出現(xiàn)正在給對象 A 分配內(nèi)存,指針還沒來得及修改,對象 B 又同時使用了原來的指針來分配內(nèi)存的情況。解決這個問題有兩種方案:

對分配內(nèi)存空間的動作進(jìn)行同步處理(采用 CAS + 失敗重試來保障更新操作的原子性);

把內(nèi)存分配的動作按照線程劃分在不同的空間之中進(jìn)行,即每個線程在 Java 堆中預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖(Thread Local Allocation Buffer, TLAB)。哪個線程要分配內(nèi)存,就在哪個線程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 時,才需要同步鎖。

初始化

內(nèi)存分配完后,虛擬機(jī)要將分配到的內(nèi)存空間初始化為零值(不包括對象頭),如果使用了 TLAB,這一步會提前到 TLAB 分配時進(jìn)行。這一步保證了對象的實例字段在 Java 代碼中可以不賦初始值就直接使用。

接下來設(shè)置對象頭(Object Header)信息,包括對象是哪個類的實例、如何找到類的元數(shù)據(jù)、對象的 Hash、對象的 GC 分代年齡等。

這一系列動作完成之后,緊接著會執(zhí)行 方法,把對象按照程序員的意圖進(jìn)行初始化,這樣一個真正意義上的對象就產(chǎn)生了。

JVM 中對象的創(chuàng)建過程大致如下圖:

2.2 對象的內(nèi)存布局

在 HotSpot 虛擬機(jī)中,對象在內(nèi)存中的布局可以分為 3 塊:對象頭(Header)、實例數(shù)據(jù)(Instance Data)對齊填充(Padding)

對象頭

對象頭包含兩部分信息,第一部分用于存儲對象自身的運(yùn)行時數(shù)據(jù),比如哈希碼(HashCode)、GC 分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程 ID、偏向時間戳等。這部分?jǐn)?shù)據(jù)稱之為 Mark Word。對象頭的另一部分是類型指針,即對象指向它的類元數(shù)據(jù)指針,虛擬機(jī)通過它來確定對象是哪個類的實例;如果是數(shù)組,對象頭中還必須有一塊用于記錄數(shù)組長度的數(shù)據(jù)。(并不是所有所有虛擬機(jī)的實現(xiàn)都必須在對象數(shù)據(jù)上保留類型指針,在下一小節(jié)介紹「對象的訪問定位」的時候再做詳細(xì)說明)。

實例數(shù)據(jù)

對象真正存儲的有效數(shù)據(jù),也是在程序代碼中所定義的各種字段內(nèi)容。

對齊填充

無特殊含義,不是必須存在的,僅作為占位符。

2.3 對象的訪問定位

Java 程序需要通過棧上的 reference 信息來操作堆上的具體對象。根據(jù)不同的虛擬機(jī)實現(xiàn),主流的訪問對象的方式主要有句柄訪問直接指針兩種。

句柄訪問

Java 堆中劃分出一塊內(nèi)存來作為句柄池,reference 中存儲的就是對象的句柄地址,而句柄中包含了對象實例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息。

使用句柄訪問的好處就是 reference 中存儲的是穩(wěn)定的句柄地址,在對象被移動時只需要改變句柄中實例數(shù)據(jù)的指針,而 reference 本身不需要修改。

直接指針

在對象頭中存儲類型數(shù)據(jù)相關(guān)信息,reference 中存儲的對象地址。

使用直接指針訪問的好處是速度更快,它節(jié)省了一次指針定位的開銷。由于對象訪問在 Java 中非常頻繁,因此這類開銷積少成多也是一項非??捎^的執(zhí)行成本。HotSpot 中采用的就是這種方式。

三. 垃圾回收器與內(nèi)存分配策略

在前面我們介紹 JVM 運(yùn)行時數(shù)據(jù)區(qū)的時候說過,程序計數(shù)器、虛擬機(jī)棧、本地方法棧 3 個區(qū)域隨線程而生,隨線程而死;棧中的棧幀隨著方法的進(jìn)入和退出而有條不紊的執(zhí)行著入棧和出棧的操作。每一個棧幀中分配多少內(nèi)存基本上在數(shù)據(jù)結(jié)構(gòu)確定下來的時候就已經(jīng)知道了,因此這幾個區(qū)域內(nèi)存的分配和回收是具有確定性的,所以不用過度考慮內(nèi)存回收的問題,因為在方法結(jié)束或者線程結(jié)束時,內(nèi)存就跟著回收了。

而 Java 堆和方法區(qū)則不一樣,一個接口中的多個實現(xiàn)類需要的內(nèi)存可能不一樣,一個方法中的多個分支需要的內(nèi)存也可能不一樣,我們只有在程序運(yùn)行期才能知道會創(chuàng)建哪些對象,這部分內(nèi)存的分配和回收是動態(tài)的,垃圾收集器要關(guān)注的就是這部分內(nèi)存。

3.1 對象回收的判定規(guī)則

垃圾收集器在做垃圾回收的時候,首先需要判定的就是哪些內(nèi)存是需要被回收的,哪些對象是「存活」的,是不可以被回收的;哪些對象已經(jīng)「死掉」了,需要被回收。

引用計數(shù)法

判斷對象存活與否的一種方式是「引用計數(shù)」,即對象被引用一次,計數(shù)器就加 1,如果計數(shù)器為 0 則判斷這個對象可以被回收。但是引用計數(shù)法有一個很致命的缺陷就是它無法解決循環(huán)依賴的問題,因此現(xiàn)在主流的虛擬機(jī)基本不會采用這種方式。

可達(dá)性分析算法

可達(dá)性分析算法又叫根搜索算法,該算法的基本思想就是通過一系列稱為「GC Roots」的對象作為起始點(diǎn),從這些起始點(diǎn)開始往下搜索,搜索所走過的路徑稱為引用鏈,當(dāng)一個對象到 GC Roots 對象之間沒有任何引用鏈的時候(不可達(dá)),證明該對象是不可用的,于是就會被判定為可回收對象。

在 Java 中可作為 GC Roots 的對象包含以下幾種:

虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象;

方法區(qū)中類靜態(tài)屬性引用的對象;

方法區(qū)中常量引用的對象;

本地方法棧中 JNI(Native 方法)引用的對象。

Java 中是四種引用類型

無論是通過引用計數(shù)器還是通過可達(dá)性分析來判斷對象是否可以被回收都設(shè)計到「引用」的概念。在 Java 中,根據(jù)引用關(guān)系的強(qiáng)弱不一樣,將引用類型劃為強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)和虛引用(Phantom Reference)。

強(qiáng)引用Object obj = new Object()這種方式就是強(qiáng)引用,只要這種強(qiáng)引用存在,垃圾收集器就永遠(yuǎn)不會回收被引用的對象。

軟引用:用來描述一些有用但非必須的對象。在 OOM 之前垃圾收集器會把這些被軟引用的對象列入回收范圍進(jìn)行二次回收。如果本次回收之后還是內(nèi)存不足才會觸發(fā) OOM。在 Java 中使用 SoftReference 類來實現(xiàn)軟引用。

弱引用:同軟引用一樣也是用來描述非必須對象的,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時,無論當(dāng)前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象。在 Java 中使用 WeakReference 類來實現(xiàn)。

虛引用:是最弱的一種引用關(guān)系,一個對象是否有虛引用的存在完全不影響對象的生存時間,也無法通過虛引用來獲取一個對象的實例。一個對象使用虛引用的唯一目的是為了在被垃圾收集器回收時收到一個系統(tǒng)通知。在 Java 中使用 PhantomReference 類來實現(xiàn)。

生存還是死亡,這是一個問題

在可達(dá)性分析中判定為不可達(dá)的對象,也不一定就是「非死不可的」。這時它們處于「緩刑」階段,真正要宣告一個對象死亡,至少需要經(jīng)歷兩次標(biāo)記過程:

第一次標(biāo)記:如果對象在進(jìn)行可達(dá)性分析后被判定為不可達(dá)對象,那么它將被第一次標(biāo)記并且進(jìn)行一次篩選。篩選的條件是此對象是否有必要執(zhí)行 finalize() 方法。對象沒有覆蓋 finalize() 方法或者該對象的 finalize() 方法曾經(jīng)被虛擬機(jī)調(diào)用過,則判定為沒必要執(zhí)行。

第二次標(biāo)記:如果被判定為有必要執(zhí)行 finalize() 方法,那么這個對象會被放置到一個 F-Queue 隊列中,并在稍后由虛擬機(jī)自動創(chuàng)建的、低優(yōu)先級的 Finalizer 線程去執(zhí)行該對象的 finalize() 方法。但是虛擬機(jī)并不承諾會等待該方法結(jié)束,這樣做是因為,如果一個對象的 finalize() 方法比較耗時或者發(fā)生了死循環(huán),就可能導(dǎo)致 F-Queue 隊列中的其他對象永遠(yuǎn)處于等待狀態(tài),甚至導(dǎo)致整個內(nèi)存回收系統(tǒng)崩潰。finalize() 方法是對象逃脫死亡命運(yùn)的最后一次機(jī)會,如果對象要在 finalize() 中挽救自己,只要重新與 GC Roots 引用鏈關(guān)聯(lián)上就可以了。這樣在第二次標(biāo)記時它將被移除「即將回收」的集合,如果對象在這個時候還沒有逃脫,那么它基本上就真的被回收了。

方法區(qū)回收

前面介紹過,方法區(qū)在 HotSpot 虛擬機(jī)中被劃分為永久代。在 Java 虛擬機(jī)規(guī)范中沒有要求方法區(qū)實現(xiàn)垃圾收集,而且方法區(qū)垃圾收集的性價比也很低。

方法區(qū)(永久代)的垃圾收集主要回收兩部分內(nèi)容:廢棄常量和無用的類。

廢棄常量的回收和 Java 堆中對象的回收非常類似,這里就不做過多的解釋了。

類的回收條件就比較苛刻了。要判定一個類是否可以被回收,要滿足以下三個條件:

該類的所有實例已經(jīng)被回收;

加載該類的 ClassLoader 已經(jīng)被回收;

該類的 Class 對象沒有被引用,無法再任何地方通過反射訪問該類的方法。

3.2 垃圾回收算法 標(biāo)記-清除算法

正如標(biāo)記-清除的算法名一樣,該算法分為「標(biāo)記」和「清除」兩個階段:

首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后回收所有被標(biāo)記的對象。標(biāo)記-清除算法是一種最基礎(chǔ)的算法,后續(xù)其它算法都是在它的基礎(chǔ)上基于不足之處改進(jìn)而來的。它的不足體現(xiàn)在兩方面:一是效率問題,標(biāo)記和清除的效率都不高;二是空間問題,標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導(dǎo)致以后程序的運(yùn)行過程中又要分配較大對象是,無法找打足夠的連續(xù)內(nèi)存而不得不提前出發(fā)下一次 GC。

復(fù)制算法

為了解決效率問題,于是就有了復(fù)制算法,它將內(nèi)存一分為二劃分為大小相等的兩塊內(nèi)存區(qū)域。每次只使用其中的一塊。當(dāng)這一塊用完時,就將還存活的對象復(fù)制到另一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。這樣做的好處是不用考慮內(nèi)存碎片問題了,簡單高效。只不過這種算法代價也很高,內(nèi)存因此縮小了一半。

現(xiàn)在的商業(yè)虛擬機(jī)都采用這種算法來回收新生代,在 IBM 的研究中新生代中的對象 98% 都是「朝生夕死」,所以并不需要按照 1:1 的比例來劃分空間,而是將內(nèi)存分為一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 和其中一塊 Survivor。當(dāng)回收時,將 Eden 和 Survivor 中還存活的對象一次性復(fù)制到另一塊 Survivor 空間上,最后清理掉 Eden 和剛才用過的 Survivor 空間。 HotSpot 默認(rèn) Eden 和 Survivor 的大小比例是 8:1,也就是每次新生代中可用的內(nèi)存為整個新生代容量的 90%(80%+10%),只有 10% 會被浪費(fèi)。當(dāng)然,98% 的對象可回收只是一般場景下的數(shù)據(jù),我們沒辦法保證每次回收后都只有不多于 10% 的對象存活,當(dāng) Survivor 空間不夠用時,需要依賴其它內(nèi)存(這里指老年代)進(jìn)行分配擔(dān)保。如果另外一塊 Survivor 空間沒有足夠空間存放上一次新生代收集下來存活的對象時,這些對象將直接通過分配擔(dān)保機(jī)制進(jìn)入老年代。

標(biāo)記-整理算法

通過前面對復(fù)制-收集算法的介紹我們知道,其對老年代這種對象存活時間長的內(nèi)存區(qū)域就不適用了,而標(biāo)記整理的算法就比較適用這一場景。

標(biāo)記-整理算法的標(biāo)記過程與「標(biāo)記-清除」算法一樣,但是后續(xù)步驟不是直接對可回收對象進(jìn)行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存。

分代回收算法

當(dāng)前商業(yè)虛擬機(jī)的垃圾搜集都采用「分代回收」算法,這種算法并沒有什么新的思想,只是根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊。一般是將 Java 堆分為新生代和老年代,這樣可以根據(jù)各個年代的特點(diǎn)采用最合適的搜集算法。

在新生代中,每次垃圾收集時都發(fā)現(xiàn)有大批對象死去,只有少量存活,那就選用復(fù)制算法,只需要付出少量存活對象的復(fù)制成本就可以完成收集。

而老年代中因為對象存活率高、沒有額外空間對它進(jìn)行分配擔(dān)保,就必須使用「標(biāo)記-清除」或者「標(biāo)記-整理」算法來進(jìn)行回收。

3.3 內(nèi)存分配與回收策略

所謂自動內(nèi)存管理,最終要解決的也就是內(nèi)存分配和內(nèi)存回收兩個問題。前面我們介紹了內(nèi)存回收,這里我們再來聊聊內(nèi)存分配。

對象的內(nèi)存分配通常是在 Java 堆上分配(隨著虛擬機(jī)優(yōu)化技術(shù)的誕生,某些場景下也會在棧上分配,后面會詳細(xì)介紹),對象主要分配在新生代的 Eden 區(qū),如果啟動了本地線程緩沖,將按照線程優(yōu)先在 TLAB 上分配。少數(shù)情況下也會直接在老年代上分配??偟膩碚f分配規(guī)則不是百分百固定的,其細(xì)節(jié)取決于哪一種垃圾收集器組合以及虛擬機(jī)相關(guān)參數(shù)有關(guān),但是虛擬機(jī)對于內(nèi)存的分配還是會遵循以下幾種「普世」規(guī)則:

對象優(yōu)先在 Eden 區(qū)分配

多數(shù)情況,對象都在新生代 Eden 區(qū)分配。當(dāng) Eden 區(qū)分配沒有足夠的空間進(jìn)行分配時,虛擬機(jī)將會發(fā)起一次 Minor GC。如果本次 GC 后還是沒有足夠的空間,則將啟用分配擔(dān)保機(jī)制在老年代中分配內(nèi)存。

這里我們提到 Minor GC,如果你仔細(xì)觀察過 GC 日常,通常我們還能從日志中發(fā)現(xiàn) Major GC/Full GC。

Minor GC 是指發(fā)生在新生代的 GC,因為 Java 對象大多都是朝生夕死,所有 Minor GC 非常頻繁,一般回收速度也非???;

Major GC/Full GC 是指發(fā)生在老年代的 GC,出現(xiàn)了 Major GC 通常會伴隨至少一次 Minor GC。Major GC 的速度通常會比 Minor GC 慢 10 倍以上。

大對象直接進(jìn)入老年代

所謂大對象是指需要大量連續(xù)內(nèi)存空間的對象,頻繁出現(xiàn)大對象是致命的,會導(dǎo)致在內(nèi)存還有不少空間的情況下提前觸發(fā) GC 以獲取足夠的連續(xù)空間來安置新對象。

前面我們介紹過新生代使用的是標(biāo)記-清除算法來處理垃圾回收的,如果大對象直接在新生代分配就會導(dǎo)致 Eden 區(qū)和兩個 Survivor 區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。因此對于大對象都會直接在老年代進(jìn)行分配。

長期存活對象將進(jìn)入老年代

虛擬機(jī)采用分代收集的思想來管理內(nèi)存,那么內(nèi)存回收時就必須判斷哪些對象應(yīng)該放在新生代,哪些對象應(yīng)該放在老年代。因此虛擬機(jī)給每個對象定義了一個對象年齡的計數(shù)器,如果對象在 Eden 區(qū)出生,并且能夠被 Survivor 容納,將被移動到 Survivor 空間中,這時設(shè)置對象年齡為 1。對象在 Survivor 區(qū)中每「熬過」一次 Minor GC 年齡就加 1,當(dāng)年齡達(dá)到一定程度(默認(rèn) 15) 就會被晉升到老年代。

動態(tài)對象年齡判斷

為了更好的適應(yīng)不同程序的內(nèi)存情況,虛擬機(jī)并不是永遠(yuǎn)要求對象的年齡必需達(dá)到某個固定的值(比如前面說的 15)才會被晉升到老年代,而是會去動態(tài)的判斷對象年齡。如果在 Survivor 區(qū)中相同年齡所有對象大小的總和大于 Survivor 空間的一半,年齡大于等于該年齡的對象就可以直接進(jìn)入老年代。

空間分配擔(dān)保

在新生代觸發(fā) Minor GC 后,如果 Survivor 中任然有大量的對象存活就需要老年隊來進(jìn)行分配擔(dān)保,讓 Survivor 區(qū)中無法容納的對象直接進(jìn)入到老年代。

寫在最后

對于我們 Java 程序員來說,虛擬機(jī)的自動內(nèi)存管理機(jī)制為我們在編碼過程中帶來了極大的便利,不用像 C/C++ 等語言的開發(fā)者一樣小心翼翼的去管理每一個對象的生命周期。但同時我們也喪失了內(nèi)存控制的管理權(quán)限,一旦發(fā)生內(nèi)存泄漏如果不了解虛擬機(jī)的內(nèi)存管理原理,就很排查問題。希望這篇文章能對大家理解 Java 虛擬機(jī)的內(nèi)存管理機(jī)制有所幫助。如果想對 Java 虛擬機(jī)有更進(jìn)一步的了解,推薦大家去讀周志明老師的《深入理解 Java 虛擬機(jī):JVM 高級特性與最佳實踐》這本書。

好了,關(guān)于 Java 虛擬機(jī)的自動內(nèi)存管理機(jī)制就介紹到這里,下一篇我們來聊聊「類文件結(jié)構(gòu)」。

參考資料:

《深入理解 Java 虛擬機(jī):JVM 高級特性與最佳實踐(第 2 版)》

如果你喜歡我的文章,就關(guān)注下我的公眾號 BaronTalk 、 知乎專欄 或者在 GitHub 上添個 Star 吧!

微信公眾號:BaronTalk

知乎專欄:https://zhuanlan.zhihu.com/baron

GitHub:https://github.com/BaronZ88

個人博客:http://baronzhang.com

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/74963.html

相關(guān)文章

  • 深入理解虛擬機(jī)Java內(nèi)存區(qū)域

    摘要:運(yùn)行時數(shù)據(jù)區(qū)域虛擬機(jī)在執(zhí)行程序的過程中會把它管理的內(nèi)存劃分成若干個不同的數(shù)據(jù)區(qū)域。堆虛擬機(jī)所管理的內(nèi)存中最大的一塊,堆是所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建。 《深入理解Java虛擬機(jī):JVM高級特性與最佳實踐(第二版》讀書筆記 1 概述 對于Java程序員來說,在虛擬機(jī)自動內(nèi)存管理機(jī)制下,不再需要像C/C++程序開發(fā)程序員這樣為內(nèi)一個new 操作去寫對應(yīng)的delete/...

    dance 評論0 收藏0
  • 深入理解虛擬機(jī)Java內(nèi)存區(qū)域

    摘要:深入理解虛擬機(jī)高級特性與最佳實踐第二版讀書筆記與常見面試題總結(jié)本節(jié)常見面試題介紹下內(nèi)存區(qū)域運(yùn)行時數(shù)據(jù)區(qū)。運(yùn)行時數(shù)據(jù)區(qū)域虛擬機(jī)在執(zhí)行程序的過程中會把它管理的內(nèi)存劃分成若干個不同的數(shù)據(jù)區(qū)域。 《深入理解Java虛擬機(jī):JVM高級特性與最佳實踐(第二版》讀書筆記與常見面試題總結(jié) 本節(jié)常見面試題: 介紹下Java內(nèi)存區(qū)域(運(yùn)行時數(shù)據(jù)區(qū))。 對象的訪問定位的兩種方式。 1 概述 對于Java...

    lavnFan 評論0 收藏0
  • Java虛擬機(jī)Java自動內(nèi)存管理和回收機(jī)

    摘要:所以我們提到的內(nèi)存回收大都是指堆內(nèi)存的回收。根據(jù)堆內(nèi)存對對象的代的劃分我們對堆內(nèi)存有這樣劃分各版本和種類的垃圾回收器各有其用武之地,配合使用它們得到最好的效果十分重要。 這篇文章的素材來自周志明的《深入理解Java虛擬機(jī)》。作為Java開發(fā)人員,一定程度了解JVM虛擬機(jī)的的運(yùn)作方式非常重要,本文就一些簡單的虛擬機(jī)的相關(guān)概念和運(yùn)作機(jī)制展開我自己的學(xué)習(xí)過程。 虛擬機(jī)內(nèi)存分區(qū) java虛擬機(jī)...

    xuxueli 評論0 收藏0
  • 深入理解java虛擬機(jī)》學(xué)習(xí)筆記系列——java內(nèi)存區(qū)域劃分

    摘要:運(yùn)行時數(shù)據(jù)區(qū)域的學(xué)習(xí),是學(xué)習(xí)以及機(jī)制的基礎(chǔ),也是深入理解對象創(chuàng)建及運(yùn)行過程的前提。了解內(nèi)存區(qū)域劃分,是學(xué)習(xí)概念的前提。 Java 運(yùn)行時數(shù)據(jù)區(qū)域的學(xué)習(xí),是學(xué)習(xí) jvm 以及 GC 機(jī)制的基礎(chǔ),也是深入理解 java 對象創(chuàng)建及運(yùn)行過程的前提。廢話不多說,直接進(jìn)入正題: 一張圖總結(jié) showImg(https://segmentfault.com/img/bVOMAn?w=685&h=5...

    史占廣 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<