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
では発生しないので、バージョンを上げることでも解決できます。
とはいえ、ライブラリを変更できないレガシーシステムの場合もあります。
(この場合がそうだったので自前でキャッシュしました。)
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
検証用コード
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();
}
}
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;
}
}
スレッドを使う場合のキャッシュ例
おそらく実用的なのはこちら。
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);
として使ってください。
まぁ、ライブラリのバージョンを上げられるのならその方が良いですよ…。