自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面試官:說說JVM內(nèi)存整體結(jié)構(gòu)?線程私有還是共享的?

開發(fā) 前端
一旦對(duì)象在 TLAB 空間分配內(nèi)存失敗時(shí),JVM 就會(huì)嘗試著通過使用加鎖機(jī)制確保數(shù)據(jù)操作的原子性,從而直接在 Eden 空間中分配內(nèi)存。

JVM 整體架構(gòu),中間部分就是 Java 虛擬機(jī)定義的各種運(yùn)行時(shí)數(shù)據(jù)區(qū)域。

圖片圖片

Java 虛擬機(jī)定義了若干種程序運(yùn)行期間會(huì)使用到的運(yùn)行時(shí)數(shù)據(jù)區(qū),其中有一些會(huì)隨著虛擬機(jī)啟動(dòng)而創(chuàng)建,隨著虛擬機(jī)退出而銷毀。另外一些則是與線程一一對(duì)應(yīng)的,這些與線程一一對(duì)應(yīng)的數(shù)據(jù)區(qū)域會(huì)隨著線程開始和結(jié)束而創(chuàng)建和銷毀。

線程私有:程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法區(qū)

線程共享:堆、方法區(qū), 堆外內(nèi)存(Java7的永久代或JDK8的元空間、代碼緩存)

什么是程序計(jì)數(shù)器(線程私有)?

PC 寄存器用來存儲(chǔ)指向下一條指令的地址,即將要執(zhí)行的指令代碼。由執(zhí)行引擎讀取下一條指令。

PC寄存器為什么會(huì)被設(shè)定為線程私有的?

多線程在一個(gè)特定的時(shí)間段內(nèi)只會(huì)執(zhí)行其中某一個(gè)線程方法,CPU會(huì)不停的做任務(wù)切換,這樣必然會(huì)導(dǎo)致經(jīng)常中斷或恢復(fù)。為了能夠準(zhǔn)確的記錄各個(gè)線程正在執(zhí)行的當(dāng)前字節(jié)碼指令地址,所以為每個(gè)線程都分配了一個(gè)PC寄存器,每個(gè)線程都獨(dú)立計(jì)算,不會(huì)互相影響。

什么是虛擬機(jī)棧(線程私有)?

主管 Java 程序的運(yùn)行,它保存方法的局部變量、部分結(jié)果,并參與方法的調(diào)用和返回。每個(gè)線程在創(chuàng)建的時(shí)候都會(huì)創(chuàng)建一個(gè)虛擬機(jī)棧,其內(nèi)部保存一個(gè)個(gè)的棧幀(Stack Frame),對(duì)應(yīng)著一次次 Java 方法調(diào)用,是線程私有的,生命周期和線程一致。

特點(diǎn)?

  1. 棧是一種快速有效的分配存儲(chǔ)方式,訪問速度僅次于程序計(jì)數(shù)器
  2. JVM 直接對(duì)虛擬機(jī)棧的操作只有兩個(gè):每個(gè)方法執(zhí)行,伴隨著入棧(進(jìn)棧/壓棧),方法執(zhí)行結(jié)束出棧
  3. 棧不存在垃圾回收問題
  4. 可以通過參數(shù)-Xss來設(shè)置線程的最大??臻g,棧的大小直接決定了函數(shù)調(diào)用的最大可達(dá)深度

該區(qū)域有哪些異常?

  1. 如果采用固定大小的 Java 虛擬機(jī)棧,那每個(gè)線程的 Java 虛擬機(jī)棧容量可以在線程創(chuàng)建的時(shí)候獨(dú)立選定。如果線程請(qǐng)求分配的棧容量超過 Java 虛擬機(jī)棧允許的最大容量,Java 虛擬機(jī)將會(huì)拋出一個(gè) StackOverflowError 異常
  2. 如果 Java 虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò)展,并且在嘗試擴(kuò)展的時(shí)候無法申請(qǐng)到足夠的內(nèi)存,或者在創(chuàng)建新的線程時(shí)沒有足夠的內(nèi)存去創(chuàng)建對(duì)應(yīng)的虛擬機(jī)棧,那 Java 虛擬機(jī)將會(huì)拋出一個(gè)OutOfMemoryError異常

