元空間和直接內存


參考https://blog.csdn.net/Ethan_199402/article/details/110431404
https://www.cnblogs.com/zhai1997/p/12912915.html
https://www.zhihu.com/question/456026457/answer/1850516358
https://www.jianshu.com/p/474d98fc4776

元空間和直接內存


隨着JDK8的到來,JVM不再有PermGen。但類的元數據信息(metadata)還在,只不過不再是存儲在連續的堆空間上,而是移動到叫做“Metaspace”的本地內存(Native memory)中。從上圖可以看到,元空間和直接內存都是堆外內存。

Metaspace

  • 永久代
    JDK1.8以前的HotSpot JVM的方法區用永久代(permanent generation)實現。方法區用於存放已被虛擬機加載的類信息、常量、靜態變量,即編譯器編譯后的代碼。永久代是一片連續的堆空間,通過-XX:MaxPermSize來設定永久代最大可分配空間,當JVM加載的類信息容量超過了這個值,會報OOM:PermGen錯誤。
    JDK1.7開始了永久代的部分移除:符號引用(Symbols)移至native heap,字面量(interned strings)和靜態變量(class statics)移至java heap。
  • 為什么要用Metaspace替代永久代實現方法區
    隨着動態類加載的情況越來越多,這塊內存變得不太可控,如果設置小了,系統運行過程中就容易出現內存溢出,設置大了又浪費內存。
    默認情況下,元空間的大小僅受本地內存限制,但可以通過以下參數來指定元空間的大小:
    XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那么在不超過MaxMetaspaceSize時,適當提高該值。
    -XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。
  • 元空間里到底存了什么
    java8中移除了永久代,新增了元空間的概念。原來的方法區是邏輯划分中的一個區域,對應hotspot jdk6中的永久代,可以說永久代是方法區在hotspot的一個具體實現,但是從jdk7以后方法區就“四分五裂了”,不再是在單一的一個去區域內進行存儲。
    java8中繼承了一些jdk7中的改變:符號引用存儲在native heap中,字符串常量和靜態類型變量存儲在普通的堆區中,這個影響了String的intern()方法的行為,這里不做intern的詳述。
    而在java8中移除了永久代,新增了元空間,其實在這兩者之間存儲的內容幾乎沒怎么變化,而是在內存限制、垃圾回收等機制上改變較大。元空間的出現就是為了解決突出的類和類加載器元數據過多導致的OOM問題,而從jdk7中開始永久代經過對方法區的分裂后已經幾乎只存儲類和類加載器的元數據信息了,到了jdk8,元空間中也是存儲這些信息,而符號引用、字符串常量等存儲位置與jdk7一致,還是“分裂”的方法區。
    符號引用沒有存在元空間中,而是存在native heap中,這是兩個方式和位置,不過都可以算作是本地內存,在虛擬機之外進行划分,沒有設置限制參數時只受物理內存大小限制,即只有占滿了操作系統可用內存后才OOM。
  • Metaspace內存管理:
    在metaspace中,類和其元數據的生命周期與其對應的類加載器相同,只要類的類加載器是存活的,在Metaspace中的類元數據也是存活的,不能被回收。
    每個加載器有單獨的存儲空間,分配給一個類的空間,是歸屬於這個類的類加載器的,只有當這個類加載器卸載的時候,這個空間才會被釋放。所以,只有當這個類加載器加載的所有類都沒有存活的對象,並且沒有到達這些類和類加載器的引用時,相應的 Metaspace 空間才會被 GC(full gc) 釋放。

直接內存

直接內存主要被 Java NIO 使用,某種程度上也就是指DirectByteBuffer對象占用的堆外內存。DirectByteBuffer對象創建時會通過Unsafe類接口直接調用操作系統的malloc分配內存,然后將內存的起始地址和大小保存下來,據此就可以直接操作內存空間。

可以看出,直接內存的大小並不受到java堆大小的限制,甚至不受到JVM進程內存大小的限制。它只受限於本機總內存(RAM及SWAP區或者分頁文件)大小以及處理器尋址空間的限制(最常見的就是32位/64位CPU的最大尋址空間限制不同)。

  • DirectByteBuffer使用直接內存的原因有兩點:
    (1)減少了垃圾回收
    堆外內存是直接受操作系統管理(不是JVM)。這樣做能保持一個較小的堆內內存,以減少垃圾收集對應用的影響。
    (2)提升IO速度
    堆內內存由JVM管理,屬於“用戶態”;而堆外內存由OS管理,屬於“內核態”。如果從堆內向磁盤寫數據時,數據會被先復制到堆外內存,即內核緩沖區,然后再由OS寫入磁盤,使用堆外內存避免了這個操作。
  • 直接內存的回收
    缺點就是沒有JVM協助管理內存,需要自行管理直接內存,防止內存溢出,一般會設置直接內存最大值-XX:MaxDirectMemerySize,當達到閾值的時候,調用system.gc來進行一次full gc,把那些沒有被使用的直接內存回收掉。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM