国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景

這篇具有很好參考價值的文章主要介紹了【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

前言:Jvm 整體組成

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

Jvm由4個部分組成,分為2個子系統(tǒng)和2個組件,2個子系統(tǒng)為Class loader(類裝載)、Execution engine(執(zhí)行引擎);2個組件為Runtime Data Area(運行時數(shù)據(jù)區(qū))、Native Interface(本地接口)。

  • Class loader(類加載器):根據(jù)給定的全限定名類名(如:Java.lang.Object)來裝載class文件到Runtime data area中的method area。
  • Runtime Data Area(運行時數(shù)據(jù)區(qū)域):這就是我們常說的Jvm的內(nèi)存。
  • Execution Engine(執(zhí)行引擎) :執(zhí)行classes中的指令。
  • Native Interface(本地接口) :與native libraries交互,是其它編程語言交互的接口。

各個組成部分的用途:

  1. 首先通過編譯器 Java 代碼轉(zhuǎn)換成字節(jié)碼(class文件)
  2. 類加載器(ClassLoader) 再把字節(jié)碼加載到內(nèi)存中將其放在 運行時數(shù)據(jù)區(qū)(Runtime data area)方法區(qū)內(nèi)
  3. 字節(jié)碼文件只是 Jvm 的一套指令集規(guī)范,并不能直接交給底層操作系統(tǒng)去執(zhí)行,因此需要特定的命令解析器執(zhí)行引擎(Execution Engine),將字節(jié)碼翻譯成底層系統(tǒng)指令,再交由 CPU 去執(zhí)行,而這個過程中需要調(diào)用其他語言的 本地庫接口(Native Interface) 來實現(xiàn)整個程序的功能。

一.JDK的內(nèi)存區(qū)域變遷

HotSpot虛擬機是是Sun/OracleJDK和OpenJDK中的默認Java虛擬機,是JVM應(yīng)用最廣泛的一種實現(xiàn)。

  • JDK1.6時期和我們上面講的JVM內(nèi)存區(qū)域是一致的:
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

  • JDK1.7時發(fā)生了一些變化,將字符串常量池、靜態(tài)變量,存放在堆上

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

  • JDK1.8時徹底干掉了方法區(qū),而在直接內(nèi)存中劃出一塊區(qū)域作為元空間,運行時常量池、類常量池都移動到元空間

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

  • JVM 運行時數(shù)據(jù)區(qū)的 5 個部分中,只有 Java 堆、元空間是線程共享的,其他三個均為線程私有
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

Java8虛擬機啟動參數(shù)

-Xms設(shè)置堆的最小空間大小。
   堆中 年輕代和年老默認有個比如 是  NewRatio   = 2 (默認是 2:1)
   年輕代中eden和suvivor默認有個比例 8:1:1SurvivorRatio = 8)  jps查看進程 jmap -heap 進程編號 查看到改參數(shù)
-Xmx  設(shè)置堆的最大空間大小。
-XX:NewSize  設(shè)置年輕代最小空間大小。
-XX:MaxNewSize  設(shè)置年輕代最大空間大小。
-XX:PermSize  設(shè)置永久代最小空間大小。
-XX:MaxPermSize  設(shè)置永久代最大空間大小。
-Xss  設(shè)置每個線程的堆棧大小 (64位 默認是1M  -XX:ThreadStackSize默認是0)。



-XmsJVM啟動時申請的初始Heap值,默認為操作系統(tǒng)物理內(nèi)存的1/64,例如-Xms20m
-XmxJVM可申請的最大Heap值,默認值為物理內(nèi)存的1/4,例如-Xmx20m,我們最好將 -Xms-Xmx 設(shè)為相同值,避免每次垃圾回收完成后JVM重新分配內(nèi)存;
-Xmn:設(shè)置新生代的內(nèi)存大小,-Xmn 是將NewSizeMaxNewSize設(shè)為一致,我們也可以分別設(shè)置這兩個參數(shù)

-XX:PermSize 設(shè)置最小空間
-XX:MaxPermSize 設(shè)置最大空間

-XX:MetaspaceSize :分配給類元數(shù)據(jù)空間(以字節(jié)計)的初始大小。MetaspaceSize的值設(shè)置的過大會延長垃圾回收時間。垃圾回收過后,引起下一次垃圾回收的類元數(shù)據(jù)空間的大小可能會變大。
-XX:MaxMetaspaceSize:分配給類元數(shù)據(jù)空間的最大值,超過此值就會觸發(fā)Full GC,此值默認沒有限制,但應(yīng)取決于系統(tǒng)內(nèi)存的大小。JVM會動態(tài)地改變此值。
-XX:MinMetaspaceFreeRatio:表示一次GC以后,為了避免增加元數(shù)據(jù)空間的大小,空閑的類元數(shù)據(jù)的容量的最小比例,不夠就會導(dǎo)致垃圾回收。
-XX:MaxMetaspaceFreeRatio:表示一次GC以后,為了避免增加元數(shù)據(jù)空間的大小,空閑的類元數(shù)據(jù)的容量的最大比例,不夠就會導(dǎo)致垃圾回收。

二.堆

  • 通常需要程序員調(diào)試分析的區(qū)域就是“運行時數(shù)據(jù)區(qū)”,或者更具體的來說就是“運行時數(shù)據(jù)區(qū)”里面的Heap(堆)模塊

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

0.堆的概念

在虛擬機啟動時創(chuàng)建 , 堆是被所有線程共享最大的一塊內(nèi)存,幾乎所有的對象實例都在這里分配內(nèi)存(并不是絕對);

  • 特點:線程共享
  • 異常規(guī)定: 如果在堆中沒有內(nèi)存完成實例分配,并且堆不可以再擴展時,將會拋出OutOfMemoryError。 通過-Xmx-Xms控制堆大小

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

1.堆的內(nèi)存分區(qū)

根據(jù)Java回收機制的不同,Java堆有可能擁有不同的結(jié)構(gòu)。最為常見的一種構(gòu)成是將整個java堆分為年輕代和老年代。其中年輕代存放新生對象或者年齡不大的對象,老年代則存放老年對象。

  • 年輕代有分為Eden區(qū)、s0區(qū)、s1區(qū),s0區(qū)和s1區(qū)也被稱為from和to區(qū),他們是兩塊大小相同、可以互換角色的內(nèi)存空間。

    • 結(jié)構(gòu):年輕代(Eden區(qū)+2個Survivor區(qū)) 老年代 永久代(HotSpot有)
      【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

在絕大多數(shù)情況下,對象首先分配在Eden區(qū),在一次年輕代回收之后,如果對象還存活,則進入s0或者s1,每經(jīng)過一次年輕代回收,對象如果存活,它的年齡就會加1。當對象的年齡達到一定閥值后,就會被認為是老年對象,從而進入老年代。

年輕代:新創(chuàng)建的對象——>Eden區(qū)

  • GC的時候會將 Eden中存活的對象復(fù)制?個空的 Survivor中并把當前的 Eden和正在使 的Survivor中的不可達對象 清除掉
    • 再次GC同上,也是將Eden、Survivor存活對象轉(zhuǎn)移到另一個一個空的Survivor中,然后清理剩余的不可達對象

老年代:對象如果在年輕代存活了足夠長的時間而沒有被清理掉(即在幾次Young GC后存活了下來),則會被復(fù)制到老年代

  • 如果新創(chuàng)建對象比較大(比如長字符串或大數(shù)組),且年輕代空間不足,則大對象會直接分配到老年代上大對象可能觸發(fā)提前GC,應(yīng)少用,更應(yīng)避免使用短命的大對象
  • 老年代的空間一般比年輕代大,能存放更多的對象,在老年代上發(fā)生的GC次數(shù)也比年輕代少

永久代:可以簡單理解為方法區(qū)(本質(zhì)上兩者并不等價)

  • JDK1.6及之前:常量池分配在永久代
  • JDK1.7:有,但已經(jīng)逐步“去永久代”
  • JDK1.8及之后:沒有永久代(Java.lang.OutOfMemoryError: PermGen space,這種錯誤將不會出現(xiàn)在JDK1.8中),通過使用本地內(nèi)存的元空間來代替永久代

2.堆與GC

2.1.堆的分代結(jié)構(gòu)

  • 所有的對象和它們相應(yīng)的實例變量以及數(shù)組將被存儲在這里。每個Jvm同樣只有一個堆區(qū)。由于方法區(qū)和堆區(qū)的內(nèi)存由線程共享,所以存儲的數(shù)據(jù)是非線程安全的。
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

堆由年輕代和老年代組成,年輕代又分為Eden區(qū)和survivor(幸存)區(qū),survivor區(qū)中又有from區(qū)和to區(qū).

  • new出來的對象一般都放在Eden區(qū),那當Eden區(qū)滿了之后呢?

    • 假設(shè)通過參數(shù)給堆分配600M內(nèi)存,那么老年代默認是占2/3的,也就是差不多400M,那年輕代就是200M,Eden區(qū)160M,Survivor區(qū)40M。

2.2.堆的分代GC

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)
一個程序只要在運行,那么就不會不停的new對象,那么總有一刻Eden區(qū)會放滿,那么一旦Eden區(qū)被放滿之后,虛擬機會干什么呢?

  • 沒錯,就是gc,不過這里的gc屬于minor(咪呢) gc,就是垃圾收集,來收集垃圾對象并清理的,那么什么是垃圾對象呢?

這里就涉及到了一個GC Root根以及可達性分析算法的概念,也是面試偶爾會被問到的。

  • 可達性分析算法是將GC Roots對象作為起點,從這些起點開始向下搜索引用的對象,找到的對象都標記為非垃圾對象其余未標記的都是垃圾對象。

加粗樣式那么GC Roots根對象又是什么呢?

  • GC Roots根就是判斷一個對象是否可以回收的依據(jù),只要能通過GC Roots根向下一直搜索能搜索到的對象,那么這個對象就不算垃圾對象,而可以作為GC Roots根的如:線程棧的本地變量、靜態(tài)變量、本地方法棧的變量等等它們引用的對象,說白了就是找到和根節(jié)點有聯(lián)系的對象就是有用的對象,其余都認為是垃圾對象來回收

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

  • 經(jīng)歷了第一次minor gc后,沒有被清理的對象就會被移到From區(qū),如上圖。

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

  • 上面在說對象組成的時候有寫到,在對象頭的Mark Word中有存儲GC分代年齡,一個對象每經(jīng)歷一次gc,那么它的gc分代年齡就會+1,如上圖。

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

  • 那么如果第2次新的對象又把Eden區(qū)放滿了,那么又會執(zhí)行minor gc,但是這次會連著From區(qū)一起gc,然后將Eden區(qū)From區(qū) 存活的對象都移到To區(qū)域,對象頭中分代年齡都+1,如上圖。
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

  • 那么當第3次Eden區(qū)又滿的時候,minor gc就是回收Eden區(qū)To區(qū)域了,TEden區(qū)和To區(qū)域還活著的對象就會都移到From區(qū),如上圖。

    • 說白了就是Survivor區(qū)中總有一塊區(qū)域是空著的,存活的對象存放是在From區(qū)和To區(qū)輪流存放,也就是互相復(fù)制拷貝,這也就是垃圾回收算法中的復(fù)制-回收算法

如果一個對象經(jīng)歷了一個15次gc的時候,就會移至老年代。如果還沒有到最大年齡且From區(qū)或者To區(qū)域也慢了,就會直接移到老年代,這只是舉例了兩種常規(guī)規(guī)則,還有其他規(guī)則也是會把對象存放至老年代的。

  • 那么隨著應(yīng)用程序的不斷運行,老年代最終也是會滿的,那么此時也會gc,此時的gc就是Full gc了。

那當我們老年代滿了會發(fā)生什么呢?當然是我們上面說過的Full GC,但是你仔細看我們寫的這個程序,我們所有new出來的HeapTest對象都是存放在heapLists中的,那就會被這個局部變量所引用,那么Full GC就不會有什么垃圾對象可以回收,可是內(nèi)存又滿了,那怎么辦?OOM
【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

2.3.堆的GC案例

下面是個死循環(huán),不斷的往list中添加new出來的對象。

public class HeapTest {
    byte[] a = new byte[1024 * 100];
    public static void main(String[] args) throws InterruptedException {
        ArrayList<HeapTest> heapTest = new ArrayList<>();
        
        while(true) {
            heapTest.add(new HeapTest());
            Thread.sleep(10);
        }
    }
}

通過JDK自帶的Jvm調(diào)優(yōu)工具jvisualvm觀察上面代碼執(zhí)行的內(nèi)存結(jié)構(gòu)。
【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)
打開visual GC
【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

  • 其中 老年代(Old),伊甸園區(qū)(Eden),S0(From),S1(To) 幾個區(qū)的內(nèi)存 和 動態(tài)分配圖都是清晰可見,以一對應(yīng)的
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)
    我們選擇中間一張圖給大家對應(yīng)一下上面所講的內(nèi)容:
  1. 對象放入Eden區(qū)
  2. Eden區(qū)滿發(fā)生minor gc
  3. 第二步的存活對象移至From(Survivor 0)區(qū)
  4. Eden區(qū)再滿發(fā)生minor gc
  5. 第四步存活的對象移至To(Survivor 1)區(qū)

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

  • 這里可以注意到From和To區(qū)和我們上面所說一致,總有一個是空的
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)
    可以看到老年代這里,都是一段一段的直線,中間是突然的增加,這就是在minor gc中一批一批符合規(guī)則的對象被批量移入老年代。

