cloneNodeするとサイズが増える

このQ&Aのポイント
  • XMLのテンプレートを編集して出力するプログラムで、cloneNodeの使用によりメモリリークが発生している。
  • Java 1.6での実装で、EntityのフィールドのElementオブジェクトの容量がcloneNodeの実行ごとに増加している。
  • データベースはOracle Databaseを使用しており、EntityのフィールドのElementオブジェクトの容量の増加が問題となっている。
回答を見る
  • ベストアンサー

cloneNodeするとサイズが増える

データベースからXML文書のテンプレートを取得し、それを編集して出力するプログラムを作っています。 性能要件のため、「一度取得したXMLはメモリにキャッシュしておき、2回目以降はキャッシュされたXMLを再利用する」仕組みにしています。 public class Entity{ private Element XML; public Element getXML(){ return XML.cloneNode(); } public void setXML(Element XML){ this.XML = XML.cloneNode(); } } public class Dao{ public Element getXML(){ //DBにアクセスしてXML文書を取得する処理   return XML; } } public class Main(){ private Dao dao; public void outputXML(Entity entity){ Element XML = entity.getXML; if (entity == null){ XML = dao.getXML entity.setXML(XML) }   //以下XMLを編集して出力する処理 } } get時とset時にcloneNodeすることで、保存されたテンプレートは書き換えられない……という事を期待し、実際その処理はうまく行っていたのですが 動作させているうちにメモリリークが発生し、調査してみるとどうやらcloneNodeが実行されるたびに、EntityのフィールドのElementオブジェクトの容量が増加している様子… 一体なぜこのような現象が起きているのでしょうか? また、これを回避する方法はあるでしょうか。 javaのバージョンは1.6 データベースはOracle Databaseを使用しています よろしくおねがいします。

  • Java
  • 回答数3
  • ありがとう数12

質問者が選んだベストアンサー

  • ベストアンサー
  • KSOH
  • ベストアンサー率93% (29/31)
回答No.3

問題にされている点が何かがよくわかりました。少々失礼な確認をしてしまいましたがご容赦ください。 確認された結果からcloneNodeでコピー元のオブジェクト構造のどこかが変化しメモリーの増加が生じるのは確かのように思えます。 >総使用メモリは増加するが、オブジェクトそのもののサイズは変わらないというつもりだったのですが... 自分はこのデータ構造についての詳細を知らないので階層構造のコピーで何が起こる可能性があるかが推測できません。 cloneNodeのAPI Documentをみても「関連するデータとともに UserDataHandlers が指定された場合は、このメソッドから戻る前に、それらのハンドラが該当するパラメータで呼び出されます。」というあたりからハンドラー処理によっては何か起こる可能性があるかな?ぐらいしか思いつきませんでした。この原因をご存じの方がコメントをくだされば一番早いのですが・・・ もし自分が調べるとすれば、メモリーサイズを調べるユーティリティの実装を少し変え、クラスごとのトータルサイズを調べるようにすると思います。cloneNode前後でどのクラスのインスタンスが増加しているのかの情報を手掛かりに原因を推測してゆくといった作戦ですが、これは少々迂遠な方法に思えます。このデータ構造についての知識があればもう少し的確かつ速やかに原因を推測できる気がしますので。 お役に立てず申し訳ありません。

tsuchinoko07
質問者

お礼

ご回答ありがとうございました。 解消方法はわからなかったため、別の方法を探してみようと思います。

その他の回答 (2)

  • KSOH
  • ベストアンサー率93% (29/31)
回答No.2

Javaにおいてメモリーリークの定義はGCしても回収されないオブジェクトが不当に残ることだと思います。「不当に残る」というのは「設計上意図した以上の期間にわたり不必要に長い期間GCで回収されずに残る」ということになります。 outputXML()ではentity.getXML()、entity.setXML()を用いているためにElementオブジェクトの量はcloneNode()により一時的に2倍程度になります。しかしoutputXML()の実行中はこれらのElementオブジェクトは出力処理で使用中なのでGCを行っても回収できません。これらはoutputXML()が完了した後でどこからも参照されなくなった時点で初めてGCにより回収可能となります。このように動作しているのであれば前述のようにElementは設計上意図したとおりの期間だけ生存しておりメモリーリークではないと言えます。 そうでなくoutputXML()実行完了後にGCを行っても回収されずに残る(outputXML()を何度も実行する過程でGCした結果の使用中オブジェクトのサイズが増加し続ける)ことを確認済みであればそれは確かにメモリーリークです。その場合はEntityやElementの参照が自らのコード中で参照を残すような論理がないかとか、ライブラリーの中で参照をなんらかの形でキャッシュしていないかを疑うことになります。 内容を拝見していてメモリーリークをどういう意味で使われているのか、具体的にどのようにして何を確認されたのかがはっきりわかりませんでしたので問題をはっきりさせるためにそれを伺いたいと感じました。

tsuchinoko07
質問者

補足

起きた事象を時系列順に並べますと 1.上記のプログラムを使用したバッチを実行したところ、途中でOutOfMemoryErrorが発生 2.cloneNodeが怪しそうだったので、Classmexer(http://jyohotushinkogaku.blogspot.jp/2012/11/java.html)を利用し、Entityクラスを下記のように変更して使用メモリを測定   getXML(){    system.out.println(MemoryUtil.deepMemoryUsageOf(XML); //(1) Element returnElement = XML.cloneNode(true); system.out.println(MemoryUtil.deepMemoryUsageOf(XML); //(2) return returnElement; } 3.2の状態でプログラムを実行した結果、(1)では300kbだった使用メモリが(2)では330kbになっており、   以降getXMLが呼ばれるたびに330kb、360kb、390kb……という調子でElementオブジェクトの使用メモリが増加していた。 という顛末です。 使用メモリの増加量は、多少の誤差はあるもののほぼ同じ量でした。 私の考えでは、cloneNodeすることによって総使用メモリは増加するが、オブジェクトそのもののサイズは変わらないというつもりだったのですが。。。

  • teketon
  • ベストアンサー率65% (141/215)
回答No.1

clone系のメソッドは、一般的にオブジェクトのすべての要素をコピー(Deep Copy)を行う、 非常に高コストのメソッドです。 プログラムを見た限り、EntityのgetXML、setXMLの操作は 所有するXMLオブジェクトの2倍のメモリを必要としています。 あと、キャッシュすることと、clonNodeすることは全く違う理由に思いますが。

関連するQ&A

  • Xercesを使ったjavaでのXML解析

    DOMを使ってXML文書を解析するJavaのソースコードで、DOMパーサは、クラス org.apache.xerces.parsers.DOMParserで参照している下記のプログラムで、 [Fatal Error] :17:109: The entity name must immediately follow the '&' in the entity reference. org.xml.sax.SAXParseException; lineNumber: 17; columnNumber: 109; The entity name must immediately follow the '&' in the entity reference. のエラーが出てしまって、解決策が分かりかねています。Javaのネットワークプログラミングに詳しい方、御教示願えればと思います。 package nikkei; import java.io.ByteArrayInputStream; import org.apache.xerces.parsers.DOMParser; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; public class TwitterSearch { public static void main(String[] args) throws Exception { TwitterSearch search = new TwitterSearch(); search.search("日経ソフトウエア"); } public void search(String keyword) throws Exception { SearchAPIClient client = new SearchAPIClient(); String xml = client.execute(keyword); parse(xml); } private void parse(String xml) throws Exception { DOMParser parser = new DOMParser(); try { parser.parse(new InputSource(new ByteArrayInputStream(xml.getBytes()))); Document doc = parser.getDocument(); NodeList entries = doc.getElementsByTagName("entry"); for (int i = 0; i < entries.getLength(); i++) { String name = null; String tweet = null; Element entry = (Element) entries.item(i); NodeList titleList = entry.getElementsByTagName("title"); if (titleList.getLength() == 1) { tweet = titleList.item(0).getTextContent(); } NodeList authorList = entry.getElementsByTagName("author"); if (authorList.getLength() == 1) { Element author = (Element) authorList.item(0); NodeList nameList = author.getElementsByTagName("name"); if (nameList.getLength() == 1) { name = nameList.item(0).getTextContent(); } } System.out.println(name + "さんのツイート"); System.out.println("\t" + tweet); } } catch (Exception e) { e.printStackTrace(); } } } package nikkei; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; public class SearchAPIClient { public String execute(String keyword) throws Exception { String url = "https://twitter.com/search?q=" + keyword; HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(url); HttpResponse response = httpClient.execute(httpGet); HttpEntity entity = response.getEntity(); if (entity != null) { return EntityUtils.toString(entity); } else { return null; } } } よろしくお願いいたします。

    • ベストアンサー
    • Java
  • 「オブジェクト志向」の考えかたで質問します。

    「オブジェクト志向」の考えかたで質問します。 いろいろと調べると、 ・繼承 ・カプセル化 ・ポリモーフィズム を総称したのが、「オブジェクト」志向と理解しています。 このとき、 たとえば、 指定するクラスの生徒の情報をとりだすようなソースをつくりたい。 仮に、以下をかんがえてみました。 DB処理は、省いてます。 //実行DAOクラス public class StudentDAO extends StudentDBAccessor{ //指定するクラスに属する生徒をとりだす public List getStudentList(int classNumber){ return super.getStudentList(); } //sql文生成 protected String createSqlSelectStudentList(){ StringBuffer sb = new StringBuffer(); return sb.toString(); } //キーワードを設定 public void setDataSqlStudentList(){ } } public abstract StudentDBAccessor extends DBConnector{ protected List getStudentList(){   //DBそうさ } protected abstract String createSqlStudentList(); protected void setDataSqlStudentList(int classNumber); } //DB接続クラス public class DBConnector{   //省略 } //Beanクラス public class StudentFormBean{ private int studentNumber; private String studentName; public void setStudentNumber(int number){ this.studentNumber = number; } public int getStudentNumber(){ return studentNumber; } } よろしくおねがいします。

    • ベストアンサー
    • Java
  • HQLについて分らない

    ところがあります。 Entityを以下の様に定義したとします。 public class Member implements Serializable { private Integer no; private String name; public Integer getNo(){return this.no;} public void setNo(Integer no){this.no = no;} public Member(){} public String getName(){return this.name;} public void setName(String name){this.name = name;} } その場合にHQLにおいて Query findquery=session.createQuery("select m from Member m where m.no= :no"); findquery.setInteger( "no", no.intValue() ); というように書くことが出きるのでしょうか? noはintでなくIntegerなのに m.no= :no などと記してよいのでしょうか?(Integer=intになっている) それともHQLを使うためには Entityでnoの型をIntegerでなくintにしなければならないのでしょうか?

    • ベストアンサー
    • Java
  • パラメータのやりとり

    3つのクラスの中でHTML上から取得したパラメータをやり取りしたいと思っています。 1つ目のクラス(CLASS_1.java) public class CLASS_1 extends CLASS_0 { public void doMain(HttpServletRequest request,HttpServletResponse response){ try{ String[] str = makeItem(request); CLASS_2 class_2 = new CLASS_2(); class_2.setItem(str); private String[] makeItem(HttpServletRequest request){ String[] result = new String[3]; result[0] = request.getParameter("test1"); result[1] = request.getParameter("test2"); result[2] = request.getParameter("test3"); return result; 2つ目のクラス(CLASS_3.Java) public class CLASS_3{ public void setItem(String[] data){ } public boolean makeTEXT(){ return true; } public ByteArrayOutputStream getTEXT(){ return null; } } 3つ目のクラス(CLASS_2) public class CLASS_2 extends CLASS_3 { public boolean makeTEXT(){ return true; } } という3つのクラスを作成しました。 CLASS_1で取得したstrの中の値をCLASS_2で使用したいと考えています。 ただ羅列するだけでかまいませんので、わかるかたヒントや参考になりそうなHPなどありましたら よろしくお願いします。

  • スレッドセーフ?スレッドアンセーフ?

    Struts2にてスレッドセーフを勉強しており 以下のコードがスレッドセーフなのか、それともスレッドアンセーフなのかご意見を頂きたいと思います。 個人的な意見ですが、インスタンス変数を利用しているためスレッドアンセーフになるのではないかと思いますが間違いございませんでしょうか。 //以下 Struts2 public class InsertProgram extends ActionSupport implements ServletRequestAware{ private Test test = new Test(); private String id; private String name; public class execute(){ id     = request.getParameter("id"); name = request.getParameter("name");        //以下がご意見を頂きたい箇所です。 test.setId(id); test.setName(name); test.insert(); return SUCCESS; } } public class Test{ private String id; private String name; public String getId() { return id; } public String getName() { return name; } public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public class insert() { //割愛致します。 データベースにid 及び name をinsert します。 sql = "Insert into test_db(id , name) ・・・" } }

    • ベストアンサー
    • Java
  • Java インスタンス作成のイベント取得方法

    インスタンス化したことをイベントで取得することはできますか? 例) public class MyClass{ private MyDialog dialog = null; public void action(){ dialog = new MyDialog(); //ダイアログの中身の処理・・・(1) dialog.show(); } public MyDialog getDialog(){ return dialog; } } public class MyDialog extends JDialog{ (省略) } 前提 クラスMyClassの中身は変更することができない。 やりたいことは、MyClass#action()が呼ばれるたびに MyDialogにはってあるオブジェクトに命令を行いたい。 MyDialogのインスタンス作成イベントを取得できれば 上記を実現できると考えています。 以上です。

    • ベストアンサー
    • Java
  • XMLの実体参照とXSLT

    次のようなXMLファイルをXSLTで処理したいのですが、&baseの部分がうまく表示できません。 どのようにしたら表示できるでしょうか? ■a.xml <?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="a.xsl"?> <!DOCTYPE test [ <!ELEMENT name (#PCDATA)> <!ENTITY base SYSTEM "base.xml"> ]> <test> <name>abc</name> &base; </test> ■base.xml <?xml version="1.0" encoding="UTF-8"?> <base>base</base> ■a.xsl <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="UTF-8" /> <xsl:template match="/"> <xsl:apply-templates select="test" /> <xsl:apply-templates select="base" /> </xsl:template> <xsl:template match="test"> name:<xsl:value-of select="name" /> </xsl:template> <xsl:template match="base"> base:<xsl:value-of select="base" /> </xsl:template> </xsl:stylesheet> □結果 name:abc ※base.xmlの内容を表示できない

    • ベストアンサー
    • XML
  • オブジェクト指向で、インターフェースにないメソッドを呼ぶ

    こんにちは。質問させてください。 現在C++でプログラムを書いているのですが、以下のようなプログラムを書いたとします(これ例ですので、実際のプログラムとは異なります)。 // ポインターを抽象化 class PointerInterface { }; // 抽象化したクラスを継承して整数型のポインタを作る class IntPointer : public PointerInterface { private:   // int型のポインタ   int *p; public:   int* get()   {     return p;   } }; // 抽象化したクラスを継承して少数型のポインタを作る class FloatPointer : public PointerInterface { private:   float *p; public:   float* get()   {     return p;   } }; PointerInterface* CreateInterface( bool flag ) {   PointerInterface* result = 0;   if( flag )   {     result = new IntPointer;   }   else   {     result = new FloatPointer;   }   return result; } void setIntPointer( PointerInterface* ptr ) {   // ここでint型のポインターを取り出して操作したい } void main() {   PointerInterface* ptr = CreateInterface( true );   setPointer( ptr ); } インターフェースは同じなのに継承先に内含されている変数のタイプが違い、それを取得したい場合が出てくると思います。 こういう場合どのように実装すれば、より美しくコーディングすることができるでしょうか? /* 私は 1:PointerInterfaceにvoid*を戻すメソッドを宣言する 2:setIntPointerの中で無理やりキャストして子クラスのgetを無理やり呼び出す という2つの方法が思いついたのですが、なんだかどっちのコーディングもピンときません。 お知恵を貸してください! ※今回はtemplateを使うというのは、なしでお願いします。 */

  • struts2のchainで値の受け渡しができない

    Java 6 Update 22 struts2.2.1 です。 次のとおり各クラスとstruts.xmlを作成しました。 Top_guestLoginアクションを実行してHelloUserクラスに遷移することはできましたが、変数isGuestModeの値が受け渡しされません。 public class Top implements SessionAware { private boolean isGuestMode = false; public boolean getIsGestMode() { return isGuestMode; } public String guestLogin() throws Exception { isGuestMode = true; return "success"; } } public class HelloUser { private boolean isGuestMode; public boolean getIsGuestMode() { return isGuestMode; } public void setIsGestMode(boolean b){ isGuestMode = b; } public String execute() throws Exception { return "success" ; } } struts.xml <package name="example" extends="struts-default"> <action name="HelloUser" class="example.HelloUser"> <result>/example/HelloUser.jsp</result> </action> <action name="Top_guestLogin" method="guestLogin" class="example.Top" > <result name="success" type="chain" >HelloUser</result> </action> </package> 何方かアドバイスをお願い致します。

    • ベストアンサー
    • Java
  • Androidの非同期処理でネットワークエラー発生

    Androidから、AsyncTaskを使用してURLにアクセスし、 外部DBのMySQLから引っ張ってきたデータをXML形式に変換したものを 取得したいと思っています。 一通り処理を作成し、実験的にデータを取得してみたのですが、 問題なく取得できるものと、途中でNetworkOnMainThreadExceptionが発生するものが 出てきてしまいました。 具体的には、下記のようなテーブルのデータを全カラム分取得し、 XML形式に変換しています。 【取得に成功したテーブル】 カラム数:3 内訳:INT型→1カラム、VARCHAR型→2カラム 一度に取得するデータ数:7行 【取得に失敗するテーブル】 カラム数:22 内訳:INT型→7カラム、CARCHAR型→12カラム、    TEXT型→1カラム、DATETIME型→2カラム 一度に取得するデータ数:最大10行 NetworkOnMainThreadExceptionが発生するタイミングは、 XMLデータを取得したタイミングではなく、 取得したXMLをデータ格納用オブジェクトに入れて ArrayListに蓄積しているタイミングです。 データが取得できているテーブルもあるので、 AsyncTaskの使い方が間違っているわけではないと思うのですが NetworkOnMainThreadExceptionが発生する原因を調べてみても、 AsyncTaskを使っていないから、というものしか出てこないので、 原因がさっぱりわかりません。 AsyncTaskの使い方で何か間違っている箇所があるのか、 そもそもAsyncTaskで取得させるデータ量に制限があるのか、 何かの指定をすればうまくできるものなのか、 何か思い当たることがあれば、教えていただきたいと思っています。 【AsyncTaskをextendsした自作クラス】 public class AsyncXmlLoader extends AsyncTask<String, Integer, InputStream> { public interface AsyncCallback { void preExecute(); void postExecute(InputStream result); void progressUpdate(int progress); void cancel(); } private AsyncCallback asyncCallback = null; public AsyncXmlLoader(AsyncCallback asyncCallback) { this.asyncCallback = asyncCallback; } @Override protected void onPreExecute() { super.onPreExecute(); asyncCallback.preExecute(); } @Override protected void onProgressUpdate(Integer... progress) { super.onProgressUpdate(progress); asyncCallback.progressUpdate(progress[0]); } @Override protected void onPostExecute(InputStream result) { super.onPostExecute(result); asyncCallback.postExecute(result); } @Override protected void onCancelled() { super.onCancelled(); asyncCallback.cancel(); } @Override protected InputStream doInBackground(String... uri) { InputStream inputStream = null; try { // URLクラスのインスタンス作成 URL url = new URL(uri[0]); // コネクション接続 URLConnection connection = url.openConnection(); // ストリームを取得 inputStream = connection.getInputStream(); } catch (MalformedURLException e) { Log.e("AsyncXmlLoader", e); } catch (IOException e) { Log.e("AsyncXmlLoader", e); } catch (Exception e) { Log.e("AsyncXmlLoader", e); } return inputStream; } } 【実際にデータを取得している箇所】 public class MainActivity extends ActionBarActivity { private final String URL = "http://hoge.jp/test.php"; private TextView testTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // TextViewを取得 testTextView = (TextView)findViewById(R.id.textViewTest); // XMLからカテゴリデータを取得 AsyncXmlLoader xml = new AsyncXmlLoader(new AsyncXmlLoader.AsyncCallback() { // 実行前 public void preExecute() { } // 実行後 public void postExecute(InputStream result) { if (result == null) { Log.e("AsyncCallback", "データ取得失敗"); return; } // XMLのデータを取得して画面に表示 BufferedReader br = new BufferedReader(new InputStreamReader(result)); StringBuilder sb = new StringBuilder(); String line; try { while ((line = br.readLine()) != null) { sb.append(line); } } catch (IOException e) { Log.e("AsyncCallback", e); } testTextView.setText(sb.toString()); } // 実行中 public void progressUpdate(int progress) { } // キャンセル public void cancel() { } }); // 処理を実行 xml.execute(URL); } } お知恵を貸していただきたいと思います。 どうぞよろしくお願いいたします。