19
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ガベージコレクションを意識してメモリリークを避ける

Last updated at Posted at 2020-03-31

メモリリークを避ける為に、ガベージコレクションが機能する時としない時を自分の知る限り列挙してみました。

#ガベージコレクションについて
オブジェクトがどの変数からも扱われなくなった場合にJavaの判断によって自動でオブジェクトが破棄される機能のこと。
「finalize()メソッド」がオブジェクトを破棄しているのだが、Javaの場合はこのメソッドを定義してもタイミングを管理することができない。

##ガベージコレクションが機能するケース

単純な処理で確かめたので、メモリリークしやすいようにヒープ領域を下げて実行してます。
(2Mでメモリリークするように)
ヒープの初期サイズ -Xms1m
ヒープの最大サイズ -Xmx2m

###①オブジェクトを参照している変数がすべてnullになった時
『変数にnullを代入する=変数はどのオブジェクトも表さなくなる』なので、あるオブジェクトを表している変数が全てnullになった場合に機能します。
nullを代入=「これもう使わないです」と言ってるようなものなので、ガベージコレクションさんが不要と判断してくれます。

public class AddList01 {
	public static void main(String[] args) {
		// 処理1
		List<String> list1 = new ArrayList<>();
		addList(list1);
		list1 = null;
		// 処理2
		List<String> list2 = new ArrayList<>();
		addList(list2);
		list2 = null;
	}

	private static void addList(List<String> list) {
		for (int i = 0; i < 100000; i++) {
			list.add("a");
		}
	}
}

###②オブジェクトがもう扱われなくなった時
オブジェクトを扱っている変数の記載が以降なくなった時にガベージコレクションが機能します。
「ソース全体見たけど、これ以降扱わなくなってるし中身捨てて良いよね。」って感じでガベージコレクションさんが勝手に判断してくれます。

public class AddList02 {
	public static void main(String[] args) {
		List<String> list1 = new ArrayList<>();
		List<String> list2 = new ArrayList<>();

		// 処理1
		for (int i = 0; i < 100000; i++) {
			list1.add("a");
		}

		// 処理2
		for (int i = 0; i < 100000; i++) {
			list2.add("b");
		}
	}
}

###③ループやtry-catch内のローカル変数
ループ内で宣言、インスタンス生成された等、ループ内で完結オブジェクトはループを抜ける時にガベージコレクションが機能してくれる。
ループ内で宣言した変数はループ外で扱うことができないからである。

public class AddList03 {
	public static void main(String[] args) {
		try {
			// 処理1
			for (int i = 0; i < 10; i++) {
				List<String> list1 = new ArrayList<>();
				for (int j = 0; j < 100000; j++) {
					list1.add("a");
				}
			}

			// 処理2
			List<String> list2 = new ArrayList<>();
			for (int i = 0; i < 10; i++) {
				list2 = new ArrayList<>();
				for (int j = 0; j < 100000; j++) {
					list2.add("b");
				}
			}

			// 処理3
			int i = 0;
			while (i < 10) {
				List<String> list3 = new ArrayList<>();
				for (int j = 0; j < 100000; j++) {
					list3.add("c");
				}
				i++;
			}

			// 処理4
			List<String> list4 = new ArrayList<>();
			for (int j = 0; j < 100000; j++) {
				list4.add("d");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		// 処理5
		List<String> list5 = new ArrayList<>();
		for (int j = 0; j < 100000; j++) {
			list5.add("e");
		}
	}
}

ちなみに変数「list2」をループ後に扱っていた場合は最後のループのインスタンスのみを残す。

public class AddList031 {
	public static void main(String[] args) {
		List<String> list2 = new ArrayList<>();
		for (int i = 0; i < 10; i++) {
			list2 = new ArrayList<>();
			for (int j = 0; j < 50000; j++) {
				list2.add("b");
			}
		}
		System.out.println(String.join("", list2));
	}
}

###④メソッドのローカル変数
③のループと同じ原理です。ただし、引数で参照している値や戻り値などは残る。

public class AddList04 {
	public static void main(String[] args) {
		addList();
		addList();
		addList();
	}

	private static void addList() {
		List<String> list = new ArrayList<>();
		for (int j = 0; j < 100000; j++) {
			list.add("a");
		}
	}
}

##ガベージコレクションが機能しないケース

###①メインメソッドでインスタンスを保持して別メソッドでオブジェクトを参照する。
機能するケース②では扱われなくなったオブジェクトは破棄されていましたが、別メソッドでオブジェクト参照するとガベージコレクションは機能しなくなる。
これどうしてなんでしょう?

public class AddList05 {
	public static void main(String[] args) {
		// 処理1
		List<String> list1 = new ArrayList<>();
		addList(list1);

		// 処理2
		List<String> list2 = new ArrayList<>();
		addList(list2);
	}

	private static void addList(List<String> list) {
		for (int i = 0; i < 100000; i++) {
			list.add("a");
		}
	}
}

###②リストのインスタンスをフィールドで生成している
クラスのいろんな場所で使われるので、引数が多くならないようにフィールドに記載してしまうケース。
リストのスコープが広すぎてガベージコレクションが不要と判断してくれないようです。

public class AddList06 {
	private List<String> list1 = new ArrayList<>();
	private List<String> list2 = new ArrayList<>();

	public static void main(String[] args) {
		AddList06 addList06 = new AddList06();

		// 処理1
		for (int i = 0; i < 100000; i++) {
			addList06.list1.add("a");
		}

		// 処理2
		for (int i = 0; i < 100000; i++) {
			addList06.list2.add("a");
		}
	}
}

###③メソッドの戻り値で受け取ってきたもの
mainメソッドでインスタンスを生成していなくても、呼び出したメソッド先でインスタンスを生成し戻り値で受け取ったものは残留してしまう。

public class AddList07 {
	public static void main(String[] args) {
		// 処理1
		List<String> list = addList();

		// 処理2
		list = addList();

		// 処理3
		list = addList();
	}

	private static List<String> addList() {
		List<String> list = new ArrayList<>();
		for (int i = 0; i < 50000; i++) {
			list.add("a");
		}
		return list;
	}
}

###④nullを代入するのではなく別の方法で使わないようにしている。
使用しているリストで使わなくなった値をリストのメソッドを使って削除しても、nullにしている訳ではないのでオブジェクトは参照されているままのようです。

public class AddList08 {
	public static void main(String[] args) {
		// 処理1
		List<String> list1 = new ArrayList<>();
		addList(list1);
		list1.clear();

		// 処理2
		List<String> list2 = new ArrayList<>();
		addList(list2);
		for (int i = 0; i < list2.size(); i++) {
			list2.remove(i);
		}

		// 処理3
		List<String> list3 = new ArrayList<>();
		addList(list3);
	}

	private static void addList(List<String> list) {
		for (int i = 0; i < 50000; i++) {
			list.add("a");
		}
	}
}

#まとめ
そんな単純じゃないよって思うかもですがとりあえず
・「なるべく処理をメソッドで細かく分けてローカルで持つべし」
・「無駄に変数やインスタンスを作らず使いまわせるところは使いまわすべし」
・「フィールドでの変数宣言は控えろ」
と言うのを感じました。

あとはDBからSQL取得、登録など行う処理がある場合は処理目的ごとに分けることでしょうか。
SQLが複数あると時間がかかるかもですが処理が落ちるよりはましかと思いました。

19
17
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
19
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?