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

**<font color="red">jaxb-impl-2.1.12 の問題です。 jaxb-impl-2.1.13 では発生しません。</font>**

## メモリリーク

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](https://qiita-image-store.s3.amazonaws.com/0/27443/0100f27a-1185-0bc7-ca3a-f5c93d05eaed.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

## 検証用コード
```java: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();
    }

}
```

```java: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;
    }
}
```

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

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

```java: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);`として使ってください。
まぁ、ライブラリのバージョンを上げられるのならその方が良いですよ…。