快捷搜索:  汽车  科技

深入理解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 { } }

深入理解java内存:深入理解Java虚拟机之图解Java内存区域与内存溢出异常(1)

本机直接内存溢出

直接内存(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); } } }

深入理解java内存:深入理解Java虚拟机之图解Java内存区域与内存溢出异常(2)

猜您喜欢: