- PR -

スレッドとメモリについて

投稿者投稿内容
taka
常連さん
会議室デビュー日: 2003/09/22
投稿数: 46
投稿日時: 2004-03-11 16:00
いつもお世話になっております。

早速ですが、下記のスレッドを実行すると使用メモリが異常に上がります。
コード:

public void run(){
String str1 = "";
String str2 = "";
while (flgThread) {
test();
}
System.out.println("thread stop");
}

void test(){
ArrayList aryList = new ArrayList();
for(int i = 0; i < 8; i++){ aryList.add(String.valueOf(i)); }
String str = String.valueOf(aryList.get(0));
aryList.clear();
}



例えば"run()"メソッドのWhile文の中で"test()"メソッドを実行しない場合は
メモリが異常に増える事はありませんでした。
そこで原因は"test()"メソッドのArrayListにあると考えました。
しかし原因がわかりません。
"test()"メソッド内部でNewしているのだから、"test()"メソッドが終了した時点で
NewしたArryaListは解放されるのですよね?

環境は
OS= Windows2000
開発ツール = JBuilder8
JDK = 1.2




[ メッセージ編集済み 編集者: taka 編集日時 2004-03-11 16:02 ]
がるがる
ぬし
会議室デビュー日: 2002/04/12
投稿数: 873
投稿日時: 2004-03-11 16:25
どもも。がるともうします。
Javaはそこまで詳しくないのですが。
引用:

takaさんの書き込み (2004-03-11 16:00) より:
"test()"メソッド内部でNewしているのだから、"test()"メソッドが終了した時点で
NewしたArryaListは解放されるのですよね?


とは限らないですね。
この辺の、いわゆる「delete演算子のないタイプの言語」の開放のタイミングは
2フェーズから成っていて
・開放が「可能になる」
・実際に開放する
です。
で、メソッド終了時はあくまで「開放が可能になる」だけであり、
即時開放になるかどうかは別になります。
っていうか、通常適当なタイムラグは空くことのほうが多いです。

っと、ここまでがわりとベーシックな見方かと思います。
以下、推測というか妄想というか。

コードを見ていると、test()という関数をループしてCallしてますが。
もしJavaが
・test関数が終了するときに、内部で取得したメモリの参照評価を行う
のであれば問題がないのですが、
・適当な時間軸で「test関数が生きているかどうか」を基準に参照評価を
 行う
ようなパターンの場合、ほとんどのタイミングで
・test関数自体は生きている
ために、メモリの開放は「まだ」という判断を下す可能性があります。
この辺はどんなもんなんでしょうかねぇ? > Javaに詳しい諸氏

ガベージコレクションで泣いているケースは結構あちこちで見ているので。
場合によっては、何らかの「メモリを節約するコード」を書いておいたほうが
よい…のかもしれません :-P
ぽん
大ベテラン
会議室デビュー日: 2003/05/13
投稿数: 157
投稿日時: 2004-03-11 16:28
引用:

takaさんの書き込み (2004-03-11 16:00) より:

"test()"メソッド内部でNewしているのだから、"test()"メソッドが終了した時点で
NewしたArryaListは解放されるのですよね?


解放の対象になるだけです、解放のタイミングはVMが決めます。

[追記]
かぶった・・・がるがるさんの投稿をお読み下さい


[ メッセージ編集済み 編集者: ぽん 編集日時 2004-03-11 16:31 ]
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2004-03-11 16:58
unibon です。こんにちわ。

試しに、つぎのようにかなり変形して動かしましたが、使用メモリはさほど増加しませんでした。
コード:
import java.util.*;

public class Hoge {

    public static void test() {
        ArrayList aryList = new ArrayList();
        for (int i = 0; i < 8; i++) {
            aryList.add(String.valueOf(i));
        }
        String str = String.valueOf(aryList.get(0));
        aryList.clear();
    }

    public static void main(String[] args) {
        String str1 = "";
        String str2 = "";
        while (true) {
            test();
        }
    }
}


ちなみに実行はバージョン 1.4.2 で、Windows XP 上で、
java -verbose:gc Hoge
のような感じです。出力はほぼ、
[GC 601K->89K(1984K), 0.0003092 secs]
となって安定します。

引用:

takaさんの書き込み (2004-03-11 16:00) より:
環境は
OS= Windows2000
開発ツール = JBuilder8
JDK = 1.2


