得益于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);
}
}
}