偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

深入理解Java虛擬機(jī):堆詳解

開(kāi)發(fā) 前端
內(nèi)存是非常重要的系統(tǒng)資源,是硬盤(pán)和CPU的中間倉(cāng)庫(kù)及橋梁,承載著操作系統(tǒng)和應(yīng)用程序的實(shí)時(shí)運(yùn)行JVM內(nèi)存布局規(guī)定了Java在運(yùn)行過(guò)程中內(nèi)存申請(qǐng)、分配、管理的策略,保證了JVM的高效穩(wěn)定運(yùn)行。不同的JVM對(duì)于內(nèi)存的劃分方式和管理機(jī)制存在著部分差異。

前言

本節(jié)主要講的是運(yùn)行時(shí)數(shù)據(jù)區(qū)(堆),也就是下圖這部分,它是在類(lèi)加載完成后的階段:

圖片圖片

  • 每個(gè)線程:獨(dú)立包括程序計(jì)數(shù)器、棧、本地棧
  • 線程間共享:堆、堆外內(nèi)存(永久代或元空間、代碼緩存)

當(dāng)我們通過(guò)前面的:類(lèi)的加載-> 驗(yàn)證 -> 準(zhǔn)備 -> 解析 -> 初始化 這幾個(gè)階段完成后,就會(huì)用到執(zhí)行引擎對(duì)我們的類(lèi)進(jìn)行使用,同時(shí)執(zhí)行引擎將會(huì)使用到我們運(yùn)行時(shí)數(shù)據(jù)區(qū)。

內(nèi)存是非常重要的系統(tǒng)資源,是硬盤(pán)和CPU的中間倉(cāng)庫(kù)及橋梁,承載著操作系統(tǒng)和應(yīng)用程序的實(shí)時(shí)運(yùn)行JVM內(nèi)存布局規(guī)定了Java在運(yùn)行過(guò)程中內(nèi)存申請(qǐng)、分配、管理的策略,保證了JVM的高效穩(wěn)定運(yùn)行。不同的JVM對(duì)于內(nèi)存的劃分方式和管理機(jī)制存在著部分差異。

正文

我們通過(guò)磁盤(pán)或者網(wǎng)絡(luò)IO得到的數(shù)據(jù),都需要先加載到內(nèi)存中,然后CPU從內(nèi)存中獲取數(shù)據(jù)進(jìn)行讀取,也就是說(shuō)內(nèi)存充當(dāng)了CPU和磁盤(pán)之間的橋梁。

圖片圖片

線程

線程是一個(gè)程序里的運(yùn)行單元。JVM允許一個(gè)應(yīng)用有多個(gè)線程并行的執(zhí)行。在Hotspot JVM里,每個(gè)線程都與操作系統(tǒng)的本地線程直接映射。

當(dāng)一個(gè)Java線程準(zhǔn)備好執(zhí)行以后,此時(shí)一個(gè)操作系統(tǒng)的本地線程也同時(shí)創(chuàng)建。Java線程執(zhí)行終止后,本地線程也會(huì)回收。

操作系統(tǒng)負(fù)責(zé)所有線程的安排調(diào)度到任何一個(gè)可用的CPU上。一旦本地線程初始化成功,它就會(huì)調(diào)用Java線程中的run()方法。

JVM系統(tǒng)線程:

  • 虛擬機(jī)線程:需要JVM達(dá)到安全點(diǎn)才會(huì)出現(xiàn)。這些操作必須在不同的線程中發(fā)生的,原因是他們都需要JVM達(dá)到安全點(diǎn),這樣堆才不會(huì)變化。這種線程的執(zhí)行類(lèi)型包括stop-the-world的垃圾收集,線程棧收集,線程掛起以及偏向鎖撤銷(xiāo)。
  • 周期任務(wù)線程:這種線程是時(shí)間周期事件的體現(xiàn)(比如中斷),他們一般用于周期性操作的調(diào)度執(zhí)行。
  • GC線程:這種線程對(duì)在JVM里不同種類(lèi)的垃圾收集行為提供了支持。
  • 編譯線程:這種線程在運(yùn)行時(shí)會(huì)將字節(jié)碼編譯成到本地代碼。
  • 信號(hào)調(diào)度線程:這種線程接收信號(hào)并發(fā)送給JVM,在它內(nèi)部通過(guò)調(diào)用適當(dāng)?shù)姆椒ㄟM(jìn)行處理。

