Java 運行時的jvm劃分



程序計數器

記錄當前線程所執行的字節碼行號,用於獲取下一條執行的字節碼。
當多線程運行時,每個線程切換後需要知道上一次所運行的狀態、位置。由此也可以看出程序計數器是每個線程私有的。

虛擬機棧

虛擬機棧由一個一個的棧幀組成,棧幀是在每一個方法調用時產生的。
每一個棧幀由局部变量区操作数栈等組成。每創建一個棧幀壓棧,當一個方法執行完畢之後則出棧。
  • 如果出現方法遞歸調用出現死循環的話就會造成棧幀過多,最終會拋出StackOverflowError
  • 若線程執行過程中棧幀大小超出虛擬機棧限制,則會拋出StackOverflowError
  • 若虛擬機棧允許動態擴展,但在嘗試擴展時內存不足,或者在為一個新線程初始化新的虛擬機棧時申請不到足夠的內存,則會拋出 OutOfMemoryError
這塊內存區域也是線程私有的。

Java 堆

Java 堆是整個虛擬機所管理的最大內存區域,所有的對象創建都是在這個區域進行內存分配。
可利用參數-Xms -Xmx進行堆內存控制。
這塊區域也是垃圾回收器重點管理的區域,由於大多數垃圾回收器都採用分代回收算法,所有堆內存也分為新生代老年代,可以方便垃圾的準確回收。
這塊內存屬於線程共享區域。

方法區(JDK1.7)

方法區主要用於存放已經被虛擬機加載的類信息,如常量,静态变量這塊區域也被稱為永久代
可利用參數-XX:PermSize -XX:MaxPermSize控制初始化方法區和最大方法區大小。

元數據區(JDK1.8)

JDK1.8中已經移除了方法區(永久代),並使用了一個元數據區域進行代替(Metaspace)。
默認情況下元數據區域會根據使用情況動態調整,避免了在1.7中由於加載類過多從而出現java.lang.OutOfMemoryError: PermGen
但也不能無限擴展,因此可以使用-XX:MaxMetaspaceSize來控制最大內存。

運行時常量池

運行時常量池是方法區的一部分,其中存放了一些符號引用。new一個對象時,會檢查這個區域是否有這個符號的引用。

直接內存

直接內存又稱為Direct Memory(堆外内存),它並不是由JVM虛擬機所管理的一塊內存區域。
有使用過Netty的朋友應該對這塊並內存不陌生,在Netty中所有的IO(nio)操作都會通過Native函數直接分配堆外內存。
它是通過在堆內存中的DirectByteBuffer對像操作的堆外內存,避免了堆內存和堆外內存來回複製交換複製,這樣的高效操作也稱為零拷贝
既然是內存,那也得是可以被回收的。但由於堆外內存不直接受JVM管理,所以常規GC操作並不能回收堆外內存。它是藉助於老年代產生的fullGC順便進行回收。同時也可以顯式調用System.gc()方法進行回收(前提是沒有使用-XX:+DisableExplicitGC參數來禁止該方法)。
值得注意的是:由於堆外內存也是內存,是由操作系統管理。如果應用有使用堆外內存則需要平衡虛擬機的堆內存和堆外內存的使用佔比。避免出現堆外內存溢出。

常用參數

通過上圖可以直觀的查看各個區域的參數設置。
常見的如下:
  • -Xms64m最小堆內存64m.
  • -Xmx128m最大堆內存128m.
  • -XX:NewSize=30m新生代初始化大小為30m.
  • -XX:MaxNewSize=40m新生代最大大小為40m.
  • -Xss=256k 線程棧大小。
  • -XX:+PrintHeapAtGC 當發生GC 時打印內存佈局。
  • -XX:+HeapDumpOnOutOfMemoryError 發送內存溢出時dump 內存。
新生代和老年代的默認比例為1:2,也就是說新生代佔用1/3的堆內存,而老年代佔用2/3的堆內存。
可以通過參數-XX:NewRatio=2來設置老年代/新生代的比例。


留言

這個網誌中的熱門文章

Json概述以及python對json的相關操作

Docker容器日誌查看與清理

利用 Keepalived 提供 VIP