2.4.堆垃圾回收方式

  1. Minor GC(YGC): 它主要是用來對年輕代進行垃圾回收的方式,使用的復(fù)制算法,因為年輕代的對象大多數(shù)生命周期很短,所以GC的頻率也會比較頻繁,但是回收速度很快。

  2. Major GC(YGC): 它是主要用于對老年代對象的垃圾回收方式,老年代的對象生命周期都是比較長的,所以對象不會輕易滅亡,Major GC的頻率不會像Minor GC那么頻繁,況且一次Full GC會比Minor GC需要花費更多的時間、消耗更大,通常出現(xiàn)一次Major GC一般也會出現(xiàn)一次Minor GC(但不絕對)。

  3. Full GC(): Full GC是針對整個年輕代、老生代、元空間(metaspace,java8以上版本取代perm gen)的全局范圍的GC,但是它并不等于Major GC + Minor GC,具體是要看使用什么垃圾收集器組合。一次Full GC 需要花費更多的時間、消耗更大,所以要盡可能減少Full GC的次數(shù)

  4. 特點比較:

    • Minor GC使用復(fù)制算法,需要一塊空的內(nèi)存空間,所以空間使用效率不高,但是它不會出現(xiàn)空間碎片的問題。
    • 而Full GC一般是采用標記-清除算法,容易產(chǎn)生空間碎片,如果再有對象需要請求連續(xù)的空間而無法提供時,會提前觸發(fā)垃圾回收,所以它適合存活對象較多的場景使用也就是老年代的垃圾回收。

3.什么是內(nèi)存泄露

  • 內(nèi)存泄漏是不再被使用的對象或者變量一直被占據(jù)在內(nèi)存中。理論上來說,Java是有GC垃圾回收機制的,也就是說,不再被使用的對象,會被GC自動回收掉,自動從內(nèi)存中清除。

    • 但也有特例即: 長生命周期的對象持有短生命周期對象的引用就很可能發(fā)生內(nèi)存泄露,盡管短生命周期對象已經(jīng)不再需要,但是因為長生命周期對象持有它的引用而導(dǎo)致不能被回收,這就是Java中內(nèi)存泄露的發(fā)生場景。

4.堆棧的區(qū)別

物理地址

  • 堆的物理地址分配對對象是不連續(xù)的。因此性能慢些。在GC的時候也要考慮到不連續(xù)的分配,所以有各種算法。比如,標記-消除,復(fù)制,標記-壓縮,分代(即年輕代使用復(fù)制算法,老年代使用標記——壓縮)

  • 棧使用的是數(shù)據(jù)結(jié)構(gòu)中的棧,后進先出的原則,物理地址分配是 連續(xù)的。所以性能快。

內(nèi)存分別

  • 堆因為是不連續(xù)的,所以分配的內(nèi)存是在運行期確認的,因此大小不固定。一般堆大小遠遠大于棧。

  • 棧是連續(xù)的,所以分配的內(nèi)存大小要在編譯期就確認,大小是固定的。

存放的內(nèi)容

  • 堆存放的是對象的實例和數(shù)組。因此該區(qū)更關(guān)注的是數(shù)據(jù)的存儲

  • 棧存放:局部變量,操作數(shù)棧,返回結(jié)果。該區(qū)更關(guān)注的是程序方法的執(zhí)行。

程序的可見度

  • 堆對于線程都是共享、可見的。
  • 棧只是線程私有的。他的生命周期和線程相同。

5.堆、方法區(qū) 和 棧的關(guān)系

該代碼聲明了一個類,并在main方法中創(chuàng)建了兩個SimpleHeap實例。

public class SimpleHeap {
  private int id;
  
  public SimpleHeap(int id){
    this.id = id;
  }
  
  public void show(){
    System.out.println("My id is "+id);
  }

  public static void main(String[] args) {
    SimpleHeap s1 = new SimpleHeap(1);
    SimpleHeap s2 = new SimpleHeap(2);
    s1.show();
    s2.show();
  }
}

各對象和局部變量的存放情況如下圖:

  • SimpleHeap實例本身分配在中,描述SimpleHeap類的信息存放在 方法區(qū),main函數(shù)中的s1 s2局部變量存放在java棧上,并指向堆中2個實例
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

三.虛擬機棧

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

0.虛擬機棧概念

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

是Java方法執(zhí)行的內(nèi)存模型,每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息,每個方法從調(diào)用直至執(zhí)行完成的過程,都對應(yīng)著一個棧幀在虛擬機棧中入棧到出棧的過程。

  • 每一個線程都有一個私有的Java棧,一個線程的Java棧在線程創(chuàng)建的時候被創(chuàng)建java棧中保存著棧幀信息
  • 局部變量表:存放了編譯器可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對象引用(引用指針,并非對象本),
  • 局部變量表所需的內(nèi)存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的局部變量是完全確定的,在運行期間棧幀不會改變局部變量表的大小空間
  • 特點:線程私有
  • 異常規(guī)定:StackOverflowError、OutOfMemoryError
    1. 如果線程請求的棧深度大于虛擬機所允許的棧深度就會拋出StackOverflowError
    2. 如果虛擬機內(nèi)存是可以動態(tài)擴展的,如果擴展時無法申請到足夠的內(nèi)存就會拋出OutOfMemoryError

JVM 會在線程被創(chuàng)建時,創(chuàng)建一個線程私有的虛擬機棧,也叫“線程棧”。該棧的生命周期和線程是一致,除了Native方法以外,Java方法都是通過Java 虛擬機棧來實現(xiàn)調(diào)用和執(zhí)行過程的(需要程序計數(shù)器、堆、元空間內(nèi)數(shù)據(jù)的配合)。所以Java虛擬機棧是虛擬機執(zhí)行引擎的核心之一。而Java虛擬機棧中出棧入棧的元素就稱為「棧幀」。

  • 每個線程棧由多個棧幀(Frame) 組成,對應(yīng)著每個方法運行時所占用的內(nèi)存。
  • 每個線程只能有一個活動棧幀,也叫當前棧幀,對應(yīng)著當前正在執(zhí)行的方法,當方法執(zhí)行時壓入棧,方法執(zhí)行完畢后彈出棧。
  • 方法體中的基本類型的變量都在棧上,引用變量的指針在棧上,實例在堆上