堆針對(duì)一個(gè)JVM進(jìn)程來(lái)說(shuō)是唯一的,也就是一個(gè)進(jìn)程只有一個(gè)JVM,但是進(jìn)程包含多個(gè)線程,他們是共享同一堆空間的。

圖片圖片

數(shù)組和對(duì)象可能永遠(yuǎn)不會(huì)存儲(chǔ)在棧上,因?yàn)闂斜4嬉?,這個(gè)引用指向?qū)ο蠡蛘邤?shù)組在堆中的位置,在方法結(jié)束后,堆中的對(duì)象不會(huì)馬上被移除,僅僅在垃圾收集的時(shí)候才會(huì)被移除。

堆內(nèi)存細(xì)分

Java 7及之前堆內(nèi)存邏輯上分為三部分:新生區(qū)+老年區(qū)+永久區(qū)

  • Young Generation Space 新生區(qū),又被劃分為Eden區(qū)和Survivor區(qū)
  • Tenure generation space 老年區(qū)
  • Permanent Space 永久區(qū)

Java 8及之后堆內(nèi)存邏輯上分為三部分:新生區(qū)+老年區(qū)+元空間

  • Young Generation Space 新生區(qū),又被劃分為Eden區(qū)和Survivor區(qū)
  • Tenure generation space 老年區(qū)
  • Meta Space 元空間

Jdk1.6

圖片圖片

Jdk1.7

圖片圖片

Jdk1.8

圖片圖片

設(shè)置堆內(nèi)存大小

  • -Xms用于表示堆區(qū)的起始內(nèi)存,等價(jià)于-XX:InitialHeapSize,默認(rèn)物理電腦內(nèi)存大小/64
  • -Xmx則用于表示堆區(qū)的最大內(nèi)存,等價(jià)于-XX:MaxHeapSize,默認(rèn)物理電腦內(nèi)存大小/4

通常會(huì)將-Xms和-Xmx兩個(gè)參數(shù)配置相同的值,其目的是為了能夠在Java垃圾回收機(jī)制清理完堆區(qū)后不需要重新分隔計(jì)算堆區(qū)的大小,從而提高性能。

一旦堆區(qū)中的內(nèi)存大小超過(guò)-Xmx所指定的最大內(nèi)存時(shí),將會(huì)拋出OutOfMemoryError異常

年輕代與老年代

存儲(chǔ)在JVM中的Java對(duì)象可以被劃分為兩類(lèi):

  • 生命周期較短的瞬時(shí)對(duì)象,這類(lèi)對(duì)象的創(chuàng)建和消亡都非常迅速。
  • 生命周期非常長(zhǎng),在某些極端的情況下還能夠與JVM的生命周期保持一致。

圖片圖片

  • 默認(rèn)-XX:NewRatio=2,表示新生代占1,老年代占2。
  • Eden空間和另外兩個(gè)survivor空間缺省所占的比例是8:1:1。

圖片圖片

  • jinfo -flag NewRatio 進(jìn)程號(hào) 可查看相關(guān)屬性值
  • jinfo -flag SurvivorRatio 進(jìn)程號(hào) 可查看相關(guān)屬性值

對(duì)象分配過(guò)程

為新對(duì)象分配內(nèi)存是一件非常嚴(yán)謹(jǐn)和復(fù)雜的任務(wù),JVM的設(shè)計(jì)者們不僅需要考慮內(nèi)存如何分配、在哪里分配等問(wèn)題,并且由于內(nèi)存分配算法與內(nèi)存回收算法密切相關(guān),所以還需要考慮GC執(zhí)行完內(nèi)存回收后是否會(huì)在內(nèi)存空間中產(chǎn)生內(nèi)存碎片

