LoginSignup
1
2

More than 3 years have passed since last update.

JAXBのメモリリーク(~v2.1.12)を回避

Last updated at Posted at 2018-09-03

jaxb-impl-2.1.12 の問題です。 jaxb-impl-2.1.13 では発生しません。

メモリリーク

JAXBを利用して、JSON/XML変換をしている処理でPermanent領域の不足が発生した。
原因を調べると、JAXBがメモリリークしているようだ。

後に載せているサンプルコードの様にJAXBContext.newInstanceもしくはnew JSONJAXBContextなどを繰り返すとVOのクラスが毎回クラスロードされ、Permanent領域の使用が増え続ける。

解決策は、createMarshaller1_2のように JAXBContextをキャッシュしてあげること。
jaxb-impl-2.1.13では発生しないので、バージョンを上げることでも解決できます。
とはいえ、ライブラリを変更できないレガシーシステムの場合もあります。
(この場合がそうだったので自前でキャッシュしました。)

  • 上が修正前のGCグラフ、下が修正後のGCグラフ(Permanent不足によるConcurrentGCが減った) gc.png

jaxb-impl-2.1.13のリリースノートには、バグを修正したぐらいしか情報が見つかりませんでした。

Notable Changes between 2.1.12 to 2.1.13
Fixes to bugs reported in java.net
https://jaxb.java.net/2.1.17/docs/changelog2.html

検証用コード

Jikken.java
package jp.kajiken.jikken;

import java.io.StringWriter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;
import com.sun.jersey.api.json.JSONMarshaller;

public class Jikken {

    private static JAXBContext jaxbContext = null;
    private static Marshaller marshal = null;

    public static void main(String[] args) throws Exception {
        System.out.println("Start");
        while (true) {
            createMarshaller1(new AppVO());
        }
    }

    protected final static void createMarshaller1(Object obj) throws Exception {

        StringWriter sw = new StringWriter();
        JAXBContext jaxbContext = JAXBContext.newInstance(obj.getClass());
        Marshaller marshal = jaxbContext.createMarshaller();
        marshal.marshal(obj, sw);
        sw.close();
    }

    protected final static void createMarshaller2(Object obj) throws Exception {

        StringWriter sw = new StringWriter();
        JAXBContext context = new JSONJAXBContext(JSONConfiguration.mapped()
                .rootUnwrapping(true).build(), obj.getClass());

        JSONMarshaller jsonMarshaller = JSONJAXBContext
                .getJSONMarshaller(context.createMarshaller());

        jsonMarshaller.marshallToJSON(obj, sw);
        sw.close();
    }

    protected final static void createMarshaller1_1(Object obj) throws Exception {

        StringWriter sw = new StringWriter();
        if (null == marshal) {
            JAXBContext jaxbContext = JAXBContext.newInstance(obj.getClass());
            marshal = jaxbContext.createMarshaller();
        }
        marshal.marshal(obj, sw);
        sw.close();
    }

    protected final static void createMarshaller1_2(Object obj) throws Exception {

        StringWriter sw = new StringWriter();
        if (null == jaxbContext) {
            jaxbContext = JAXBContext.newInstance(obj.getClass());
        }
        Marshaller marshal = jaxbContext.createMarshaller();
        marshal.marshal(obj, sw);
        sw.close();
    }

}
AppVO.java
package jp.kajiken.jikken;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class AppVO {
    private String aaa = "a";
    private String bbb = "b";
    private String ccc = "c";
    private String ddd = "d";
    private String eee = "e";
    private String fff = "f";
    private String ggg = "g";
    private String hhh = "h";
    private String iii = "i";
    public String getAaa() {
        return aaa;
    }
    public void setAaa(String aaa) {
        this.aaa = aaa;
    }
    public String getBbb() {
        return bbb;
    }
    public void setBbb(String bbb) {
        this.bbb = bbb;
    }
    public String getCcc() {
        return ccc;
    }
    public void setCcc(String ccc) {
        this.ccc = ccc;
    }
    public String getDdd() {
        return ddd;
    }
    public void setDdd(String ddd) {
        this.ddd = ddd;
    }
    public String getEee() {
        return eee;
    }
    public void setEee(String eee) {
        this.eee = eee;
    }
    public String getFff() {
        return fff;
    }
    public void setFff(String fff) {
        this.fff = fff;
    }
    public String getGgg() {
        return ggg;
    }
    public void setGgg(String ggg) {
        this.ggg = ggg;
    }
    public String getHhh() {
        return hhh;
    }
    public void setHhh(String hhh) {
        this.hhh = hhh;
    }
    public String getIii() {
        return iii;
    }
    public void setIii(String iii) {
        this.iii = iii;
    }
}

