最近一直在看周志明老師的《深刻理解虛擬機》,老是看了忘,忘了又看,陷入這樣無休止的循環當中。抱着紙上得來終覺淺的想法,準備陸續的寫幾篇學習筆記,梳理知識的脈絡並強化一下對知識的掌握。(本文遠遠談不上深刻,但爲了博瀏覽量,請原諒我這個標題黨)。java
"Write Once,Run Anywhere"是sun公司用來展現java語言跨平臺特性的口號。這標示着java語言能夠在任何機器上開發,並編譯成標準的字節碼,在任何具備jvm虛擬機上的設備運行,這也是java語言早期興起的關鍵。java另外一大特性是其虛擬機的內存自動管理機制,這使得java程序員在建立任何一個對象時都不須要去寫與之配對的delete/free代碼(釋放內存),不容易出現由於粗枝大葉而致使的內存泄漏和內存溢出的問題。但是由於將內存管理的權利交給虛擬機,一旦出現內存泄漏和內存溢出的問題,若是咱們不瞭解虛擬機相關的知識,排查問題將是一件極爲艱難的事情。程序員
java虛擬機在運行java程序時,會將其管理的內存區域劃分紅若干個不一樣的數據區域。接下來的知識若是沒有指明jdk版本號,統一以jdk1.6爲標準,內存區域以下圖所示:算法
通過一長串的的理論分析,咱們已經大體清楚java的內存區域,如今咱們使用具體的例子來驗證。會將jvm的參數放在代碼註釋中。數組
/** * -XX:+PrintGCDetails -Xmx20m -Xms20m */ public class HeapOOM { static class OOMObjectt { } public static void main(String[] args) { List<OOMObjectt> list = new ArrayList<OOMObjectt>(); try { while (true){ list.add(new OOMObjectt()); } } catch (Exception e) { } } }
其運行結果以下:安全
[GC [PSYoungGen: 5898K->480K(6656K)] 5898K->3769K(20480K), 0.0043241 secs] [Times: user=0.09 sys=0.00, real=0.00 secs] [GC [PSYoungGen: 6315K->488K(6656K)] 9604K->8320K(20480K), 0.0064706 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [Full GC [PSYoungGen: 6632K->0K(6656K)] [ParOldGen: 10997K->13393K(13824K)] 17629K->13393K(20480K) [PSPermGen: 3164K->3163K(21504K)], 0.1786099 secs] [Times: user=0.58 sys=0.00, real=0.18 secs] [Full GC [PSYoungGen: 3031K->3001K(6656K)] [ParOldGen: 13393K->13393K(13824K)] 16425K->16394K(20480K) [PSPermGen: 3163K->3163K(21504K)], 0.1063835 secs] [Times: user=0.64 sys=0.02, real=0.11 secs] [Full GC [PSYoungGen: 3001K->3001K(6656K)] [ParOldGen: 13393K->13377K(13824K)] 16394K->16378K(20480K) [PSPermGen: 3163K->3163K(21504K)], 0.0873232 secs] [Times: user=0.28 sys=0.02, real=0.09 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:2245) at java.util.Arrays.copyOf(Arrays.java:2219) at java.util.ArrayList.grow(ArrayList.java:242) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208) at java.util.ArrayList.add(ArrayList.java:440) at HeapOOM.main(HeapOOM.java:17) Heap PSYoungGen total 6656K, used 3144K [0x00000000ff900000, 0x0000000100000000, 0x0000000100000000) eden space 6144K, 51% used [0x00000000ff900000,0x00000000ffc12240,0x00000000fff00000) from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) ParOldGen total 13824K, used 13377K [0x00000000feb80000, 0x00000000ff900000, 0x00000000ff900000) object space 13824K, 96% used [0x00000000feb80000,0x00000000ff890578,0x00000000ff900000) PSPermGen total 21504K, used 3194K [0x00000000f9980000, 0x00000000fae80000, 0x00000000feb80000) object space 21504K, 14% used [0x00000000f9980000,0x00000000f9c9ebc8,0x00000000fae80000)
public class JavaVMStackSOF { private int stackLength=1; public void stackLeak(){ stackLength++; stackLeak(); } public static void main(String[] args) { JavaVMStackSOF oom = new JavaVMStackSOF(); try { oom.stackLeak(); } catch (Exception e) { System.out.println("e.length:"+oom.stackLength); e.printStackTrace(); } } }
Exception in thread "main" java.lang.StackOverflowError at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:6) at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:7) at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:7) at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:7)
前文提到hotspot虛擬機棧中方法區是由永久代來實現的,能夠用參數-XX:PermSize -XX:MaxPermSize來限制其空間,當沒法申請到足夠的內存時,會出現「permgen space」異常。但在jdk1.7中已經將永久代的字符串常量池移除,將其移入到Class對象末尾(也就是gc heap)。在jdk1.8將廢除永久代,引用元空間概念,使用native memory來實現,能夠經過參數:-XX:MetaspaceSize -XX:MaxMetaspaceSize來指定元空間大小。多線程
/** * vm args:-XX:PermSize=4m -XX:MaxPermSize=4m -Xmx6m * Created by zhizhanxue on 18-3-26. */ public class MethodAreaOOM { public static void main(String[] args) { long i=0; List<String> list = new ArrayList<>(); while (true){ list.add(String.valueOf(i++).intern()); } } }
jdk1.6的運行結果:併發
jdk1.7的運行結果:jvm
jdk1.8的運行結果:ide
下面咱們來驗證下元空間的例子:函數
/** *-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m */ public class SpringTest { static class OOM implements MethodInterceptor{ public Object getInstance(){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOM.class); enhancer.setCallback(this); enhancer.setUseCache(false); return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invoke(o,objects); } } public static void main(String[] args) throws ExecutionException, InterruptedException { List<Object> list = new ArrayList<>(); OOM oom = new OOM(); while (true){ list.add(oom.getInstance()); } } }
運行結果:
DirectMemory容量能夠經過參數-XX:MaxDirectMemorySize來指定,若是不指定則默認與java堆最大值(-Xmx指定同樣),代碼經過unsafe.allocateMemory()去申請堆外內存模擬本地內存溢出異常。
/** * -Xmx220m -XX:MaxDirectMemorySize=10m */ public class LocalOOM { public static void main(String[] args) throws IllegalAccessException { Field field = Unsafe.class.getDeclaredFields()[0]; field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null); while (true){ unsafe.allocateMemory(1024*1024); } } }
運行結果:
Exception in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at LocalOOM.main(LocalOOM.java:12)
由堆外內存致使的內存溢出,通常都是gc日誌不多,且堆dump文件不會看到明顯的異常,若是狀況和上述相似,你的項目中又使用了NIO,能夠着重檢查下是否是這方面的緣由。
1.對象已死?(如何判斷對象是否存活)2.垃圾收集的四種基礎算法3.垃圾收集器的介紹