圖片圖片

  1. new的對(duì)象先放伊甸園區(qū)。此區(qū)有大小限制。
  2. 當(dāng)伊甸園的空間填滿(mǎn)時(shí),程序又需要?jiǎng)?chuàng)建對(duì)象,JVM的垃圾回收器將對(duì)伊甸園區(qū)進(jìn)行垃圾回收(MinorGC),將伊甸園區(qū)中的不再被其他對(duì)象所引用的對(duì)象進(jìn)行銷(xiāo)毀,再加載新的對(duì)象放到伊甸園區(qū)。
  3. 然后將伊甸園中的剩余對(duì)象移動(dòng)到幸存者s0區(qū)。
  4. 如果再次觸發(fā)垃圾回收,此時(shí)上次幸存下來(lái)的放到幸存者s0區(qū)的,如果沒(méi)有回收,就會(huì)放到幸存者s1區(qū)。
  5. 如果再次經(jīng)歷垃圾回收,此時(shí)會(huì)重新放回幸存者s0區(qū),接著再去幸存者s1區(qū)。
  6. 啥時(shí)候能去養(yǎng)老區(qū)呢?可以設(shè)置次數(shù)。默認(rèn)是15次 ,進(jìn)行設(shè)置-Xx:MaxTenuringThreshold= N。
  7. 在養(yǎng)老區(qū),相對(duì)悠閑。當(dāng)養(yǎng)老區(qū)內(nèi)存不足時(shí),再次觸發(fā)GC:Major GC,進(jìn)行養(yǎng)老區(qū)的內(nèi)存清理。
  8. 若養(yǎng)老區(qū)執(zhí)行了Major GC之后,發(fā)現(xiàn)依然無(wú)法進(jìn)行對(duì)象的保存,就會(huì)產(chǎn)生OOM異常。
  • 針對(duì)幸存者s0,s1區(qū)的總結(jié):復(fù)制之后又交換,誰(shuí)空誰(shuí)是to。
  • 垃圾回收:頻繁在新生區(qū)收集,很少在老年代收集,幾乎不在永久代和元空間進(jìn)行收集。

Minor GC,MajorGC、Full GC

JVM在進(jìn)行GC時(shí),并非每次都對(duì)上面三個(gè)內(nèi)存區(qū)域一起回收的,大部分時(shí)候回收的都是指新生代。

針對(duì)Hotspot VM的實(shí)現(xiàn),它里面的GC按照回收區(qū)域又分為兩大種類(lèi)型:一種是部分收集(Partial GC),一種是整堆收集(FullGC)

  • 部分收集:不是完整收集整個(gè)Java堆的垃圾收集。其中又分為:

新生代收集(Minor GC / Young GC):只是新生代的垃圾收集。

老年代收集(Major GC / Old GC):只是老年代的圾收集。

混合收集(MixedGC):收集整個(gè)新生代以及部分老年代的垃圾收集。

  • 整堆收集(Full GC):收集整個(gè)Java堆和方法區(qū)的垃圾收集。
  • 目前,只有CMS GC會(huì)有單獨(dú)收集老年代的行為,很多時(shí)候Major GC會(huì)和Full GC混淆使用,需要具體分辨是老年代回收還是整堆回收。
  • 目前,只有G1 GC會(huì)有混合收集。

年輕代GC(Minor GC)觸發(fā)機(jī)制

  • 當(dāng)年輕代空間不足時(shí),就會(huì)觸發(fā)MinorGC,這里的年輕代滿(mǎn)指的是Eden代滿(mǎn),Survivor滿(mǎn)不會(huì)引發(fā)GC。(每次Minor GC會(huì)清理年輕代的內(nèi)存。)
  • Minor GC會(huì)引發(fā)STW,暫停其它用戶(hù)的線程,等垃圾回收結(jié)束,用戶(hù)線程才恢復(fù)運(yùn)行 。

老年代GC(Major GC / Full GC)觸發(fā)機(jī)制

  • 對(duì)象從老年代消失時(shí),我們說(shuō)Major GC或 Full GC發(fā)生了。
  • 出現(xiàn)了Major Gc,經(jīng)常會(huì)伴隨至少一次的Minor GC。
  • 如果Major GC后,內(nèi)存還不足,就報(bào)OOM。

內(nèi)存分配策略