スレッドを使う場合のキャッシュ例

おそらく実用的なのはこちら。

JAXBContextHolder.java
package jp.kajiken.jikken;

import java.util.concurrent.ConcurrentHashMap;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import jp.kajiken.jikken.AppVO;

import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;

/**
 * JAXBContextHolder
 * @author K.Kajino
 * @version $Id$
 * @since JDK5.0
 */
public class JAXBContextHolder {

    /** JAXBContext Cache. */
    private static ConcurrentHashMap<Integer, JAXBContext> xml =
        new ConcurrentHashMap<Integer, JAXBContext>();

    /** JSONJAXBContext Cache. */
    private static ConcurrentHashMap<Integer, JSONJAXBContext> json =
        new ConcurrentHashMap<Integer, JSONJAXBContext>();

    /** JSONConfiguration */
    public static final JSONConfiguration config =
        JSONConfiguration.mapped().rootUnwrapping(true).build();


    /**
     * getJSONJAXBContext
     * @param clazz Class<?>
     * @return JSONJAXBContext
     * @throws JAXBException
     */
    public static JSONJAXBContext getJSONJAXBContext(Class<?> clazz) throws JAXBException {
        if (containsJSONJAXBContext(clazz)) {
            return json.get(clazz.hashCode());
        }

        JSONJAXBContext context = new JSONJAXBContext(
            config,
            AppVO.class,
            clazz);

        putJSONJAXBContext(clazz, context);

        return context;
    }

    /**
     * putJSONJAXBContext
     * @param clazz Class<?>
     * @param context JSONJAXBContext
     */
    public static void putJSONJAXBContext(Class<?> clazz, JSONJAXBContext context) {
        json.put(clazz.hashCode(), context);
    }

    /**
     * containsJSONJAXBContext
     * @param clazz Class<?>
     * @return boolean
     */
    public static boolean containsJSONJAXBContext(Class<?> clazz) {
        return json.containsKey(clazz.hashCode());
    }


    /**
     * getJAXBContext
     * @param clazz Class<?>
     * @return JAXBContext
     * @throws JAXBException
     */
    public static JAXBContext getJAXBContext(Class<?> clazz) throws JAXBException {
        if (containsJAXBContext(clazz)) {
            return xml.get(clazz.hashCode());
        }

        JAXBContext context = JAXBContext.newInstance(
            AppVO.class,
            clazz);

        putJAXBContext(clazz, context);

        return context;
    }

    /**
     * putJAXBContext
     * @param clazz Class<?>
     * @param context JAXBContext
     */
    public static void putJAXBContext(Class<?> clazz, JAXBContext context) {
        xml.put(clazz.hashCode(), context);
    }

    /**
     * containsJAXBContext
     * @param clazz Class<?>
     * @return boolean
     */
    public static boolean containsJAXBContext(Class<?> clazz) {
        return xml.containsKey(clazz.hashCode());
    }

}

JAXBContext context = JAXBContextHolder.getJAXBContext(clazz);として使ってください。
まぁ、ライブラリのバージョンを上げられるのならその方が良いですよ…。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2