最近一直在看周志明老师的《深刻理解虚拟机》,老是看了忘,忘了又看,陷入这样无休止的循环当中。抱着纸上得来终觉浅的想法,准备陆续的写几篇学习笔记,梳理知识的脉络并强化一下对知识的掌握。(本文远远谈不上深刻,但为了博浏览量,请原谅我这个标题党)。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.垃圾收集器的介绍