Hotspot JVM 中的 Java 線程與原生操做系統線程有直接的映射關係。當線程本地存儲、緩 衝區分配、同步對象、棧、程序計數器等準備好之後,就會建立一個操做系統原生線程。 Java 線程結束,原生線程隨之被回收。操做系統負責調度全部線程,並把它們分配到任何可 用的 CPU 上。當原生線程初始化完畢,就會調用 Java 線程的 run() 方法。當線程結束時,會釋放原生線程和 Java 線程的全部資源。 java
Hotspot JVM 後臺運行的系統線程主要有下面幾個:算法
虛擬機線程 (VM thread)數組 |
這個線程等待 JVM 到達安全點操做出現。這些操做必需要在獨立的線程裏執行,由於當 堆修改沒法進行時,線程都須要 JVM 位於安全點。這些操做的類型有:stop-the- world 垃圾回收、線程棧 dump、線程暫停、線程偏向鎖(biased locking)解除。安全 |
週期性任務線程數據結構 |
這線程負責定時器事件(也就是中斷),用來調度週期性操做的執行。spa |
GC 線程操作系統 |
這些線程支持 JVM 中不一樣的垃圾回收活動。線程 |
編譯器線程3d |
這些線程在運行時將字節碼動態編譯成本地平臺相關的機器碼。對象 |
信號分發線程 |
這個線程接收發送到 JVM 的信號並調用適當的 JVM 方法處理。 |
程序計數器(線程私有)
一塊較小的內存空間, 是當前線程所執行的字節碼的行號指示器,每條線程都要有一個獨立的程序計數器,這類內存也稱爲「線程私有」的內存。
正在執行 java 方法的話,計數器記錄的是虛擬機字節碼指令的地址(當前指令的地址)。如 果仍是 Native 方法,則爲空。
這個內存區域是惟一一個在虛擬機中沒有規定任何 OutOfMemoryError 狀況的區域。
虛擬機棧(線程私有)
是描述 java 方法執行的內存模型,每一個方法在執行的同時都會建立一個棧幀(Stack Frame) 用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每個方法從調用直至執行完成 的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
棧幀( Frame)是用來存儲數據和部分過程結果的數據結構,同時也被用來處理動態連接 (Dynamic Linking)、 方法返回值和異常分派( Dispatch Exception)。棧幀隨着方法調用而建立,隨着方法結束而銷燬——不管方法是正常完成仍是異常完成(拋出了在方法內未被捕獲的異 常)都算做方法結束。
本地方法區(線程私有)
本地方法區和 Java Stack 做用相似, 區別是虛擬機棧爲執行 Java 方法服務, 而本地方法棧則爲 Native 方法服務, 若是一個 VM 實現使用 C-linkage 模型來支持 Native 調用, 那麼該棧將會是一個 C 棧,但 HotSpot VM 直接就把本地方法棧和虛擬機棧合二爲一。
堆(Heap-線程共享)-運行時數據區
線程共享的一塊內存區域,建立的對象和數組都保存在 Java 堆內存中,也是垃圾收集器進行 垃圾收集的最重要的內存區域。因爲現代 VM 採用分代收集算法, 所以 Java 堆從 GC 的角度還能夠 細分爲: 新生代(Eden 區、From Survivor 區和 To Survivor 區)和老年代。
方法區/永久代(線程共享)
即咱們常說的永久代(Permanent Generation), 用於存儲被 JVM 加載的類信息、常量、靜 態變量、即時編譯器編譯後的代碼等數據. HotSpot VM 把 GC 分代收集擴展至方法區, 即便用 Java 堆的永久代來實現方法區, 這樣 HotSpot 的垃圾收集器就能夠像管理 Java 堆同樣管理這部份內存, 而沒必要爲方法區開發專門的內存管理器(永久帶的內存回收的主要目標是針對常量池的回收和類型 的卸載, 所以收益通常很小)。
運行時常量池(Runtime Constant Pool)是方法區的一部分。Class 文件中除了有類的版本、字段、方法、接口等描述等信息外,還有一項信息是常量池(Constant Pool Table),用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加 載後存放到方法區的運行時常量池中。 Java 虛擬機對 Class 文件的每一部分(天然也包括常量 池)的格式都有嚴格的規定,每個字節用於存儲哪一種數據都必須符合規範上的要求,這樣纔會被虛擬機承認、裝載和執行。
Java 堆從 GC 的角度還能夠細分爲: 新生代(Eden 區、From Survivor 區和 To Survivor 區)和老年代。
新生代:是用來存放新生的對象。通常佔據堆的1/3空間。因爲頻繁建立對象,因此新生代會頻繁觸發MinorGC 進行垃圾回收。新生代又分爲 Eden區、ServivorFrom、ServivorTo 三個區。
Eden 區 :Java新對象的出生地(若是新建立的對象佔用內存很大,則直接分配到老 年代)。當Eden區內存不夠的時候就會觸發MinorGC,對新生代區進行一次垃圾回收。
ServivorFrom :上一次 GC 的倖存者,做爲這一次 GC 的被掃描者。
ServivorTo:保留了一次 MinorGC 過程當中的倖存者。
MinorGC 的過程(複製->清空->互換)
老年代:主要存放應用程序中生命週期長的內存對象。
老年代的對象比較穩定,因此 MajorGC 不會頻繁執行。在進行 MajorGC 前通常都先進行 了一次 MinorGC,使得有新生代的對象晉身入老年代,致使空間不夠用時才觸發。當沒法找到足 夠大的連續空間分配給新建立的較大對象時也會提早觸發一次 MajorGC 進行垃圾回收騰出空間。
MajorGC 採用標記清除算法:首先掃描一次全部老年代,標記出存活的對象,而後回收沒 有標記的對象。MajorGC 的耗時比較長,由於要掃描再回收。MajorGC 會產生內存碎片,爲了減 少內存損耗,咱們通常須要進行合併或者標記出來方便下次直接分配。當老年代也滿了裝不下的 時候,就會拋出 OOM(Out of Memory)異常。
永久代:指內存的永久保存區域,主要存放 Class 和 Meta(元數據)的信息,Class 在被加載的時候被 放入永久區域,它和和存放實例的區域不一樣,GC 不會在主程序運行期對永久區域進行清理。因此這也致使了永久代的區域會隨着加載的 Class 的增多而脹滿,最終拋出OOM異常。
在Java8中,永久代已經被移除,被一個稱爲「元數據區」(元空間)的區域所取代。元空間的本質和永久代相似,元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。所以,默認狀況下,元空間的大小僅受本地內存限制。類的元數據放入native memory, 字符串池和類的靜態變量放入java堆中,這樣能夠加載多少類的元數據就再也不由MaxPermSize控制, 而由系統的實際可用空間來控制。
堆內內存由JVM管理,屬於「用戶態」;而堆外內存由OS管理,屬於「內核態」。若是從堆內向磁盤寫數據時,數據會被先複製到堆外內存,即內核緩衝區,而後再由OS寫入磁盤,使用堆外內存避免了數據從用戶內向內核態的拷貝。
主動回收(推薦): 對於Sun的JDK,只要從DirectByteBuffer裏取出那個sun.misc.Cleaner,而後調用它的clean()就行;
基於 GC 回收:堆內的DirectByteBuffer對象被GC時,會調用cleaner回收其引用的堆外內存。問題是YGC只會將將新生代裏的不可達的DirectByteBuffer對象及其堆外內存回收,若是有大量的DirectByteBuffer對象移到了old區,可是又一直沒有作CMS GC或者FGC,而只進行YGC,物理內存會被慢慢耗光,觸發OOM;