和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空间越大,可保存的类信息就越多.