1.線程棧的結(jié)構(gòu)

public class Math {
    public static int initData = 666;
    public static User user = new User();
 
    public int compute() {
        int a = 1;
        int b = 2;
        int c = (a+b) * 10;
        return c;
    }
 
    public static void main(String[] args) {
        Math math = new Math();
        math.compute();
        System.out.println("test");
    }
}
  • 每個方法都有自己的局部變量,如圖中main方法中的math,compute方法中的a b c,那么Java虛擬機為了區(qū)分不同方法中局部變量作用域范圍的內(nèi)存區(qū)域,每個方法在運行的時候都會分配一塊獨立的棧幀內(nèi)存區(qū)域, 上圖中的程序代碼執(zhí)行的內(nèi)存活動如下。

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

  • 執(zhí)行main方法中的第1行代碼是,棧中會分配main()方法的棧幀,并存儲math局部變量,,接著執(zhí)行compute()方法,那么棧又會分配compute()的棧幀區(qū)域。

    • compute()方法執(zhí)行完之后,就會出棧被釋放,也就符合先進后出的特點,后調(diào)用的方法先出棧。

2.棧幀

棧幀(Stack Frame)是用于支持虛擬機進行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)。

  • 棧幀存儲了方法的局部變量表、操作數(shù)棧、動態(tài)連接和方法返回地址等信息**。

  • 每一個方法從調(diào)用至執(zhí)行完成的過程,都對應(yīng)著一個棧幀在虛擬機棧里從入棧到出棧的過程**。

  • 簡單的理解就是:棧對應(yīng)線程,棧幀對應(yīng)方法

    • 棧幀主要由4個部分組成。
      【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

局部變量表(Local Variable Table)

局部變量表(Local Variable Table)一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)定義的局部變量。包括8種基本數(shù)據(jù)類型、對象引用(reference類型)和returnAddress類型 (指向一條字節(jié)碼指令的地址)。

  • 如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常
  • 如果虛擬機棧動態(tài)擴展時無法申請到足夠的內(nèi)存時會拋出OutOfMemoryError異常。

直接上代碼

public int test(int a, int b) {
    Object obj = new Object();
    return a + b;
}
  • 如果局部變量是Java的8種基本數(shù)據(jù)類型,則存在局部變量表中,如果是引用類型。如new出來的String,局部變量表中存的是引用,而實例在堆中。
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

操作數(shù)棧(Operand Stack)

操作數(shù)棧(Operand Stack) 也稱作操作棧,是一個后入先出棧(LIFO)。隨著方法執(zhí)行字節(jié)碼指令的執(zhí)行,會從局部變量表或?qū)ο髮嵗淖侄?/code>中復(fù)制常量或變量寫入到操作數(shù)棧,再隨著計算的進行將棧中元素 出棧到局部變量表 或者 返回給方法調(diào)用者,也就是出棧/入棧操作。

public class OperandStackTest {

    public int sum(int a, int b) {
        return a + b;
    }
}

編譯生成.class文件之后,再反匯編查看匯編指令

javac OperandStackTest.java
javap -v OperandStackTest.class

OperandStackTest字節(jié)碼文件

 public int sum(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3 // 最大棧深度為2 局部變量個數(shù)為3
         0: iload_1 // 局部變量1 壓棧
         1: iload_2 // 局部變量2 壓棧
         2: iadd    // 棧頂兩個元素相加,計算結(jié)果壓棧
         3: ireturn
      LineNumberTable:
        line 10: 0

動態(tài)鏈接

動態(tài)鏈接:Java虛擬機棧中,每個棧幀都包含一個指向運行時常量池該棧所屬方法的符號引用,持有該引用是為了支持方法調(diào)用過程中的動態(tài)鏈接(Dynamic Linking)。

方法返回地址/方法出口

方法返回地址/方法出口無論方法是否正常完成,都需要返回到方法被調(diào)用的位置,程序才能繼續(xù)進行

  • 方法執(zhí)行時有2種退出情況:
    • 正常退出,即正常執(zhí)行到任何方法的返回字節(jié)碼指令,如 RETURN、IRETURN、ARETURN等
    • 異常退出

無論何種退出情況,都將返回至方法當前被調(diào)用的位置。方法退出的過程相當于彈出當前棧幀,退出可能有3種方式:

  • 返回值壓入上層調(diào)用棧幀
  • 異常信息拋給能夠處理的棧幀
  • PC 計數(shù)器指向方法調(diào)用后的下一條指令

那么要講這個就會涉及到更底層的原理–字節(jié)碼。我們先看下我們上面代碼的字節(jié)碼文件。

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)
看著就是一個16字節(jié)的文件,看著像亂碼,其實每個都是有對應(yīng)的含義的,oracle官方是有專門的Jvm字節(jié)碼指令手冊來查詢每組指令對應(yīng)的含義的。那我們研究的,當然不是這個。

  • JDK有自帶一個javap的命令,可以將上述class文件生成一種更可讀的字節(jié)碼文件
  • 我們使用javap -c命令將class文件反編譯并輸出到TXT文件中。
Compiled from "Math.java"
public class com.example.demo.test1.Math {
  public static int initData;
 
  public static com.example.demo.bean.User user;
 
  public com.example.demo.test1.Math();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
 
  public int compute();
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: bipush        10
       9: imul
      10: istore_3
      11: iload_3
      12: ireturn
 
  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/example/demo/test1/Math
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method compute:()I
      12: pop
      13: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: ldc           #6                  // String test
      18: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      21: return
 
  static {};
    Code:
       0: sipush        666
       3: putstatic     #8                  // Field initData:I
       6: new           #9                  // class com/example/demo/bean/User
       9: dup
      10: invokespecial #10                 // Method com/example/demo/bean/User."<init>":()V
      13: putstatic     #11                 // Field user:Lcom/example/demo/bean/User;
      16: return
}

其中方法中的指令還是有點懵,我們舉compute()方法來看一下:

Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: bipush        10
       9: imul
      10: istore_3
      11: iload_3
      12: ireturn
  • 這幾行代碼就是對應(yīng)的我們代碼中compute()方法中的四行代碼。大家都知道越底層的代碼,代碼實現(xiàn)的行數(shù)越多,因為他會包含一些java代碼在運行時底層隱藏的一些細節(jié)原理。

    那么一樣的,這個Jvm指令官方也是有手冊可以查閱的,網(wǎng)上也有很多翻譯版本,大家如果想了解可自行百度。


0. 將int類型常量1壓入操作數(shù)棧

0: iconst_1     

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)


1. 將int類型值存入局部變量1

