深入理解java内存:深入理解Java虚拟机之图解Java内存区域与内存溢出异常
深入理解java内存:深入理解Java虚拟机之图解Java内存区域与内存溢出异常
方法区和运行时常量池溢出- 方法区容量控制
public class Hello {
/**
* JDK8前VM参数: -XX:PermSize=6M -XX:MaxPermSize=6M
* JDK8VM参数:-XX:MetaspaceSize=6M -XX:MaxMetaspaceSize=6M
*/
public static void main(String[] args) {
// 使用Set保持常量池引用,避免Full GC回收常量池行为
Set<String> set = new HashSet<>();
// 在short范围内足以让6M大小的PermSize(永久代,JDK8前有,JDK8及之后版本都已采用元空间替代)产生OOM了
short i = 0;
// JDK8前,抛出OOM异常
// JDK8下,正常情况会进入死循环,并不会抛出任何异常
while (true) {
// String.intern()进入字符串常量池
set.add(String.valueOf(i ).intern());
}
}
}
上述代码在JDK8环境下并不会抛出任何异常,这是因为字符串常量池已经被移至Java堆之中,控制方法区容量的大小对Java堆并没有什么影响
- String.intern() 方法介绍:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回常量池中这个字符串的String对象;否则,将此String对象包含的字符复制添加到常量池中,并返回此String对象的引用
/**
* JDK6:false false
* JDK8:true false
*/
public static void main(String[] args) {
String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
}
- JDK6因为 new StringBuilder() 分配到的是Java堆内存,而 String.intern() 会把首次遇到的字符串复制到的是字符串常量池(方法区),所以都是false
- JDK8因为字符串常量池都移动到了Java堆中, new StringBuilder() 分配到Java堆内存后,字符串常量池也记录到了首次遇到的实例引用,那么 String.intern() 和 new StringBuilder() 都是同一个了( true );而因为 java 字符串在 sun.misc.Version 类加载时已进入常量池,那么 intern() 方法就返回当前常量池的String对象, new StringBuilder() 在堆中重新创建了一个,自然也就不一样了( false )
- 方法区的主要职责是用于存放类型的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等,因此运行时产生大量的类填满方法区也可以造成方法区溢出
/*
* 借助CGLib造成方法区溢出
* VM参数:-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
*/
public class Hello {
public static void main(String[] args) {
while (true) {
// 创建CgLib增强对象
Enhancer enhancer = new Enhancer();
// 设置被代理的类
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
// 指定拦截器
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj Method method Object[] args MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj args);
}
});
// 创建代理对象
enhancer.create();
}
}
static class OOMObject {
}
}
直接内存(Direct Memory)的容量大小可通过-XX:MaxDirectMemorySize参数来指定,如果不去指定,则默认与Java堆最大值(由-Xmx指定)一致
// 使用unsafe分配本机内存
public class Hello {
// VM参数:-Xmx20M -XX:MaxDirectMemorySize=10M
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
// 真正申请分配内存
unsafe.allocateMemory(_1MB);
}
}
}