棧幀的內(nèi)部結(jié)構(gòu)?

  1. 局部變量表(Local Variables)
  2. 操作數(shù)棧(Operand Stack)(或稱為表達(dá)式棧)
  3. 動(dòng)態(tài)鏈接(Dynamic Linking):指向運(yùn)行時(shí)常量池的方法引用
  4. 方法返回地址(Return Address):方法正常退出或異常退出的地址
  5. 一些附加信息

圖片圖片

Java虛擬機(jī)棧如何進(jìn)行方法計(jì)算的?

以如下代碼為例:

private static int add(int a, int b) {
    int c = 0;
    c = a + b;
    return c;
}

可以通過jsclass 等工具查看bytecode

圖片圖片

壓棧的步驟如下:

0:   iconst_0 // 0壓棧
1:   istore_2 // 彈出int,存放于局部變量2
2:   iload_0  // 把局部變量0壓棧
3:   iload_1  // 局部變量1壓棧
4:   iadd     //彈出2個(gè)變量,求和,結(jié)果壓棧
5:   istore_2 //彈出結(jié)果,放于局部變量2
6:   iload_2  //局部變量2壓棧
7:   ireturn  //返回

如果計(jì)算100+98的值,那么操作數(shù)棧的變化如下圖:

圖片圖片

什么是本地方法棧(線程私有)?

  • 本地方法接口

一個(gè) Native Method 就是一個(gè) Java 調(diào)用非 Java 代碼的接口。我們知道的 Unsafe 類就有很多本地方法。

  • 本地方法棧(Native Method Stack)

Java 虛擬機(jī)棧用于管理 Java 方法的調(diào)用,而本地方法棧用于管理本地方法的調(diào)用

什么是方法區(qū)(線程共享)?

方法區(qū)(method area)只是 JVM 規(guī)范中定義的一個(gè)概念,用于存儲(chǔ)類信息、常量池、靜態(tài)變量、JIT編譯后的代碼等數(shù)據(jù),并沒有規(guī)定如何去實(shí)現(xiàn)它,不同的廠商有不同的實(shí)現(xiàn)。而永久代(PermGen)**是 **Hotspot** 虛擬機(jī)特有的概念, Java8 的時(shí)候又被**元空間取代了,永久代和元空間都可以理解為方法區(qū)的落地實(shí)現(xiàn)。

JDK1.8之前調(diào)節(jié)方法區(qū)大?。?/p>

-XX:PermSize=N //方法區(qū)(永久代)初始大小
-XX:MaxPermSize=N //方法區(qū)(永久代)最大大小,超出這個(gè)值將會(huì)拋出OutOfMemoryError

JDK1.8開始方法區(qū)(HotSpot的永久代)被徹底刪除了,取而代之的是元空間,元空間直接使用的是本機(jī)內(nèi)存。參數(shù)設(shè)置:

-XX:MetaspaceSize=N //設(shè)置Metaspace的初始(和最小大?。?-XX:MaxMetaspaceSize=N //設(shè)置Metaspace的最大大小

棧、堆、方法區(qū)的交互關(guān)系

圖片圖片

永久代和元空間內(nèi)存使用上的差異?

Java虛擬機(jī)規(guī)范中只定義了方法區(qū)用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量和即時(shí)編譯后的代碼等數(shù)據(jù)

  1. jdk1.7開始符號(hào)引用存儲(chǔ)在native heap中,字符串常量和靜態(tài)類型變量存儲(chǔ)在普通的堆區(qū)中,但分離的并不徹底,此時(shí)永久代中還保存另一些與類的元數(shù)據(jù)無關(guān)的雜項(xiàng)
  2. jdk8后HotSpot 原永久代中存儲(chǔ)的類的元數(shù)據(jù)將存儲(chǔ)在metaspace中,而類的靜態(tài)變量和字符串常量將放在Java堆中,metaspace是方法區(qū)的一種實(shí)現(xiàn),只不過它使用的不是虛擬機(jī)內(nèi)的內(nèi)存,而是本地內(nèi)存。在元空間中保存的數(shù)據(jù)比永久代中純粹很多,就只是類的元數(shù)據(jù),這些信息只對(duì)編譯期或JVM的運(yùn)行時(shí)有用。
  3. 永久代有一個(gè)JVM本身設(shè)置固定大小上線,無法進(jìn)行調(diào)整,而元空間使用的是直接內(nèi)存,受本機(jī)可用內(nèi)存的限制,并且永遠(yuǎn)不會(huì)得到j(luò)ava.lang.OutOfMemoryError。
  4. 符號(hào)引用沒有存在元空間中,而是存在native heap中,這是兩個(gè)方式和位置,不過都可以算作是本地內(nèi)存,在虛擬機(jī)之外進(jìn)行劃分,沒有設(shè)置限制參數(shù)時(shí)只受物理內(nèi)存大小限制,即只有占滿了操作系統(tǒng)可用內(nèi)存后才OOM。

