Orika导致的一次jvm OOM

Orika是一个不错的Bean拷贝工具,可以将类的不同属性做映射进行拷贝。比如A对象的price和B对象的fee都表示价格,类型也都一样,只是字段名称不一样,这样通过映射可以方便的进行拷贝。

下面是个示例:

Source类:

public class Source{
    private Integer id;
    private Double price;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }
}

Target类:

public class Target{
    private Integer id;
    private Double fee;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Double getFee() {
        return fee;
    }

    public void setFee(Double fee) {
        this.fee = fee;
    }
}

使用Orika

maven坐标:

    <dependency>
      <groupId>ma.glasnost.orika</groupId>
      <artifactId>orika-core</artifactId>
      <version>1.5.4</version>
    </dependency>

oirka拷贝程序--错误示例

public class OOMTest {
    private static final MapperFactory MAPPER_FACTORY = new DefaultMapperFactory.Builder().build();

    public static void main(String[] args) {
        while(true) {
            Source source = new Source();
            source.setId(111);
            source.setPrice(123d);
            System.out.println(JSON.toJSONString(source));
            MAPPER_FACTORY.classMap(Source.class, Target.class)
                    .field("price", "fee")
                    .byDefault()
                    .register();
            Target target2 = MAPPER_FACTORY.getMapperFacade().map(source, Target.class);
            System.out.println(JSON.toJSONString(target2));
        }
    }
}

静态变量创建MapperFactory,classMap是映射两个类型中的相同含义不同名称的属性。

打印的结果:

{"id":111,"price":123.0}
{"fee":123.0,"id":111}

之所以要用while循环,是为了测试大量调用的时候元空间溢出的问题,jvm对元空间的设置:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=48m,最终报错:

Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
	at javassist.util.proxy.DefineClassHelper.toClass(DefineClassHelper.java:274)
	at javassist.ClassPool.toClass(ClassPool.java:1232)
	at javassist.CtClass.toClass(CtClass.java:1384)
	at ma.glasnost.orika.impl.generator.JavassistCompilerStrategy.compileClass(JavassistCompilerStrategy.java:246)
	at ma.glasnost.orika.impl.generator.SourceCodeContext.compileClass(SourceCodeContext.java:228)
	at ma.glasnost.orika.impl.generator.SourceCodeContext.getInstance(SourceCodeContext.java:244)
	at ma.glasnost.orika.impl.generator.MapperGenerator.build(MapperGenerator.java:73)
	at ma.glasnost.orika.impl.DefaultMapperFactory.buildMapper(DefaultMapperFactory.java:1478)
	at ma.glasnost.orika.impl.DefaultMapperFactory.registerClassMap(DefaultMapperFactory.java:1254)
	at ma.glasnost.orika.impl.DefaultMapperFactory.registerClassMap(DefaultMapperFactory.java:1267)
	at ma.glasnost.orika.metadata.ClassMapBuilder.register(ClassMapBuilder.java:768)
	at orika.OOMTest.main(OOMTest.java:23)

在jdk8中,元空间在jvm之外,也就是说对jvm的监控是没有元空间的报警的。

程序执行过程中,通过命令查看元空间的占用情况,打开命令提示符

查看java进行,jps -l

27796 org.jetbrains.jps.cmdline.Launcher
34036 org.jetbrains.jps.cmdline.Launcher
13528 sun.tools.jps.Jps
28056
14860 org.jetbrains.idea.maven.server.RemoteMavenServer
19964 org.jetbrains.jps.cmdline.Launcher
24268 orika.OOMTest

通过jstat查看元空间的使用情况:jstat -gcmetacapacity 24268  1000

MC列表示当前元空间的使用情况,发现元空间的占用是越来越大的。是因为Orika的类映射是生成字节码文件,该文件是存到元空间中的,直到占满设置的元空间大小(本示例是48M) ,导致的内存溢出。

问题解决:

对象映射不用每次都做,注册一次就够了。

public class OOMTest {
    private static final MapperFactory MAPPER_FACTORY = new DefaultMapperFactory.Builder().build();
    static {
        MAPPER_FACTORY.classMap(Source.class, Target.class)
                .field("price", "fee")
                .field("id", "id")
                .byDefault()
                .register();
    }
    public static void main(String[] args) {
        while(true) {
            Source source = new Source();
            source.setId(111);
            source.setPrice(123d);
            System.out.println(JSON.toJSONString(source));
//            MAPPER_FACTORY.classMap(Source.class, Target.class)
//                    .field("price", "fee")
//                    .byDefault()
//                    .register();
            Target target2 = MAPPER_FACTORY.getMapperFacade().map(source, Target.class);
            System.out.println(JSON.toJSONString(target2));
        }
    }
}

程序的改动就是将每次执行拷贝的地方的classMap注释,放在外面的静态块中,只执行一次就可以了。 

这次在看元空间的监控效果:

MC不在增长。解决问题。

元空间查看参考文章:https://www.jianshu.com/p/123079b47670

发布了57 篇原创文章 · 获赞 39 · 访问量 20万+

猜你喜欢

转载自blog.csdn.net/SJZYLC/article/details/103641073
OOM