JVM(四) Java虚拟机中的方法区或永久区

本文探讨了Java方法区(在JDK1.6、1.7中称为永久代 PermGen,在JDK1.8后变为元空间 Metaspace)的内存管理,特别是当使用CGLIB库大量动态生成类时对方法区的影响。通过示例代码展示了如何使用CGLIB动态创建Bean,并通过测试观察到当元空间大小限制为5M时,系统在创建约10553个Class对象后引发内存溢出。实验表明,元空间大小直接影响可保存的类信息数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    和java堆一样,方法区是一块所有线程共享的内存区域。它用于保存系统的类信息(字段,方法,常量池等). 方法区的大小决定系统可保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误。

    在JDK1.6、JDK1.7中,方法区可以理解为永久区(Perm)。永久区可以使用参数-XX:PermSize和-XX:MaxPermSize指定,默认情况下,-XX:MaxPermSize为64M。一个大的永久区可以保存更多的类信息。如果系统使用了一些动态代理(Proxy),那么有可能会在运行时生成大量的类,如果这样,就需要设置一个合理的永久区大小,确保不发生永久区内存溢出。

   下面程序使用CGLIB库来测试在大量生成动态类时,JVM中Perm区的使用情况. 

1. 先使用maven引入cglib及asm库. 

  <dependency>
       <groupId>cglib</groupId>
       <artifactId>cglib</artifactId>
       <version>2.2</version>
</dependency>
<dependency>
       <groupId>asm</groupId>
       <artifactId>asm</artifactId>
       <version>3.3.1</version>
</dependency>

2. 利用 ASM 动态生成 Class 对象. 

import net.sf.cglib.beans.BeanGenerator;
import net.sf.cglib.core.ClassEmitter;
import net.sf.cglib.core.Constants;
import net.sf.cglib.core.EmitUtils;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Type;
import java.util.HashMap;
import java.util.Map;

/**
 * Bean生成器对象
 */
public class BeanGeneratorObj extends BeanGenerator{
    public BeanGeneratorObj() {
        super();
    }

    private Map props = new HashMap();   //类中所有的属性 Map

    /**
     * 给类中添加属性及类型
     * @param name
     * @param type
     */
    @Override
    public void addProperty(String name, Class type) {
        super.addProperty(name, type);
        if (props.containsKey(name)) {
            throw new IllegalArgumentException("Duplicate property name \"" + name + "\"");
        }
        props.put(name, Type.getType(type));
    }

    /**
     * 生成类的Class对象
     * @param v
     * @throws Exception
     */
    @Override
    public void generateClass(ClassVisitor v) throws Exception {
        // ClassVisitor   ->  用于访问类的  Class部分
        int size = props.size();
        String[] names = (String[]) props.keySet().toArray(new String[size]);  //取出所有的属性名,并根据属性数生成对应的Type[]
        Type[] types = new Type[size];
        for (int i = 0; i < size; i++) {
            types[i] = (Type) props.get(names[i]);
        }
        ClassEmitter ce = new ClassEmitter(v);
        //  通过 ClassEmitter创建 Class
        //         参数:   class版本    权限修饰符               类名   父类 ,  接口, 源码
        ce.begin_class(Constants.V1_2, Constants.ACC_PUBLIC, getClassName(),
                getDefaultClassLoader() != null ? Type.getType(getDefaultClassLoader().getClass())
                        : Constants.TYPE_OBJECT, null, null);
        EmitUtils.null_constructor(ce);  //无参构方法
        add_properties(ce, names, types);   //给ce 添加属性及类型
        ce.end_class();   //结束class创建,并调用 static方法
    }

    private void add_properties(ClassEmitter ce, String[] names, Type[] types) {
        for (int i = 0; i < names.length; i++) {
            String fieldName = names[i];
            ce.declare_field(Constants.ACC_PRIVATE, fieldName, types[i], null);
            EmitUtils.add_property(ce, names[i], types[i], fieldName);
        }
    }
}

