(1) Metaspace 区域发生内存溢出的原理
- 一旦 JVM 不停加载类,加载了很多很多的类,然后 Metaspace 区域放满了;
- 一旦 Metaspace 区域满了,会触发 Full GC,连带着回收 Metaspace 里的类;
- 类要满足被回收的条件,是相当苛刻的,比如:这个类的类加载器先要被回收,比如:这个类的所有对象实例都要被回收,等等;
- 一旦回收了 Metaspace 中的类之后,发现还是没能腾出太多空间,仍然要往 Metaspace 中放入更多的类,进入导致内存溢出的问题,因为 Metaspace 空间的内存空间不够;
(2) 两种常见的触发 Metaspace 内存溢出的场景
- Metaspace 直接使用默认参数,根本不设置其大小,进而导致默认的 Metaspace 可能才几十MB而已,对大一点的系统而言根本不够用,推荐值 512MB,一般足够的;
- 系统中太多使用 cglib 之类的技术动态生成一些类,一旦代码中没有控制好,导致生成的类过于多的时候,很容易把 Metaspace 塞满,进而引发内存溢出;
(3) 模拟 Metaspace 内存溢出的场景
JVM 参数
-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
一段 CGLIB 动态生成类的代码示例
添加依赖 cglib
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
public class Demo1 { public static void main(String[] args) { long counter = 0; while (true){ System.out.println("目前创建了"+(++counter)+"个Car类的子类"); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Car.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { if(method.getName().equals("run")){ System.out.println("汽车启动之前,先进行自动的安全检查...."); return methodProxy.invokeSuper(o, objects); }else { return methodProxy.invokeSuper(o, objects); } } }); Car car = (Car) enhancer.create(); car.run(); } } static class Car{ public void run(){ System.out.println("汽车启动,开始行驶...."); } } }
以上代码相当于手动创建 Car 的子类:
public lass SageCar extends Car { public void run(){ System.out.println("汽车启动之前,先进行自动的安全检查...."); super.run(); } }
异常日志:
目前创建了260个Car类的子类 Exception in thread "main" net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null ... Caused by: java.lang.OutOfMemoryError: Metaspace at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ... 11 more