「每日五分鐘,玩轉JVM」:執行緒共享區

  • 2019 年 10 月 3 日
  • 筆記

前言

上一篇中,我們了解了JVM中的執行緒獨佔區,這節課我們就來了解一下JVM中的執行緒共享區,JVM中的執行緒共享區是跟隨JVM啟動時一起創建的,包括堆(Heap)和方法區()兩部分,而執行緒獨佔區的程式計數器,虛擬機棧,本地方法棧的生命周期都是跟隨執行緒的,隨執行緒的創建而誕生,隨執行緒的銷毀而銷毀。

堆(Heap)

堆記憶體作為JVM管理的記憶體中最大的一塊,用於存放我們的對象實例,我們經常會把JVM的記憶體簡單的分為堆記憶體和棧記憶體,這樣說雖然有些片面,但是也有這麼說的道理,這兩塊兒一個作為執行程式的,一個作為存放對象的,是JVM中最為重要的兩塊兒記憶體。所以,我們的垃圾收集一般是針對的用於存放對象的堆記憶體,所以堆記憶體有時候也會被稱為GC堆。

從記憶體分配的角度上來說,堆記憶體中包含了新生代記憶體和老年代記憶體,而年輕代又分為Eden和Survivor區。Survivor區由From Survivor和To Survivor組成。Eden區佔大容量,Survivor兩個區佔小容量,默認比例是8:1:1,而且JVM 每次只會使用 Eden 和其中的一塊 Survivor 區域來為對象服務,所以無論什麼時候,總是有一塊 Survivor 區域是空閑著的。

這樣設計的原因是為了更方便的進行垃圾收集,我們會在後面垃圾收集的章節中去詳細的講解。

TLAB

TLAB的全稱是Thread Local Allocation Buffer,即執行緒本地分配快取區,這是一個處於堆記憶體中執行緒私有的記憶體分配區域,默認情況下這個區域就是開啟的,當然我們也可以在啟動時配置XX:+UseTLAB去開啟該區域,這個區域所佔空間非常的小,默認情況下只佔Eden區域的1%,我們也可以通過也XX:TLABWasteTargetPercent設置TLAB空間所佔用Eden空間的百分比大小。

方法區

方法區存儲虛擬機載入的類資訊常量靜態變數,即時編譯器編譯後的程式碼等數據,在Java虛擬機的規範中,把方法區認為是堆記憶體的邏輯部分,但是實際上他們是完全隔離的。

在JDK 8 之前,方法區被稱為(或者可以說是被實現為)持久代,永久代(Perman Gen),而在 JDK 8 之後,取消了永久代的概念,取而代之的實現是元空間(MetaSpace),原本位於永久代中的運行時常量池和靜態變數都存儲到了堆中,而其餘的內容則是移到了元空間。

元空間的本質和永久代類似,都是對JVM規範中方法區的實現,它們之間最大的區別在於:元空間並不在虛擬機中,而是使用本地記憶體。因此,默認情況下,元空間的大小僅受本地記憶體限制,但可以通過以下參數來指定元空間的大小:-XX:MetaspaceSize-XX:MaxMetaspaceSize

所以我們前幾年JDK7盛行的時候OOM錯誤消息是這樣的:

java.lang.OutOfMemoryError:PermGen space

而在近幾年JDK8的使用中遇到的OOM是這樣的:

java.lang.OutOfMemoryError:Metaspace

運行時常量池

運行時常量池位於元空間中,用於存儲編譯期生成的各種字面量和符號引用,而這裡需要注意一點:字元串常量池從JDK 7 之後就移到了堆記憶體中去管理,但是運行時常量是仍然位於方法區基於JDK 8 的新實現——元空間中。

網上有部分聲音說運行時常量池在JDK8移到了堆記憶體中,其實這種說法是錯誤的,真正移到堆記憶體的是字元串常量池,並且是在JDK7的更新中就已經移到了堆中。

更詳細的關於常量池的資訊我們會在類結構中去學習。

後話

每天五分鐘,跟Vi玩轉JVM!

下篇解密一個對象的誕生!

公眾號

本文首發於公眾號,掃碼關注即可獲取最新文章