バージョンが 1.2 であることに依存している?
あるいは明示的に new Thread() のようなことをしないと再現しない?
佐々木
大ベテラン
会議室デビュー日: 2003/03/30
投稿数: 121
投稿日時: 2004-03-11 17:12
待ったなしでブンブンループを回してるから、ガベージコレクションのスレッドがあまり動けていないんじゃないですか?
taka
常連さん
会議室デビュー日: 2003/09/22
投稿数: 46
投稿日時: 2004-03-11 17:15
がるがるさん、ぽんさん、unibonさん、貴重なご意見ありがとうございました。

がるがるさん
>何らかの「メモリを節約するコード」を書いておいたほうが
>よい…のかもしれません
とは?どのようなコードを書くとメモリの節約ができるのですか?

unibonさん
>バージョンが 1.2 であることに依存している?
うーん・・・ あまり聞いた事がないのですが、そういった可能性もあるのですね・・・


ところで、"test()"メソッドを下記のように書き直した結果メモリの異常な上がりはありませんでした。
aryListはメソッドの外でprivate定義しました。
"For"文が原因?!
コード:

 void test(){
aryList.clear();
aryList.add("0");
aryList.add("1");
aryList.add("2");
aryList.add("3");
aryList.add("4");
aryList.add("5");
aryList.add("6");
aryList.add("7");
//ArrayList aryList = new ArrayList();
//for(int i = 0; i < 8; i++){ aryList.add(String.valueOf(i)); }
//String str = String.valueOf(aryList.get(0));
//aryList.clear();
}






[ メッセージ編集済み 編集者: taka 編集日時 2004-03-11 17:29 ]
山本 裕介
ぬし
会議室デビュー日: 2003/05/22
投稿数: 2415
お住まい・勤務地: 恵比寿
投稿日時: 2004-03-11 17:31
処理内容ではなく、単純にスレッドを使ってるからヒープを食べてるだけではないでしょうか?
がるがる
ぬし
会議室デビュー日: 2002/04/12
投稿数: 873
投稿日時: 2004-03-11 18:06
ども、がるです。

引用:

takaさんの書き込み (2004-03-11 17:15) より:
がるがるさん
>何らかの「メモリを節約するコード」を書いておいたほうが
>よい…のかもしれません
とは?どのようなコードを書くとメモリの節約ができるのですか?


まぁ手法は色々あるのですが。takaさんが書かれている「Private定義」もまた、
メモリ節約の方法の一つですね。

…と、ここで終わると読んでいる他の人で「え???」って人もあるいは
いるかもしれないので、もうちょっと細かく砕いてみます。
コード:

ArrayList aryList = new ArrayList();
for(int i = 0; i < 8; i++){ aryList.add(String.valueOf(i)); }
String str = String.valueOf(aryList.get(0));
aryList.clear();


において、必要な領域は「つねに新しいものを確保する」ロジックになっています。
そうですね。例えば「newしてデータをaddしていくのに必要なaryList」のメモリの
量を1kバイトだと仮定してみましょう。
既存のロジックの場合、例えばtestが10回回ると、1k * 10回で、10kのメモリが
必要になります。また、メモリの確保自体がそんなに軽い作業ではないのと、new
という「インスタンスを生成する」作業は意外と重かったりするときがあります。

一方で
コード:

aryList.clear();
aryList.add("0");
aryList.add("1");
aryList.add("2");
aryList.add("3");
aryList.add("4");
aryList.add("5");
aryList.add("6");
aryList.add("7");


の場合、少なくとも「メモリは一回確保したものを使いまわす」うえで「newが
一回しか走らない」ために、特にtest()をぶん回す回数が多ければ多いほど、
作業に必要なコストに開きが出てきます。

大雑把な数式で書くと、双方には
 a(n-1) + c*b(n-1)
 ただし
  a = (new ArrayList())にかかる時間またはコスト
  b = データ一つをaddするさいに必要なメモリを確保するための時間またはコスト
  c = 一回のtest()でデータをaddする回数
  n = test()をループでぶん回す回数
分の時間、またはコストの差が生じます。nが大きければ大きいほど、差が
顕著になるのがなんとなく予想できるかなぁ、と思います。
# 最適化がない、と仮定した場合の計算式ですが :-P

このあたりは「とある命令が、実際にはコンピュータの中でどのようなCPU命令に
なってどのようなメモリ確保が行われるのか?」をきちんと意識しておいたほうが
よい好例です。
まぁ結構大変だと思うのですが ^^;
ある程度以上大きなもの、重いものを扱うときには重要な考え方だと思います。


[ メッセージ編集済み 編集者: がるがる 編集日時 2004-03-11 18:07 ]

スキルアップ/キャリアアップ(JOB@IT)