Java 解决方法区溢出 | Eddie'Blog
Java 解决方法区溢出

Java 解决方法区溢出

eddie 442 2021-05-07

目录

实战-方法区溢出

  • 线程共享,用来存储被虚拟机加载的类型信息、常量、静态变量等

方法区的组成

  • 大于等于JDK8
      • 静态变量
      • 字符串常量池
    • 元空间 (前身叫:持久代)
      • 类信息
        • 类的版本
        • 字段描述信息
        • 方法描述信息
        • 接口和父类等描述信息
        • class文件常量池(静态常量池)
        • 运行时常量池

示例代码-1

/**
 * JDK 6: -XX:PermSize=6m -XX:MaxPermSize=6m
 * 报永久代溢出(java.lang.OutOfMemoryError: PermGen space)
 * ==========
 * JDK 7: -XX:PermSize=6m -XX:MaxPermSize=6m
 * 不报错,原因:JDK 7把字符串常量池放到堆了,设置-Xmx6m会报堆内存溢出
 * ==========
 * JDK 8+:同JDK 7
 */
public class MethodAreaOOMTest1 {
    public static void main(String[] args) {
        // 使用Set保持着常量池引用
        Set<String> set = new HashSet<String>();
        int i = 0;
        while (true) {
            // intern():native方法
            // 如果字符串常量池里面已经包含了等于字符串x的字符串;那么
            // 就返回常量池中这个字符串的引用
            // 如果常量池中不存在,那么就会把当前字符串添加到常量池,
            // 并返回这个字符串的引用
            set.add(String.valueOf(i++).intern());
        }
    }
}

示例代码-2

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MethodAreaOOMTest2 {
    /**
     * Cglib之Enhancer创建动态代理:https://blog.csdn.net/yaomingyang/article/details/82762697
     *
     * @param args 参数
     */
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Hello.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    System.out.println("Enhanced hello");
                    // 调用Hello.say()
                    return proxy.invokeSuper(obj, args);
                }
            });
            Hello enhancedOOMObject = (Hello) enhancer.create();
            enhancedOOMObject.say();
            System.out.println(enhancedOOMObject.getClass().getName());
        }
    }
}

class Hello {
    public void say() {
        System.out.println("Hello Student");
    }
}

通过配置元空间的默认值和最大值
VM options: -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
运行Main时候就会抛出:java.lang.OutOfMemoryError: Metaspace 异常。
因为类的定义是放在元空间,而现在超出元空间的最大值。

方法区总结

  • 不同版本的JDK,方法区存放的结构不同,相同的代码报错也可能不同

方法区溢出的场景

  • 常量池里的对象太大
  • 加载的类的 “种类” 太多
    • 动态代理的操作库生成了大量的动态类
    • JPS项目
    • 脚本语言动态类加载

避免方法区溢出

  • 根据JDK版本,为常量池保留足够空间
    • JDK6: 配置较大的 PermSize、MaxPermSize
    • 大于等于JDK7: 配置较大的 Xms、Xmx
  • 防止类加载过多导致的溢出
    • 小于等于JDK7:配置较大 PermSize、MaxPermSize
    • 大于等于JDK8:留空元空间相关的配置,或者设置合理大小的元空间

元空间是本地内存,本地内存足够就基本不会出现溢出

属性作用默认值
-XX:MetaspaceSize元空间的初始值,元空间占用达到该值就会触发垃圾收集,进行类型卸载,同事,收集器会自动调整该值。如果能够释放空间,就会自动降低该值;如果释放空间很少,那么在不超过-XX:MaxMetaspaceSize的情况下,可适当提高该值。21810376字节
-XX:MaxMetaspaceSize元空间最大值受限于本地内存大小
-XX:MinMetaspaceFreeRatio垃圾收集后,计算当前元空间的空闲百分比,如果小于该值就增加元空间的大小40%
-XX:MaxMetaspaceFreeRatio垃圾收集后,计算当前元空间的空闲百分比,如果大于该值就增加元空间的大小70%
-XX:MinMetaspaceExpansion元空间增长时的最小幅度340784字节
-XX:MaxMetaspaceExpansion元空间增长时的最大幅度5452592字节