堆區(qū)內(nèi)存是怎么細(xì)分的?

對(duì)于大多數(shù)應(yīng)用,Java 堆是 Java 虛擬機(jī)管理的內(nèi)存中最大的一塊,被所有線程共享。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例以及數(shù)據(jù)都在這里分配內(nèi)存。

為了進(jìn)行高效的垃圾回收,虛擬機(jī)把堆內(nèi)存邏輯上劃分成三塊區(qū)域(分代的唯一理由就是優(yōu)化 GC 性能):

  1. 新生帶(年輕代):新對(duì)象和沒達(dá)到一定年齡的對(duì)象都在新生代
  2. 老年代(養(yǎng)老區(qū)):被長時(shí)間使用的對(duì)象,老年代的內(nèi)存空間應(yīng)該要比年輕代更大

 

圖片

Java 虛擬機(jī)規(guī)范規(guī)定,Java 堆可以是處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,像磁盤空間一樣。實(shí)現(xiàn)時(shí),既可以是固定大小,也可以是可擴(kuò)展的,主流虛擬機(jī)都是可擴(kuò)展的(通過 -Xmx 和 -Xms 控制),如果堆中沒有完成實(shí)例分配,并且堆無法再擴(kuò)展時(shí),就會(huì)拋出 OutOfMemoryError 異常。

  • 年輕代 (Young Generation)

年輕代是所有新對(duì)象創(chuàng)建的地方。當(dāng)填充年輕代時(shí),執(zhí)行垃圾收集。這種垃圾收集稱為 Minor GC。年輕一代被分為三個(gè)部分——伊甸園(Eden Memory)和兩個(gè)幸存區(qū)(Survivor Memory,被稱為from/to或s0/s1),默認(rèn)比例是8:1:1

  1. 大多數(shù)新創(chuàng)建的對(duì)象都位于 Eden 內(nèi)存空間中
  2. 當(dāng) Eden 空間被對(duì)象填充時(shí),執(zhí)行Minor GC,并將所有幸存者對(duì)象移動(dòng)到一個(gè)幸存者空間中
  3. Minor GC 檢查幸存者對(duì)象,并將它們移動(dòng)到另一個(gè)幸存者空間。所以每次,一個(gè)幸存者空間總是空的
  4. 經(jīng)過多次 GC 循環(huán)后存活下來的對(duì)象被移動(dòng)到老年代。通常,這是通過設(shè)置年輕一代對(duì)象的年齡閾值來實(shí)現(xiàn)的,然后他們才有資格提升到老一代
  • 老年代(Old Generation)

舊的一代內(nèi)存包含那些經(jīng)過許多輪小型 GC 后仍然存活的對(duì)象。通常,垃圾收集是在老年代內(nèi)存滿時(shí)執(zhí)行的。老年代垃圾收集稱為 主GC(Major GC),通常需要更長的時(shí)間。

大對(duì)象直接進(jìn)入老年代(大對(duì)象是指需要大量連續(xù)內(nèi)存空間的對(duì)象)。這樣做的目的是避免在 Eden 區(qū)和兩個(gè)Survivor 區(qū)之間發(fā)生大量的內(nèi)存拷貝

圖片圖片

JVM中對(duì)象在堆中的生命周期?

  1. 在 JVM 內(nèi)存模型的堆中,堆被劃分為新生代和老年代

新生代又被進(jìn)一步劃分為 Eden區(qū) 和 Survivor區(qū),Survivor 區(qū)由 From Survivor 和 To Survivor 組成

  1. 當(dāng)創(chuàng)建一個(gè)對(duì)象時(shí),對(duì)象會(huì)被優(yōu)先分配到新生代的 Eden 區(qū)

此時(shí) JVM 會(huì)給對(duì)象定義一個(gè)對(duì)象年輕計(jì)數(shù)器(-XX:MaxTenuringThreshold)

  1. 當(dāng) Eden 空間不足時(shí),JVM 將執(zhí)行新生代的垃圾回收(Minor GC)

JVM 會(huì)把存活的對(duì)象轉(zhuǎn)移到 Survivor 中,并且對(duì)象年齡 +1

對(duì)象在 Survivor 中同樣也會(huì)經(jīng)歷 Minor GC,每經(jīng)歷一次 Minor GC,對(duì)象年齡都會(huì)+1

  1. 如果分配的對(duì)象超過了-XX:PetenureSizeThreshold,對(duì)象會(huì)直接被分配到老年代

JVM中對(duì)象的分配過程?

為對(duì)象分配內(nèi)存是一件非常嚴(yán)謹(jǐn)和復(fù)雜的任務(wù),JVM 的設(shè)計(jì)者們不僅需要考慮內(nèi)存如何分配、在哪里分配等問題,并且由于內(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)伊甸園的空間填滿時(shí),程序又需要?jiǎng)?chuàng)建對(duì)象,JVM 的垃圾回收器將對(duì)伊甸園區(qū)進(jìn)行垃圾回收(Minor GC),將伊甸園區(qū)中的不再被其他對(duì)象所引用的對(duì)象進(jìn)行銷毀。再加載新的對(duì)象放到伊甸園區(qū)
  3. 然后將伊甸園中的剩余對(duì)象移動(dòng)到幸存者 0 區(qū)
  4. 如果再次觸發(fā)垃圾回收,此時(shí)上次幸存下來的放到幸存者 0 區(qū),如果沒有回收,就會(huì)放到幸存者 1 區(qū)
  5. 如果再次經(jīng)歷垃圾回收,此時(shí)會(huì)重新放回幸存者 0 區(qū),接著再去幸存者 1 區(qū)
  6. 什么時(shí)候才會(huì)去養(yǎng)老區(qū)呢?默認(rèn)是 15 次回收標(biāo)記
  7. 在養(yǎng)老區(qū),相對(duì)悠閑。當(dāng)養(yǎng)老區(qū)內(nèi)存不足時(shí),再次觸發(fā) Major GC,進(jìn)行養(yǎng)老區(qū)的內(nèi)存清理
  8. 若養(yǎng)老區(qū)執(zhí)行了 Major GC 之后發(fā)現(xiàn)依然無法進(jìn)行對(duì)象的保存,就會(huì)產(chǎn)生 OOM 異常

什么是 TLAB (Thread Local Allocation Buffer)?

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

為什么要有 TLAB ?

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

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

在程序中,可以通過 -XX:UseTLAB 設(shè)置是否開啟 TLAB 空間。

默認(rèn)情況下,TLAB 空間的內(nèi)存非常小,僅占有整個(gè) Eden 空間的 1%,我們可以通過 -XX:TLABWasteTargetPercent 設(shè)置 TLAB 空間所占用 Eden 空間的百分比大小。

一旦對(duì)象在 TLAB 空間分配內(nèi)存失敗時(shí),JVM 就會(huì)嘗試著通過使用加鎖機(jī)制確保數(shù)據(jù)操作的原子性,從而直接在 Eden 空間中分配內(nèi)存。

責(zé)任編輯:武曉燕 來源: 魯大猿
相關(guān)推薦

2024-02-21 07:40:17

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

2024-03-11 18:18:58

項(xiàng)目Spring線程池

2024-09-12 08:35:06

2020-03-10 08:01:05

Java堆內(nèi)存線程共享

2021-04-19 18:56:58

大數(shù)字符串運(yùn)算

2024-11-19 15:13:02

2023-12-27 18:16:39

MVCC隔離級(jí)別幻讀

2025-04-16 00:00:01

JWT客戶端存儲(chǔ)加密令

2025-04-08 00:00:00

@AsyncSpring異步

2024-08-22 10:39:50

@Async注解代理

2024-03-05 10:33:39

AOPSpring編程

2024-05-30 08:04:20

Netty核心組件架構(gòu)

2024-02-20 08:13:35

類加載引用Class

2024-03-14 14:56:22

反射Java數(shù)據(jù)庫連接

2024-07-31 08:28:37

DMAIOMMap

2021-11-25 10:18:42

RESTfulJava互聯(lián)網(wǎng)

2024-12-06 07:00:00

2024-09-20 08:36:43

零拷貝數(shù)據(jù)傳輸DMA

2020-07-02 07:52:11

RedisHash映射

2021-08-09 07:47:40

Git面試版本
點(diǎn)贊
收藏

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