1: istore_1    
  • 局部變量1,在我們代碼中也就是第一個局部變量a,先給a在局部變量表中分配內(nèi)存,然后將int類型的值,也就是目前唯一的一個1存入局部變量a
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

2. 將int類型常量2壓入操作數(shù)棧

2: iconst_2

3. 將int類型值存入局部變量2

3: istore_2

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)


4. 從局部變量1中裝載int類型值

4: iload_1

5. 從局部變量2中裝載int類型值

5: iload_2
  • 這兩個代碼是將局部變量1和2,也就是a和b的值裝載到操作數(shù)棧

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)


6. 執(zhí)行int類型的加法

6: iadd
  • iadd指令一執(zhí)行,會將操作數(shù)棧中的1和2依次從棧底彈出并相加,然后把運算結(jié)果3在壓入操作數(shù)棧底。
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

7. 將一個8位帶符號整數(shù)壓入棧

7: bipush        10
  • 這個指令就是將10壓入棧
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

8. 執(zhí)行int類型的乘法

9: imul
  • 這里就類似上面的加法了,將3和10彈出棧,把結(jié)果30壓入棧
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

9. 將將int類型值存入局部變量3

10: istore_3
  • 這里大家就不陌生了吧,和第2步第3步是一樣的,將30存入局部變量3,也就是c

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)


10. 從局部變量3中裝載int類型值

11: iload_3
  • 這個前面也說了
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

11. 返回int類型值

12: ireturn
  • 這個就不用多說了,就是將操作數(shù)棧中的30返回

到這里就把我們compute()方法講解完了,講完有沒有對局部變量表和操作數(shù)棧的理解有所加深呢?說白了賦值號=后面的就是操作數(shù),在這些操作數(shù)進行賦值,運算的時候需要往內(nèi)存存放,那就是存放在操作數(shù)棧中,作為臨時存放操作數(shù)的一小塊內(nèi)存區(qū)域。


接下來我們再說說方法出口。

  • 方法出口說白了不就是方法執(zhí)行完了之后要出到哪里,那么我們知道上面compute()方法執(zhí)行完之后應(yīng)該回到main()方法第三行那么當main()方法調(diào)用compute()的時候,compute()棧幀中的方法出口就存儲了當前要回到的位置,那么當compute()方法執(zhí)行完之后,會根據(jù)方法出口中存儲的相關(guān)信息回到main()方法的相應(yīng)位置。

3.棧幀與函數(shù)調(diào)用

  • 如下圖:函數(shù)1中調(diào)用函數(shù)2,函數(shù)2中調(diào)用函數(shù)3,函數(shù)3調(diào)用函數(shù)4。當函數(shù)1被調(diào)用時,棧幀1入棧,當函數(shù)2調(diào)用時,棧幀2入棧。。。以此類推。當前正在執(zhí)行的函數(shù)所對應(yīng)的幀就是當前幀(位于棧頂),它保存著當前函數(shù)的局部變量、中間計算結(jié)果等數(shù)據(jù)。
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)
  • 當函數(shù)返回時,棧幀從java棧中被彈出,java方法區(qū)有2種返回函數(shù)的方式,一種是正常的函數(shù)返回,使用return指令,另一種是拋出異常。不管使用哪種方式,都會導(dǎo)致棧幀被彈出。
    • 每次函數(shù)調(diào)用都會產(chǎn)生對應(yīng)的棧幀,占用一定的棧內(nèi)存,如果棧內(nèi)存不足,當請求的棧深度大于最大可用棧深度時,系統(tǒng)會拋出StackOverflowError棧溢出錯誤。

使用遞歸,由于遞歸沒有出口,這段代碼可能會拋出棧溢出錯誤,在拋出棧溢出錯誤時,打印最大的調(diào)用深度

  public class TestStackDeep {
    private static int count =0;
    public static void recursion(){
      count ++;
      recursion();
    }

    public static void main(String[] args) {
      try{
        recursion();
      }catch(Throwable e){
        System.out.println("deep of calling ="+count);
        e.printStackTrace();
      }
    }
  }
  • 使用參數(shù)-Xss128K執(zhí)行上面代碼

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

在進行大約1079次調(diào)用之后,發(fā)生了棧溢出錯誤,通過增大-Xss的值,可以獲得更深的層次調(diào)用,嘗試使用參數(shù)-Xss256K執(zhí)行上述代碼,調(diào)用層次有明顯的增加:
【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)
結(jié)論:函數(shù)嵌套調(diào)用的層次在很大程度上由棧的大小決定,棧越大,函數(shù)支持的嵌套調(diào)用次數(shù)就越多。

4.棧幀與局部變量表

局部變量表是棧幀的組成部分之一。用于保存函數(shù)的參數(shù)以及局部變量,局部變量表隨著函數(shù)棧幀的彈出而銷毀。

  • 如果函數(shù)的參數(shù)和局部變量很多 或 很大,會使得局部變量表膨脹,從而每一次函數(shù)調(diào)用就會占用更多的??臻g,最終導(dǎo)致函數(shù)的嵌套調(diào)用次數(shù)減少。

例如:一個recursion()函數(shù)含有3個參數(shù)和10個局部變量,因此,其局部變量表含有13個變量,而第二個recursion()函數(shù)不再含有任何參數(shù)和局部變量,當這兩個函數(shù)被嵌套調(diào)用時,第二個recursion函數(shù)可以擁有更深的調(diào)用層次。

public class TestStackDeep2 {
  private static int count = 0;
  
  public static void recursion(long a,long b,long c){
    long e=1,f=2,g=3,h=4,i=5,k=6,q=7,x=8,y=9,z=10;
    count ++;
    recursion(a,b,c);
  }
  
  public static void recursion(){
    count++;
    recursion();
  }

  public static void main(String[] args) {
    try{
      recursion(0L,0L,0L);
      //recursion();
    }catch(Throwable e){
      System.out.println("deep of calling = "+count);
      e.printStackTrace();
    }
  }
}
  • 使用虛擬機參數(shù)-Xss128K 遞歸執(zhí)行上述代碼中的recursion(long a,long b,long c)函數(shù),輸出結(jié)果為:
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)
  • 使用虛擬機參數(shù)-Xss128K 遞歸執(zhí)行不帶參數(shù)的recursion()函數(shù)
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

四.本地方法棧

虛擬機棧的作用是一樣的,只不過虛擬機棧是服務(wù) Java 方法的,而本地方法棧是為虛擬機調(diào)用本地方法(Native方法)服務(wù)的

  • 特性和異常: 同虛擬機棧,請參考上面知識點。即線程私有,StackOverflowError、OutOfMemoryError
new Thread().start();
public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }

其中底層調(diào)用了一個start0()的方法,本地方法,底層通過C語言實現(xiàn)的

private native void start0();

那java代碼里為什么會有C語言實現(xiàn)的本地方法呢?

  • 大家都知道JAVA出來之前一個公司的系統(tǒng)百分之九十九都是使用C語言實現(xiàn)的,但是java出現(xiàn)后,很多項目都要轉(zhuǎn)為java開發(fā),那么新系統(tǒng)和舊系統(tǒng)就免不了要有交互,那么就需要本地方法來實現(xiàn)了,底層是調(diào)用C語言中的dll庫文件,就類似于java中的jar包,當然,如今跨語言的交互方式就很多了,比如`thrift,http接口方式,webservice等,當時并沒有這些方式,就只能通過本地方法來實現(xiàn)了。

    • 那么本地方法始終也是方法,每個線程在運行的時候,如果有運行到本地方法,那么必然也要產(chǎn)生局部變量等,那么就需要存儲在本地方法棧了。如果沒有本地方法,也就沒有本地方法棧了。

五.程序計數(shù)器(PC寄存器/指令切換器)

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

程序計數(shù)器/*PC寄存器(Program Counter Register)可以看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器,每個線程都有一個程序計數(shù)器來保存當前執(zhí)行指令的地址,一旦該指令被執(zhí)行,程序計數(shù)器會被更新至下條指令的地址。程序計數(shù)器是Java虛擬機規(guī)定的唯一不會發(fā)生內(nèi)存溢出的區(qū)域。

  • 這是一塊較小的內(nèi)存空間(可忽略不記),用于記錄當前線程所執(zhí)行的字節(jié)碼的行號指示器,字節(jié)碼解析器的工作是通過改變這個計數(shù)器的值,來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能,都需要依賴這個計數(shù)器來完成;

  • 特點:線程私有

  • 異常規(guī)定:無

那么Jvm虛擬機為什么要設(shè)置程序計數(shù)器這個結(jié)構(gòu)呢?

  • 因為Jvm的多線程是通過線程輪流切換并分配處理器執(zhí)行時間(cpu時間片)來的方式來實現(xiàn)的,也就是任何時刻,一個處理器(對于多核處理器來說是一個內(nèi)核)只會執(zhí)行一條線程中的指令。因此為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每個線程都有獨立的程序計數(shù)器。

    它被設(shè)計出來的目的,是為了讓多線程情況下的JAVA程序每個線程都能夠正常的工作,每個線程都有自己的程序計數(shù)器,用于保存線程的執(zhí)行情況,這樣在進行線程切換的時候就可以在上次執(zhí)行的基礎(chǔ)上繼續(xù)執(zhí)行了

六.元空間

注意:方法區(qū)是一種概念,而永久代和元空間是它的2種實現(xiàn)方式。

1.元空間概念

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)
方法區(qū)即我們常說的永久代(Permanent Generation), 也稱為非堆(No-Heap)、是線程共享的一塊內(nèi)存區(qū)域,用于存儲被 JVM 加載的類信息、常量、靜態(tài)變量、JIT即時編譯器編譯后的代碼 等數(shù)據(jù)

  • 運?時常量池: 運?時常量池是?法區(qū)的?部分,Class 文件中除了有 類的版本、字段、方法、接口等描述等信息外,還有一項信息是常量池,用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后存放到方法區(qū)的運行時常量池中
  • 字?量 : 字符串(JDK 8 移動到堆中)、final常量、基本數(shù)據(jù)類型的值(如Integer,管理-128–127的常量。)。
  • 符號引? : 類和結(jié)構(gòu)的完全限定名、字段的名稱和描述符、?法的名稱和描述符。

JDK1.8后永久代被元空間代替,元空間存儲在直接內(nèi)存(系統(tǒng)內(nèi)存),而不在虛擬機當中(不受JVM最大運行內(nèi)存的限制,只和本地內(nèi)存的大小有關(guān) 其他內(nèi)容比如類元信息、字段、靜態(tài)屬性、方法、常量等都移動到元空間區(qū)

  • 特點:線程共享

  • 異常規(guī)定:當方法無法滿足內(nèi)存分配需求時會拋出OutOfMemoryError異常。

    • 默認最小值為16MB,最大值為64MB,可以通過-XX:PermSize 和 -XX:MaxPermSize 參數(shù)限制方法區(qū)的大小
  • 運行時常量池是方法區(qū)的一部分,用于存放編譯器生成的各種字面量和符號引用。
    【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

  • JDK1.6字符串常量池在方法區(qū)中,1.7將放在方法區(qū)的字符串常量池放到中。**在JDK1.8時徹底去掉了永久代的概念,而在直接內(nèi)存中劃出一塊區(qū)域作為元空間,運行時常量池、類常量池都移動到元空間。。

2.為什么要使用元空間取代永久代的實現(xiàn)

1.避免OOM:

  • 方法區(qū)主要是存儲類的相關(guān)信息(包括類的字節(jié)碼文件) , 雖然永久代可以使用PerSize和MaxPerSize等參數(shù)設(shè)置永久代的空間大小, 但隨著ASM、Cglib等動態(tài)生成字節(jié)碼技術(shù)的出現(xiàn)可以修改對象字節(jié)碼信息后,無法控制類信息的大小, 因此對于永久代的大小指定比較困難,太小容易出現(xiàn)永久代溢出,太大則容易導(dǎo)致老年代溢出 ,即 java.lang.OutOfMemoryError: PermGen。
    • JDK1.8使用了元空間替換永久代,因為元空間是使用系統(tǒng)內(nèi)存,由系統(tǒng)的實際可用空間來控制,在一定程度上可以避免OOM的出現(xiàn),但是也需要通過指定MaxMetaspaceSize等參數(shù)來控制大小。

2.提高GC性能:

  • 永久代的垃圾收集是和老年代捆綁在一起的,所以無論兩者誰滿了,都會觸發(fā)永久代和老年代的垃圾收集。

    JDK1.7時永久代的部分數(shù)據(jù)已經(jīng)從Java的永久代中轉(zhuǎn)移到了堆中,如:符號引用、字符串常量池

    • 使用元空間替換后,簡化了Full GC,減少了GC的時間(因為GC時不需要再掃描永久代中的數(shù)據(jù)),提高了GC的性能在元空間中,只有少量指針指向堆,如類的元數(shù)據(jù)中指向class對象的指針。

3.Hotspot和JRockit合并:

  • 官方原因,永久代只是Hotspot虛擬機中存在的概念,JRockit中并沒有這個說法,JDK8需要整合Hotspot和JRockit,所以廢棄了永久代,引入了元空間。

七.拓展—直接內(nèi)存

直接內(nèi)存(Direct Memory):  也叫堆外內(nèi)存,直接內(nèi)存并不是Jvm管理的內(nèi)存,可以這樣理解就是Jvm以外的機器內(nèi)存,比如,你有4G的內(nèi)存,Jvm占用了1G,則其余的3G就是直接內(nèi)存

  • 在JDK 1.4中新加入了NIO(New Input/Output)類,引入了一種基于通道(Channel)緩沖區(qū)(Buffer)的I/O方式,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內(nèi)存的引用進行操作。通
    • 通常訪問直接內(nèi)存的速度會優(yōu)于Java堆。因此出于性能的考慮,讀寫頻繁的場合可以考慮使用直接內(nèi)存,避免了在Java堆和Native堆中來回復(fù)制數(shù)據(jù)。
      • 但系統(tǒng)內(nèi)存是有限的,Java堆和直接內(nèi)存的總和依然受限于操作系統(tǒng)能給出的最大內(nèi)存。

八.對象創(chuàng)建

1.對象組成

對象在內(nèi)存中存儲的布局可以分為3塊區(qū)域:對象頭(Header)、實例數(shù)據(jù)(Instance Data)對齊填充(Padding)
【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

HotSpot虛擬機的對象頭包括2部分信息:

Mark Word

  • 第一部分markword,用于存儲對象自身的運行時數(shù)據(jù),如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標志、線程持有的鎖、偏向線程ID、偏向時間戳等,

  • 對象頭的另外一部分是klass類型指針(Klass Pointer),即對象指向它的Class類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例.

  • 數(shù)組長度(只有數(shù)組對象有): 如果對象是一個數(shù)組, 那在對象頭中還必須有一塊數(shù)據(jù)用于記錄數(shù)組長度.

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

  • 第二部分實例數(shù)據(jù),是對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內(nèi)容。無論是從父類繼承下來的,還是在子類中定義的,都需要記錄起來。

對齊填充

  • 第三部分對齊填充并不是必然存在的,也沒有特別的含義,它僅僅起著占位符的作用。由于HotSpot VM的自動內(nèi)存管理系統(tǒng)要求對象起始地址必須是8字節(jié)的整數(shù)倍,換句話說,就是對象的大小必須是8字節(jié)的整數(shù)倍。而對象頭部分正好是8字節(jié)的倍數(shù)(1倍或者2倍),因此,當對象實例數(shù)據(jù)部分沒有對齊時,就需要通過對齊填充來補全。

其中的類型指針就是那條紅色的線,那是怎么聯(lián)系的呢?
【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

類加載其實最終是以Class對象的形式存儲在方法區(qū)中的,math和math2都是由同一個類new出來的,當對象被new時,都會在對象頭中存儲一個指向類元信息的指針,這就是Klass Pointer類型指針

2.Java中提供的幾種對象創(chuàng)建方式

  • 使用new關(guān)鍵字->調(diào)用了構(gòu)造方法
  • 使用ClassnewInstance方法->調(diào)用了構(gòu)造方法
  • 使用Constructor類的newInstance方法->調(diào)用了構(gòu)造方法
  • 使用clone方法->沒有調(diào)用構(gòu)造方法
  • 使用反序列化->`沒有調(diào)用構(gòu)造方法

