您好,登錄后才能下訂單哦!
本篇內容介紹了“什么是JVM直接內存”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
從上面的圖中可以看到Java8相比Java7來講將方法區的實現,從非堆空間(其實邏輯與堆相連,所屬于運行時數據區內部)遷移到了本地內存中,不會造成FullGC過多的壓力以及與老年代的耦合度過高的問題,減少FullGC的掃描范圍,從而改為手動去回收機制(也可以自動回收需要配置調整)。 之前的文章里面介紹了JVM的運行時數據區的相關介紹,一直對直接內存的研究和學習較少,現在我們就開始介紹一下直接內存的分配方式以及回收方式。
1. 常見于NIO操作時,用于數據緩沖區(ByteBuffer.allocate),當然也可以采用Unsafe類進行native方法運作進行申請直接內存。
2. 分配回收成本較高(屬于操作系統內存),但讀寫性能高。(因為直接內存不需要經過JVM解釋器進行地址映射轉換到系統真正內存,故此讀寫速度會比堆內存在快很多,但是申請和回收機制角度而言復雜,因為屬于直接由操作系統進行管理,而非JVM直接進行管理。
3. 不受JVM內存回收管理(依舊存在內存溢出的問題),此外受限制與操作系統物理內存,以及我們可以手動設置它的閾值(MaxDirectMemory),默認情況下來講直接內存幾乎沒有限制(當沒有超出物理內存的控制而言)
4. jvm的內存分配與回收是自動的,不需要手動調用任何的方法,但是直接內存需要我們手動調用方法。
Java堆內存:
使用直接內存后,可以減少步驟:
可以看出來,直接內存無需進行與java堆內存進行映射和轉換,可以直接去操作系統內存。
書寫程序:每次都分配直接內存,直到內存溢出
public class Test1 { static int _100Mb=1024*1024*100; public static void main(String[] args) { List<ByteBuffer> list=new ArrayList<>(); int i=0; try { while (true){ ByteBuffer byteBuffer=ByteBuffer.allocateDirect(_100Mb); list.add(byteBuffer); i++; } }finally { System.out.println(i); } } }
測試結果:
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory at java.nio.Bits.reserveMemory(Bits.java:694) at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123) at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311) at pers.zhb.test.Test1.main(Test1.java:15)
運行程序前:
直接內存的分配與釋放程序:
public class Test1 { static int _1Gb=1024*1024*1024; public static void main(String[] args) throws IOException { ByteBuffer byteBuffer=ByteBuffer.allocateDirect(_1Gb); System.out.println("分配完畢"); System.in.read(); System.out.println("開始釋放"); byteBuffer=null; System.gc(); } }
分配直接內存后:
可以看出來相關的多出一個java的進程,并且內存占用1037M,所以也證實了我們的猜測和預想。
在IDEA的控制臺點擊回車對內存進行釋放:
可以看到恢復到了原始的狀態。
控制臺打印出分配與回收的提示:
分配完畢 開始釋放 Process finished with exit code 0
其中System.gc() ,強制執行FullGC,回收掉byteBuffer對象
至此,我們的推測全部驗證通過,直接內存會采用另外一個進程去存儲相關的系統內存區域,并且不屬于jvm內存之內的管理。
public class Test1 { static int _1Gb=1024*1024*1024; public static void main(String[] args) throws IOException { Unsafe unsafe=getUnsafe(); //分配內存 long base=unsafe.allocateMemory(_1Gb); unsafe.setMemory(base,_1Gb,(byte)0); System.in.read(); //釋放內存 unsafe.freeMemory(base); System.in.read(); } public static Unsafe getUnsafe(){ Field field= null; try { field = Unsafe.class.getDeclaredField("theUnsafe"); } catch (NoSuchFieldException e) { e.printStackTrace(); } field.setAccessible(true); Unsafe unsafe= null; try { unsafe = (Unsafe)field.get(null); } catch (IllegalAccessException e) { e.printStackTrace(); } return unsafe; } }
jvm的內存分配與回收是自動的,不需要手動調用任何的方法,但是直接內存需要我們手動調用方法。
ByteBuffer byteBuffer= ByteBuffer.allocateDirect(_1Gb);
public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); }
DirectByteBuffer(int cap) { super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); Bits.reserveMemory(size, cap); long base = 0; try { base = unsafe.allocateMemory(size); } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } unsafe.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; }
底層用到的依舊是unsafe對象
減少了垃圾回收
堆外內存是直接受操作系統管理(不是JVM)。這樣做能保持一個較小的堆內內存,以減少垃圾收集對應用的影響。
提升IO速度
堆內內存由JVM管理,屬于“用戶態”;而堆外內存由OS管理,屬于“內核態”。如果從堆內向磁盤寫數據時,數據會被先復制到堆外內存,即內核緩沖區,然后再由OS寫入磁盤,使用堆外內存避免了這個操作。
沒有JVM協助管理內存,需要我們自己手動來管理堆外內存,防止內存溢出同時也為了避免一直有FULL GC,最終導致物理內存被耗完。
我們會指定直接內存的最大值,通過-XX:MaxDirectMemorySize來指定,當達到閾值的時候,調用system.gc來進行一次full gc,把那些沒有被使用的直接內存回收掉。
此外,分配直接內存和回收內存的性能和速度也比較復雜,所以成本也會很高,特別是回收需要FullGC、分配內存的時候,需要切換一次用戶態到系統態的操作,分配直接內存,從而用用戶態的句柄來引用系統態的內存空間。
“什么是JVM直接內存”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。