首頁 > 軟體

教你JVM怎麼使用native memory

2023-09-08 22:00:17

JRE如何使用native儲存

今天看到一篇特別好的文章,翻譯其中一小段Understanding how the JVM uses native memory

Runtime環境提供了被某些未知的使用者程式碼驅動的能力,這使runtime在任何情況下都能使用合適的資源。每一個JVM管理的java應用的行為都會潛在的影響JVM所能提供的執行時環境。這一節我們討論為什麼Java應用會消耗native儲存

Java堆和GC

Java的堆是用來儲存分配物件的一塊記憶體,大多數的JVM有一塊邏輯堆記憶體,也有少數的JVM實現了多塊堆儲存。一個實體記憶體可以基於GC被分配成多塊邏輯上的記憶體。

The Just-in-time (JIT) compiler

JIT編譯器會把java位元組碼編譯成執行時可以直接執行的機器碼,這極大的提升了JRE執行速度,使Java程式碼執行比肩native code。
位元組碼編譯會使用native記憶體(同理,一些像GCC這樣的編譯器也需要記憶體去run),但是JIT的輸入(位元組碼)輸出(機器碼)都必須儲存在native記憶體中。所以包含很多JIT-compiled的方法的應用相對來說更佔用native記憶體。

Classes and classloaders

Java程式由定義了物件和方法邏輯的類組成,可能是Java執行時的庫(比如java.lang.String),也可能是三方庫。這些class在被使用的時候會被載入進來並被儲存在記憶體裡面。

class如何被儲存不同JVM的實現相差極大。Sun JDK儲存在永生帶(PermGen),IBM從Java5開始為每個classloader開闢native記憶體並將它們儲存在那裡。具體的儲存位置需要檢視實現的檔案。

顯而易見的是,用更多的類會消耗更多的記憶體。(這意味著你的native記憶體消耗會持續增加,或者明確的開闢一塊記憶體,像PermGen,去容納所有的class),需要注意的是不止是你的應用的class需要儲存,frameworks,application servers,三方庫,JRE這裡面的class在被用到的時候都會被載入並儲存進來。

JRE允許解除安裝class去回收空間,但是這僅僅是在記憶體嚴重不足的情況下。不可能僅僅解除安裝一個單獨的class檔案,而是解除安裝classloader,和它載入進來的所有class,一個classloader僅僅會在以下情況下被解除安裝:

  • Java堆中不包含任何代表此classloader的java.lang.ClassLoader物件的應參照
  • Java堆中不包含任何代表由此classloader載入進來的類的java.lang.Class物件的參照
  • Java堆中沒有任何被此classloader載入進來的物件存活。

JNI

JNI允許原生程式碼和java程式碼相互呼叫。JRE嚴重依賴JNI程式碼去實現檔案和網路這些類庫的功能,一個JNI應用能以三種方式增加JRE的native記憶體

  • JNI應用的native程式碼會被編譯進一個so動態連結庫,執行時會被載入到可執行的地址空間呢,大型native應用程式只需載入就可佔據程序地址空間的很大一部分。
  • native程式碼必須跟JVM共用記憶體,任何native程式碼分配或者對映所需要的native記憶體都需要佔用JVM的記憶體。
  • 某些JNI方法可以使用native作為他們正常操作的一部分,比如GetTypeArrayElements或者GetTypeArrayRegion方法都可以拷貝Java堆記憶體到到native記憶體供native程式碼使用。以這種方式存取大塊的Java堆記憶體相應的會佔用大量的native記憶體

NIO

NIO是java1.4之後新增的API,基於管道和快取,以一種新的方式實現IO操作。除了基於堆的I/O,NIO還新增了基於native記憶體的direct ByteBuffer(通過java.nio.ByteBuffer.allocateDirect()方法分配)。Direct ByteBuffers可以直接呼叫系統庫的方法去實現I/O操作,這會顯示提升在某些場景下的執行效率,因為能避免在Java堆和native堆之間拷貝資料。

我們可能會疑惑direct ByteBuffer申請的記憶體到底存在哪裡,應用仍然用的是Java堆裡面的物件去完成I/O操作,但是持有資料的快取仍然存在native記憶體中 -Java堆的物件只是持有了一個native堆快取的參照。一個non-direct ByteBuffer則是直接在Java堆中儲存了byte[]陣列。

Memory topology for direct and non-direct java.nio.ByteBuffers

Java堆發生GC的時候同樣會對Direct ByteBuffer資料執行清除native快取操作,GC僅僅會在Java堆中已經滿了,不支援新的堆空間分配或者程式手動呼叫GC(不建議手動呼叫GC)的情況下發生。

還有一種情況,native記憶體已經滿了,又有程式碼來請求native記憶體,但是這個時候Java堆還沒有達到GC的條件,所以並不會發生GC。(也就是說native記憶體的GC完全依賴Java堆的GC,反之如果native需要GC了但是堆沒有GC的需求的則不會引發GC)

Threads

應用的每一個執行緒都需要記憶體去儲存它的棧(這塊記憶體用來儲存本地變數表和儲存狀態),每一個Java執行緒都需要棧去執行,根據實現,Java執行緒可以具有單獨的native和Java棧。除了堆疊空間之外,每個執行緒還需要一些native記憶體用於thread-local儲存和內部資料結構。
堆疊大小因Java實現和架構而異。某些實現允許您指定Java執行緒的堆疊大小。通常在256KB和756KB之間的值。

儘管每個執行緒使用的記憶體量非常小,但對於具有數百個執行緒的應用程式,執行緒堆疊的總記憶體使用量可能很大。執行具有比可用處理器多的執行緒來執行它們的應用程式通常是低效的,並且可能導致效能低下以及增加的記憶體使用。

以上就是教你JVM怎麼使用native memory的詳細內容,更多關於JVM使用native memory的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com