3.對象創(chuàng)建的主要流程

【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景,JVM,jvm,堆,虛擬機棧,元空間,永久代,運行時數(shù)據(jù)區(qū)

虛擬機遇到一條new指令時,先檢查常量池是否已經(jīng)加載相應(yīng)的類,如果沒有,必須先執(zhí)行相應(yīng)的類加載。類加載通過后,接下來分配內(nèi)存。若Java堆中內(nèi)存是絕對規(guī)整的,使用 “指針碰撞“ 方式分配內(nèi)存;如果不是規(guī)整的,就從空閑列表中分配,叫做 ”空閑列表“ 方式。

  • 分內(nèi)存時還需要考慮一個問題-并發(fā),也有2種方式: CAS同步處理,或者本地線程分配緩沖(Thread Local Allocation Buffer, TLAB)。然后內(nèi)存空間初始化操作,接著是做一些必要的對象設(shè)置(元信息、哈希碼…),最后執(zhí)行<init>方法。

4.對象內(nèi)存分配2種方式

類加載完成后,接著會在Java堆中劃分一塊內(nèi)存分配給對象。內(nèi)存分配根據(jù)Java堆是否規(guī)整,有2種方式:

  • 指針碰撞: 如果Java堆的內(nèi)存是規(guī)整即所有用過的內(nèi)存放在一邊,而空閑的的放在另一邊。分配內(nèi)存時將位于中間的指針指示器向空閑的內(nèi)存移動一段與對象大小相等的距離,這樣便完成分配內(nèi)存工作。
  • 空閑列表: 如果Java堆的內(nèi)存是不規(guī)整的,已使用的內(nèi)存和空閑的內(nèi)存相互交錯, 那就沒辦法簡單的進行指針碰撞了, 必須由由虛擬機維護一個列表來記錄那些內(nèi)存是可用的,在分配的時候從列表找到一塊足夠大的內(nèi)存分配給對象,并在分配后更新列表記錄。

5.處理并發(fā)安全問題

對象的創(chuàng)建在虛擬機中是一個非常頻繁的行為,哪怕只是修改一個指針所指向的位置,在并發(fā)情況下也是不安全的,可能出現(xiàn)正在給對象 A 分配內(nèi)存,指針還沒來得及修改,對象 B 又同時使用了原來的指針來分配內(nèi)存的情況。

解決這個問題有兩種方案:

  1. 對分配內(nèi)存空間的動作進行同步處理(采用 CAS + 失敗重試保障更新操作的原子性);
  2. 把內(nèi)存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在 Java 堆中預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖(Thread Local Allocation Buffer, TLAB)。哪個線程要分配內(nèi)存,就在哪個線程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 時,才需要同步鎖。通過-XX:+/-UserTLAB參數(shù)來設(shè)定虛擬機是否使用TLAB。

面試題:(Java實習(xí)生)每日10道面試題打卡——JVM篇文章來源地址http://www.zghlxwxcb.cn/news/detail-830768.html

到了這里,關(guān)于【Jvm】運行時數(shù)據(jù)區(qū)域(Runtime Data Area)原理及應(yīng)用場景的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 【jvm系列-05】精通運行時數(shù)據(jù)區(qū)共享區(qū)域---方法區(qū)

    【jvm系列-05】精通運行時數(shù)據(jù)區(qū)共享區(qū)域---方法區(qū)

    JVM系列整體欄目 內(nèi)容 鏈接地址 【一】初識虛擬機與java虛擬機 https://blog.csdn.net/zhenghuishengq/article/details/129544460 【二】jvm的類加載子系統(tǒng)以及jclasslib的基本使用 https://blog.csdn.net/zhenghuishengq/article/details/129610963 【三】運行時私有區(qū)域之虛擬機棧、程序計數(shù)器、本地方法棧 https

    2023年04月09日
    瀏覽(23)
  • jvm復(fù)習(xí),深入理解java虛擬機一:運行時數(shù)據(jù)區(qū)域

    jvm復(fù)習(xí),深入理解java虛擬機一:運行時數(shù)據(jù)區(qū)域

    ? ? ? ? 程序計數(shù)器 (Program Counter Register) 它是程序控制流的指示器,簡單來說,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個獨立的程序計數(shù)器 ? ? ? ? ?Java虛擬機棧 (Java Virtual Machine Stack)也是線程私有的,它的生命周期 與線程相同。虛擬機棧描述

    2024年01月22日
    瀏覽(17)
  • java-JVM內(nèi)存區(qū)域&JVM運行時內(nèi)存

    java-JVM內(nèi)存區(qū)域&JVM運行時內(nèi)存

    JVM 內(nèi)存區(qū)域主要分為線程私有區(qū)域【程序計數(shù)器、虛擬機棧、本地方法區(qū)】、線程共享區(qū)域【JAVA 堆、方法區(qū)】、直接內(nèi)存。 線程私有數(shù)據(jù)區(qū)域生命周期與線程相同, 依賴用戶線程的啟動/結(jié)束 而 創(chuàng)建/銷毀(在 HotspotVM 內(nèi), 每個線程都與操作系統(tǒng)的本地線程直接映射, 因此這部

    2024年02月12日
    瀏覽(20)
  • JVM運行時區(qū)域——對象創(chuàng)建內(nèi)存分配過程

    JVM運行時區(qū)域——對象創(chuàng)建內(nèi)存分配過程

    ????????新創(chuàng)建的對象 , 都存放在伊甸園區(qū)域 ,當垃圾回收時,將伊甸園區(qū)域的垃圾數(shù)據(jù)銷毀,然后將存活的對象轉(zhuǎn)移到幸存者0區(qū)域,之后創(chuàng)建的新的對象還是存放在伊甸園區(qū)域,等到再次垃圾回收后,將伊甸園區(qū)域和幸存者0區(qū)域中存活的對象一起轉(zhuǎn)移到幸存者1區(qū)域中

    2024年02月15日
    瀏覽(22)
  • 華為在ospf area 0單區(qū)域的情況下結(jié)合pbr對數(shù)據(jù)包的來回路徑進行控制

    華為在ospf area 0單區(qū)域的情況下結(jié)合pbr對數(shù)據(jù)包的來回路徑進行控制

    配置思路: 兩邊去的包在R1上用mqc進行下一跳重定向 兩邊回程包在R4上用mqc進行下一跳重定向 最終讓內(nèi)網(wǎng) 192.168.10.0出去的數(shù)據(jù)包來回全走上面R-1-2-4 192.168.20.0出去的數(shù)據(jù)包來回全走 下面R1-3-4 R2和R3就是簡單ospf配置和宣告,其它沒有配置,這里就不貼上去了。 dis current-config

    2024年02月13日
    瀏覽(17)
  • JVM工作原理與實戰(zhàn)(十六):運行時數(shù)據(jù)區(qū)-Java虛擬機棧

    JVM工作原理與實戰(zhàn)(十六):運行時數(shù)據(jù)區(qū)-Java虛擬機棧

    JVM工作原理與實戰(zhàn) RabbitMQ入門指南 從零開始了解大數(shù)據(jù) 目錄 專欄導(dǎo)航 前言 一、運行時數(shù)據(jù)區(qū) 二、Java虛擬機棧 1.棧幀的組成 2.局部變量表 3.操作數(shù)棧 4.幀數(shù)據(jù) 總結(jié) JVM作為Java程序的運行環(huán)境,其負責(zé)解釋和執(zhí)行字節(jié)碼,管理內(nèi)存,確保安全,支持多線程和提供性能監(jiān)控工具

    2024年01月20日
    瀏覽(25)
  • LVGL misc area 方塊區(qū)域通用函數(shù)(lv_area.c)

    更多源碼分析請訪問: LVGL 源碼分析大全

    2024年02月07日
    瀏覽(21)
  • JVM7:垃圾回收是什么?從運行時數(shù)據(jù)區(qū)看垃圾回收到底回收哪塊區(qū)域?垃圾回收如何去回收?垃圾回收策略,引用計數(shù)算法及循環(huán)引用問題,可達性分析算法

    JVM7:垃圾回收是什么?從運行時數(shù)據(jù)區(qū)看垃圾回收到底回收哪塊區(qū)域?垃圾回收如何去回收?垃圾回收策略,引用計數(shù)算法及循環(huán)引用問題,可達性分析算法

    在Java中,垃圾回收(Garbage Collection,簡稱GC),是自動管理內(nèi)存的機制。它負責(zé)檢測不再使用的對象,并釋放它們所占用的內(nèi)存,以供其他對象使用。 JVM內(nèi)存模型認識的差不多了,就應(yīng)該思考,什么樣的內(nèi)存模型適合什么樣的GC策略,包括垃圾回收為什么會出現(xiàn)。實際上,很多

    2024年02月11日
    瀏覽(20)
  • css 安全區(qū)域 safe-area-inset-

    css 安全區(qū)域 safe-area-inset-

    安全區(qū)域與邊界是iOS11 新增特性。 安全區(qū)域的內(nèi)容不受圓角(corners)、齊劉海(sensor housing)、小黑條(Home Indicator)影響。 Webkit 為此增加了相應(yīng)的CSS 函數(shù),用于獲取安全區(qū)域邊界值。 安全區(qū)域邊界有4個預(yù)定義變量: safe-area-inset-left:安全區(qū)域距離左邊邊界距離 safe-area

    2023年04月20日
    瀏覽(22)
  • 微信小程序 movable-area 區(qū)域拖動動態(tài)組件演示

    微信小程序 movable-area 區(qū)域拖動動態(tài)組件演示

    movable-area 組件在小程序中的作用是用于創(chuàng)建一個可移動的區(qū)域,可以在該區(qū)域內(nèi)拖動視圖或內(nèi)容。這個組件常用于實現(xiàn)可拖動的容器或可滑動的列表等交互效果。 使用 movable-area 組件可以對其內(nèi)部的 movable-view 組件進行拖動操作,可以通過設(shè)置不同的屬性和事件來自定義拖動

    2024年02月03日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包