Java 虛擬機在執行 Java 程序的過程當中會把它管理的內存劃分爲若干個不一樣的數據區域。它們各有用途,有些隨着虛擬機進程的啓動一直存在(堆、方法區),有些則隨着用戶線程的啓動和結束而創建和銷燬(程序計數器、虛擬機棧、本地方法棧)。html
《Java 虛擬機規範》中規定 Java 虛擬機管理的內存包括如下幾個區域:java
下面簡要分析各個區域的特色。數組
程序計數器(Program Counter Register),能夠看作當前線程所執行的字節碼的行號指示器(其實就是記錄代碼執行到了哪裏)。特色以下:緩存
主要做用:記錄線程執行到了哪裏。bash
Java 虛擬機棧(Java Virtual Machine Stacks):Java 方法執行的線程內存模型。jvm
每一個方法被執行時,虛擬機棧都會建立一個棧幀(Stack Frame)用於存儲局部變量表、操做數棧、動態鏈接、方法出口等信息。每一個方法從被調用直至執行完畢的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。其中局部變量表包括:佈局
這些數據類型在局部變量表中的存儲空間以局部變量槽(Slot)表示,其中 long 和 double 佔用兩個槽,其餘類型佔用一個槽。局部變量表所需內存空間在編譯期完成分配,當進入一個方法時,該方法須要在棧幀中分配多大的局部變量空間是徹底肯定的,運行期間不會改變其大小。性能
虛擬機棧的特色:ui
線程私有;spa
生命週期與線程相同;
兩類異常
主要目的:Java 方法執行的線程內存模型。
本地方法棧(Native Method Stacks)與 Java 虛擬機棧做用相似。兩者區別:
異常與 Java 虛擬機棧相同。
主要目的:Native 方法執行的線程內存模型。
對多數應用來講,Java 堆(Java Heap)是 JVM 管理的內存中最大的一塊。
惟一目的:存放對象實例(【幾乎全部】的對象實例都在這裏分配內存)。
《Java 虛擬機規範》描述:全部對象實例及數組都應在堆上分配。
而從實現角度看,因爲即便編譯技術(尤爲是逃逸分析技術的日漸強大),"棧上分配"等手段使得對象並不是徹底在堆上分配。
特色:
PS: "新生代"、"老年代"、"Eden 區"等一系列對堆的區域劃分,只是部分垃圾收集器的一些共性或設計風格,而非虛擬機的固有內存佈局,更非《Java 虛擬機規範》的劃分。
將 Java 堆細分的目的只是爲了更好地回收內存,或者更快地分配內存。
方法區(Method Area):用於存儲已被虛擬機加載的類型信息、常量、靜態變量、即時編譯器編譯後的代碼緩存等數據,該區域也是線程共享的。又稱"非堆"。
與方法區聯繫密切的一個概念是"永久代",下面簡要介紹。
"永久代(Permanent Generation)",能夠理解爲 JDK 1.8 以前 HotSpot 虛擬機對《Java 虛擬機規範》中"方法區"的實現。從 JDK 1.六、1.7 到 1.8+,HotSpot 虛擬機的運行時數據區變遷示意圖以下:
HotSpot VM JDK 1.6 的運行時數據區示意圖以下:
JDK 1.7 中,將 1.6 中永久代的字符串常量池和靜態變量等移到了堆中,以下(虛線框表示已移除):
而到了 JDK 1.8,則徹底廢棄了"永久代",改用了在本地內存中實現的"元空間(Metaspace)",將 JDK 1.7 中永久代剩餘的部分(主要是類型信息)移到了元空間,以下(虛線框表示已移除):
從上面幾張圖能夠看出永久代和元空間的主要區別有如下兩點:
存儲位置不一樣
存儲內容不一樣:元空間存儲的是「類型信息」(即類的元信息),而永久代除了類型信息,還包括「字符串常量池」和「靜態變量」等(能夠理解爲元空間是永久代拆分出來的一部分)。
那麼問題來了:爲何要把永久代替換爲元空間呢?
緣由大概有如下幾點:
運行時常量池(Runtime Constant Pool)是方法區的一部分。
Class 文件中除了有類的版本、字段、方法、接口等描述外信息,還有一項信息是常量池表(Constant Pool Table),用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加載後進入方法區的運行時常量池中存放。
相比於 Class 文件常量池的一個重要特性是「動態性」,運行期間也能夠將新的常量放入池中(例如 String 類的 intern() 方法)。
直接內存(Direct Memory)並不是虛擬機運行時數據區的一部分,也非《Java 虛擬機規範》定義的內存區域。但該部份內存被頻繁使用(例如 NIO),並且可能致使 OutOfMemoryError。
$ java -version java version "1.8.0_191" Java(TM) SE Runtime Environment (build 1.8.0_191-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
$ java -version java version "1.7.0_80" Java(TM) SE Runtime Environment (build 1.7.0_80-b15) Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)
public class HeapOOM { public static void main(String[] args) { List<Object> list = new ArrayList<>(); while (true) { list.add(new OOMObject()); } } static class OOMObject { } }
# 設置堆空間大小爲 20M -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid39807.hprof ... Heap dump file created [27773554 bytes in 0.342 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) ...
public class StackOverflowError { private int stackLength = 1; private void stackLeak() { stackLength++; stackLeak(); } public static void main(String[] args) { JvmStackOverflow sof = new JvmStackOverflow(); try { sof.stackLeak(); } catch (Throwable ex) { // 注意這裏是 Throwable,而非 Exception (Error 不是 Exception) System.out.println("stack length: " + sof.stackLength); throw ex; } } }
因爲 HotSpot 虛擬機不區分 Java 虛擬機棧和本地方法棧。所以 -Xoss
參數(設置本地方法棧大小)並無做用,棧空間只能由 -Xss
參數。
# Java 虛擬機棧大小 -Xss160K
stack length: 772 Exception in thread "main" java.lang.StackOverflowError at com.jaxer.example.JvmStackOverflow.stackLeak(JvmStackOverflow.java:11) at com.jaxer.example.JvmStackOverflow.stackLeak(JvmStackOverflow.java:12) ...
public class RuntimeConstantPoolOOM { static String baseStr = "string"; public static void main(String[] args) { List<String> list = new ArrayList<>(); while (true) { String s = baseStr + baseStr; baseStr = s; list.add(s.intern()); } } }
JDK 1.8 參數及異常:
# 最大堆空間爲 10M,永久代爲 10M (爲便於觀察,打印了啓動命令和 GC 信息) -Xmx10m -XX:PermSize=10m -XX:MaxPermSize=10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=10m; support was removed in 8.0 Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=10m; support was removed in 8.0 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3332) ...
JDK 1.7 參數及異常信息:
# 設置永久代大小爲 10M -XX:PermSize=10m -XX:MaxPermSize=10m
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:2367) ...
package com.jaxer.example.cglib; public class OOMObject { }
使用 CGLib 生成代碼:
public class PermGenOOM { public static void main(String[] args) { try { while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invoke(o, objects); } }); enhancer.create(); } } catch (Throwable t) { t.printStackTrace(); } } }
JDK 1.8 參數及異常:
# 設置元空間大小爲 10M -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
java.lang.OutOfMemoryError: Metaspace at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:348) ...
JDK 1.7 參數及異常信息:
# 設置永久代大小爲 10M -XX:PermSize=10m -XX:MaxPermSize=10m -XX:+PrintGCDetails
Exception in thread "main" Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
此處的異常沒法被捕獲,Debug 模式斷點以下:
能夠看到,這裏實際仍是永久代(PermGen space)OOM 異常。
public class DirectMemoryOOM { private static final int _1M = 2014 * 1024; public static void main(String[] args) { List<ByteBuffer> list = new ArrayList<>(); while (true) { ByteBuffer buffer = ByteBuffer.allocateDirect(_1M); // java.lang.OutOfMemoryError: Direct buffer memory // ByteBuffer buffer = ByteBuffer.allocate(_1M); // java.lang.OutOfMemoryError: Java heap space list.add(buffer); } } }
# 設置堆內存最大爲 20M,直接內存最大爲 10M -Xmx20m -XX:MaxDirectMemorySize=10m
java.lang.OutOfMemoryError: Direct buffer memory
本文主要分析了《Java 虛擬機規範》中規定的 Java 虛擬機管理的運行時內存區域,並以 HotSpot 虛擬機爲例,分析了 JDK 1.7 和 1.8 內存溢出的狀況。主要內容總結以下圖:
PS: 一些虛擬機參數以下
# 設置堆空間大小 -Xms20m -Xmx20m # 設置虛擬機棧空間大小 -Xss160K # 設置永久代大小 -XX:PermSize=10m -XX:MaxPermSize=10m # 設置元空間大小 -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m # 打印 GC 日誌 -XX:+PrintGCDetails # 打印命令行參數 -XX:+PrintCommandLineFlags # 堆棧信息 -XX:+HeapDumpOnOutOfMemoryError