如果對(duì)象在Eden出生并經(jīng)過(guò)第一次Minor GC后仍然存活,并且能被Survivor容納的話,將被移動(dòng)到survivor空間中,并將對(duì)象年齡設(shè)為1。對(duì)象在survivor區(qū)中每熬過(guò)一次MinorGC,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲,其實(shí)每個(gè)JVM、每個(gè)GC都有所不同)時(shí),就會(huì)被晉升到老年代。

對(duì)不同年齡段的對(duì)象分配原則如下所示:

  • 優(yōu)先分配到Eden
  • 大對(duì)象直接分配到老年代(盡量避免程序中出現(xiàn)過(guò)多的大對(duì)象)
  • 長(zhǎng)期存活的對(duì)象分配到老年代
  • 動(dòng)態(tài)對(duì)象年齡判斷:如果survivor區(qū)中相同年齡的所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象可以直接進(jìn)入老年代,無(wú)須等到MaxTenuringThreshold中要求的年齡。
  • 空間分配擔(dān)保:-XX:HandlePromotionFailure

圖片圖片

TLAB

為什么有TLAB

  • 堆區(qū)是線程共享區(qū)域,任何線程都可以訪問(wèn)到堆區(qū)中的共享數(shù)據(jù) 。
  • 由于對(duì)象實(shí)例的創(chuàng)建在JVM中非常頻繁,因此在并發(fā)環(huán)境下從堆區(qū)中劃分內(nèi)存空間是線程不安全的 。
  • 為避免多個(gè)線程操作同一地址,需要使用加鎖等機(jī)制,進(jìn)而影響分配速度。

什么是TLAB

圖片圖片

  • 從內(nèi)存模型而不是垃圾收集的角度,對(duì)Eden區(qū)域繼續(xù)進(jìn)行劃分,JVM為每個(gè)線程分配了一個(gè)私有緩存區(qū)域,它包含在Eden空間內(nèi)。
  • 多線程同時(shí)分配內(nèi)存時(shí),使用TLAB可以避免一系列的非線程安全問(wèn)題,同時(shí)還能夠提升內(nèi)存分配的吞吐量,因此我們可以將這種內(nèi)存分配方式稱(chēng)之為快速分配策略 。

盡管不是所有的對(duì)象實(shí)例都能夠在TLAB中成功分配內(nèi)存,但JVM確實(shí)是將TLAB作為內(nèi)存分配的首選。

圖片圖片

堆空間的參數(shù)設(shè)置

-XX:+PrintFlagsInitial  //查看所有的參數(shù)的默認(rèn)初始值
-XX:+PrintFlagsFinal  //查看所有的參數(shù)的最終值(可能會(huì)存在修改,不再是初始值)
-Xms  //初始堆空間內(nèi)存(默認(rèn)為物理內(nèi)存的1/64)
-Xmx  //最大堆空間內(nèi)存(默認(rèn)為物理內(nèi)存的1/4)
-Xmn  //設(shè)置新生代的大小。(初始值及最大值)
-XX:NewRatio  //配置新生代與老年代在堆結(jié)構(gòu)的占比
-XX:SurvivorRatio  //設(shè)置新生代中Eden和S0/S1空間的比例
-XX:MaxTenuringThreshold  //設(shè)置新生代垃圾的最大年齡
-XX:+PrintGCDetails //輸出詳細(xì)的GC處理日志
//打印gc簡(jiǎn)要信息:①-Xx:+PrintGC ② - verbose:gc
-XX:HandlePromotionFalilure://是否設(shè)置空間分配擔(dān)保

堆是分配對(duì)象的唯一選擇么?

隨著JIT編譯期的發(fā)展與逃逸分析技術(shù)逐漸成熟,棧上分配、標(biāo)量替換優(yōu)化技術(shù)將會(huì)導(dǎo)致一些微妙的變化,所有的對(duì)象都分配到堆上也漸漸變得不那么絕對(duì)了。

在Java虛擬機(jī)中,對(duì)象是在Java堆中分配內(nèi)存的,這是一個(gè)普遍的常識(shí)。但是,有一種特殊情況,那就是如果經(jīng)過(guò)逃逸分析(Escape Analysis)后發(fā)現(xiàn),一個(gè)對(duì)象并沒(méi)有逃逸出方法的話,那么就可能被優(yōu)化成棧上分配。這樣就無(wú)需在堆上分配內(nèi)存,也無(wú)須進(jìn)行垃圾回收了。這也是最常見(jiàn)的堆外存儲(chǔ)技術(shù)。

逃逸分析的基本行為就是分析對(duì)象動(dòng)態(tài)作用域:

  • 當(dāng)一個(gè)對(duì)象在方法中被定義后,對(duì)象只在方法內(nèi)部使用,則認(rèn)為沒(méi)有發(fā)生逃逸。
  • 當(dāng)一個(gè)對(duì)象在方法中被定義后,它被外部方法所引用,則認(rèn)為發(fā)生逃逸。例如作為調(diào)用參數(shù)傳遞到其他地方中。
public class EscapeAnalysis {

    public EscapeAnalysis obj;

    /**
     * 方法返回EscapeAnalysis對(duì)象,發(fā)生逃逸
     * @return
     */
    public EscapeAnalysis getInstance() {
        return obj == null ? new EscapeAnalysis() : obj;
    }

    /**
     * 為成員屬性賦值,發(fā)生逃逸
     */
    public void setObj() {
        this.obj = new EscapeAnalysis();
    }

    /**
     * 對(duì)象的作用于僅在當(dāng)前方法中有效,沒(méi)有發(fā)生逃逸
     */
    public void useEscapeAnalysis() {
        EscapeAnalysis e = new EscapeAnalysis();
    }

    /**
     * 引用成員變量的值,發(fā)生逃逸
     */
    public void useEscapeAnalysis2() {
        EscapeAnalysis e = getInstance();
    }
}

使用逃逸分析,編譯器可以對(duì)代碼做如下優(yōu)化:

  • 一、棧上分配:將堆分配轉(zhuǎn)化為棧分配。如果一個(gè)對(duì)象在子程序中被分配,要使指向該對(duì)象的指針永遠(yuǎn)不會(huì)發(fā)生逃逸,對(duì)象可能是棧上分配的候選,而不是堆上分配。
  • 二、同步省略:如果一個(gè)對(duì)象被發(fā)現(xiàn)只有一個(gè)線程被訪問(wèn)到,那么對(duì)于這個(gè)對(duì)象的操作可以不考慮同步。
  • 三、分離對(duì)象或標(biāo)量替換:有的對(duì)象可能不需要作為一個(gè)連續(xù)的內(nèi)存結(jié)構(gòu)存在也可以被訪問(wèn)到,那么對(duì)象的部分(或全部)可以不存儲(chǔ)在內(nèi)存,而是存儲(chǔ)在CPU寄存器中。
責(zé)任編輯:武曉燕 來(lái)源: 一安未來(lái)
相關(guān)推薦

2024-04-03 13:49:00

Java虛擬機(jī)方法區(qū)

2012-11-14 09:57:46

JavaJava虛擬機(jī)JVM

2019-07-24 16:04:47

Java虛擬機(jī)并發(fā)

2024-03-26 07:30:07

Java虛擬機(jī)源文件

2016-09-01 12:37:13

OpenStack虛擬機(jī)Metadata

2024-04-10 07:40:45

Java虛擬機(jī)內(nèi)存

2023-09-22 23:00:11

Java虛擬機(jī)

2019-12-31 10:45:30

JavaVisualVM高并發(fā)

2017-11-14 14:41:11

Java泛型IO

2011-12-28 13:38:00

JavaJVM

2011-12-28 13:24:47

JavaJVM

2020-05-08 16:55:48

Java虛擬機(jī)JVM

2016-12-08 15:36:59

HashMap數(shù)據(jù)結(jié)構(gòu)hash函數(shù)

2010-06-01 15:25:27

JavaCLASSPATH

2020-07-21 08:26:08

SpringSecurity過(guò)濾器

2022-08-21 16:52:27

Linux虛擬內(nèi)存

2021-09-18 06:56:01

JavaCAS機(jī)制

2012-03-05 11:09:01

JavaClass

2020-09-23 10:00:26

Redis數(shù)據(jù)庫(kù)命令

2019-06-25 10:32:19

UDP編程通信
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)