3. 利用cglib库根据上面生成的Class生成Bean


import net.sf.cglib.beans.BeanGenerator;
import net.sf.cglib.beans.BeanMap;
import net.sf.cglib.core.NamingPolicy;
import net.sf.cglib.core.Predicate;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class CglibBean {
    /**
     * 实体Object
     */
    public Object object = null;

    /**
     * 属性map
     */
    public BeanMap beanMap = null;

    public CglibBean() {
        super();
    }

    /**
     * 通过属性创建对象
     * @param propertyMap
     */
    @SuppressWarnings("unchecked")
    public CglibBean(Map propertyMap) {
        this.object = generateBean(propertyMap);
        this.beanMap = BeanMap.create(this.object);
    }

    /**
     * 通过 类名和属性名创建对象
     * @param className
     * @param propertyMap
     */
    public CglibBean(String className,Map propertyMap) {
        this.object = generateBean(className,propertyMap);
        this.beanMap = BeanMap.create(this.object);
    }

    /**
     * 给bean属性赋值
     *
     * @param property
     *            属性名
     * @param value
     *            值
     */
    public void setValue(String property, Object value) {
        beanMap.put(property, value);
    }

    /**
     * 通过属性名得到属性值
     *
     * @param property
     *            属性名
     * @return 值
     */
    public Object getValue(String property) {
        return beanMap.get(property);
    }

    /**
     * 得到该实体bean对象
     *
     * @return
     */
    public Object getObject() {
        return this.object;
    }

    /**
     * 创建Bean, 普通Bean
     * @param propertyMap
     * @return
     */
    private Object generateBean(Map propertyMap) {
        BeanGenerator generator = new BeanGenerator();   //Bean生成器对象
        Set keySet = propertyMap.keySet();   //获取所有属性
        for (Iterator i = keySet.iterator(); i.hasNext();) {   //循环每个属性
            String key = (String) i.next();
            generator.addProperty(key, (Class) propertyMap.get(key));   //设置属性值
        }
        return generator.create();   //创建对象
    }

    /**
     * 根据className, 属性Map生成B月工已
     * @param className
     * @param propertyMap
     * @return
     */
    private Object generateBean(final String className,Map propertyMap) {
        BeanGeneratorObj generator = new BeanGeneratorObj();
        generator.setUseCache(false);
        generator.setNamingPolicy(new NamingPolicy() {
            @Override
            public String getClassName(String prefix, String source, Object key, Predicate names) {
                return className;
            }
        });

        Set keySet = propertyMap.keySet();
        for (Iterator i = keySet.iterator(); i.hasNext();) {
            String key = (String) i.next();
            generator.addProperty(key, (Class) propertyMap.get(key));
        }
        return generator.create();
    }
}

以上代码中重要的方法是两个 generateBean()的重载方法, 它们负责根据 属性Map 来动态创建Bean. 

4. 测试生成动态类. 

import java.util.HashMap;

/**
 * JDK1.6 1.7 -XX:+PrintGCDetails -XX:PermSize=5M -XX:MaxPermSize=5m
 * JDK1.8 -XX:+PrintGCDetails -XX:MaxMetaspaceSize=5M
 *
 */
public class PermTest {
    public static void main(String[] args) {
        int i = 0;
        try {
            for (i = 0; i < 100000; i++) {
                CglibBean bean = new CglibBean("com.yc.TestObject" + i, new HashMap());
            }
        } catch (Exception e) {
            System.out.println("内存溢出,总创建对象:" + i);
            throw e;
        }
    }
}

5. 运行结果分析: 

当我修改 -XX:MaxMetaspaceSize=50M 后, 系统在创建完10553个Class对象后,内存溢出。 

小结: 当使用cglib动态产生类时,不仅仅是对象实例,由于类的信息(字段,方法,字节码等)保存在此,所以metaspace空间越大,可保存的类信息就越多. 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangyingchengqi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值