得益于Java虚拟机的内存管理机制,Java程序员无需手动分配、释放内存,可以专注在自身功能模块的开发。但是懂得JVM的内存管理机制可以在实际开发中,避免一些问题。在排查故障时提供思路。
Java不同于C、C++,Java程序员不需要自己手动管理内存。而是交给JVM(Java Virtual Machine)的自动内存管理机制。
JVM在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域。每个区域有各自的用途,以及创建和销毁的时机。有的区域随着JVM进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁
对象如何创建?如何布局?如何访问?以下基于HotSpot虚拟机的常用内存区域Java堆内存区域,探索Java堆中对象分配、布局和访问的全过程
HotSpot JVM采用直接指针法来访问对象
-XX:SurvivorRatio=8设置两个Survivor区与一个Eden区的比值为2:8
/** * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError * @author luhuancheng * @date 2019/3/20 */ public class HeapOOM { static class OOMObject { } public static void main(String[] args) { List<OOMObject> list = new ArrayList<>(); while (true) { list.add(new OOMObject()); } } }
设置每个线程栈内存大小为128k
/** * -Xss160k 设置线程栈容量大小,随着栈容量大小的值增大,递归的深度就越大 * @author luhuancheng * @date 2019/3/23 */ public class StackOverflowErrorExample { private static int stackDepth = 1; public void stackLeak() { stackDepth++; stackLeak(); } public static void main(String[] args) { try { StackOverflowErrorExample stackOverflowErrorExample = new StackOverflowErrorExample(); stackOverflowErrorExample.stackLeak(); } catch (StackOverflowError e) { System.out.println("stack depth is " + stackDepth); throw e; } } }
JDK8中方法区已经被废弃,取而代之的是MetaSpace,分配在native memory(本机内存)默认不限制大小
/** * -XX:PermSize=10M -XX:MaxPermSize=10M(jdk8时已被废弃) * -XX:MaxMetaspaceSize=10M(jdk8时采用这个配置设置元数据区) * @author luhuancheng * @date 2019/3/23 */ public class RunTimeConstantPoolOOM { public static void main(String[] args) { // JDK8设置MaxMetaspaceSize=10M,达不到溢出效果,为什么? // List<String> list = new ArrayList<>(); // int i = 0; // while (true) { // list.add(String.valueOf(i++).intern()); // } /* StringBuilder.toString()会在堆上构造一个String对象 String.intern()会从字符串常量池中获取与str1相同字面量的实例,存在则返回对象指针,不存在则将str1的引用记录到常量池中 由于此处的"计算机软件"是第一次出现,因此str1.intern()返回的是str1的引用。即str1.intern() == str1 结果为true */ String str1 = new StringBuilder("计算机").append("软件").toString(); System.out.println(str1.intern() == str1); // true /* 同理,但是此处的"java"已经不是第一次出现了,str2.intern()从常量池返回的引用时第一次加入常量池的引用, 与StringBuilder.toString()构造的String对象不是同一个。即str2.intern() == str2 结果为false */ String str2 = new StringBuilder("ja").append("va").toString(); System.out.println(str2.intern() == str2); // false } }
/** * -Xmx20M -XX:MaxDirectMemorySize=10M * @author luhuancheng * @date 2019/3/23 */ public class DirectMemoryOOM { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws IllegalAccessException { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true) { // 在MacOS JDK8环境下,无法引发OOM,为什么? unsafe.allocateMemory(_1MB); } } }