#Javaのオブジェクト生成とメモリ管理について
社会人1年目のSEです。
文系出身で分からないなりに日々Javaについて勉強しています。
先日、OOMEが出た時に初めてJVMのメモリ管理について意識するようになりました。
原因はループ内でのオブジェクト大量生成だったのですが、オブジェクトの大量生成がどれほど負荷がかかるのか半信半疑だったので、検証してみます。
##検証方法
百万回ループさせる中で、StringBuilderを新しく作るか使いまわすかで同じ処理をさせ、JVM引数に
-verbose:gc
-XX:+PrintGCDetails
を指定して、GCとヒープ領域の様子を見ます。
1.オブジェクトをループ毎にインスタンス化する場合
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (long i = 1; i < 1000000; i++) {
StringBuilder stash = new StringBuilder();
stash.append("text1");
stash.append("text2");
stash.append("text3");
stash.append("text4");
stash.append("text5");
}
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
結果
[GC [PSYoungGen: 16384K->533K(18944K)] 16384K->533K(60928K), 0.0024591 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 16917K->485K(18944K)] 16917K->485K(60928K), 0.0018063 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 16869K->485K(18944K)] 16869K->485K(60928K), 0.0019565 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 16869K->501K(35328K)] 16869K->501K(77312K), 0.0014947 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 33269K->517K(35328K)] 33269K->517K(77312K), 0.0015976 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 33285K->501K(66560K)] 33285K->501K(108544K), 0.0016127 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
209ms
Heap
PSYoungGen total 66560K, used 29444K [0x00000000eb600000, 0x00000000ef800000, 0x0000000100000000)
eden space 65536K, 44% used [0x00000000eb600000,0x00000000ed243a50,0x00000000ef600000)
from space 1024K, 48% used [0x00000000ef700000,0x00000000ef77d630,0x00000000ef800000)
to space 1024K, 0% used [0x00000000ef600000,0x00000000ef600000,0x00000000ef700000)
ParOldGen total 41984K, used 0K [0x00000000c2200000, 0x00000000c4b00000, 0x00000000eb600000)
object space 41984K, 0% used [0x00000000c2200000,0x00000000c2200000,0x00000000c4b00000)
PSPermGen total 21504K, used 2618K [0x00000000bd000000, 0x00000000be500000, 0x00000000c2200000)
object space 21504K, 12% used [0x00000000bd000000,0x00000000bd28ebd8,0x00000000be500000)
2.一つのオブジェクトを使いまわす場合
public static void main(String[] args) {
long start = System.currentTimeMillis();
StringBuilder stash = new StringBuilder();
for (long i = 1; i < 1000000; i++) {
stash.append("text1");
stash.append("text2");
stash.append("text3");
stash.append("text4");
stash.append("text5");
stash.setLength(0);
}
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
結果
60ms
Heap
PSYoungGen total 18944K, used 1359K [0x00000000eb600000, 0x00000000ecb00000, 0x0000000100000000)
eden space 16384K, 8% used [0x00000000eb600000,0x00000000eb753c70,0x00000000ec600000)
from space 2560K, 0% used [0x00000000ec880000,0x00000000ec880000,0x00000000ecb00000)
to space 2560K, 0% used [0x00000000ec600000,0x00000000ec600000,0x00000000ec880000)
ParOldGen total 41984K, used 0K [0x00000000c2200000, 0x00000000c4b00000, 0x00000000eb600000)
object space 41984K, 0% used [0x00000000c2200000,0x00000000c2200000,0x00000000c4b00000)
PSPermGen total 21504K, used 2616K [0x00000000bd000000, 0x00000000be500000, 0x00000000c2200000)
object space 21504K, 12% used [0x00000000bd000000,0x00000000bd28e0f8,0x00000000be500000)
結果、使いまわしたほうはGCが発生しないし、その分処理も高速である。
##privateメソッドに切り出すと?
メソッドを切り出すと、参照喪失のタイミングがループブロック単位ではなくメソッドのブロック単位になる。
が、メモリ効率的には毎回インスタンスしているのと変わらないはず。
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (long i = 1; i < 1000000; i++) {
makeSentence();
}
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
public static void makeSentence() {
StringBuilder stash = new StringBuilder();
stash.append("text1");
stash.append("text2");
stash.append("text3");
stash.append("text4");
stash.append("text5");
}
結果
[GC [PSYoungGen: 16384K->517K(18944K)] 16384K->517K(60928K), 0.0020680 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 16901K->592K(18944K)] 16901K->592K(60928K), 0.0022169 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 16976K->517K(18944K)] 16976K->517K(60928K), 0.0014909 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 16901K->453K(35328K)] 16901K->453K(77312K), 0.0020386 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 33221K->501K(35328K)] 33221K->501K(77312K), 0.0015145 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 33269K->453K(66560K)] 33269K->453K(108544K), 0.0013017 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
202ms
Heap
PSYoungGen total 66560K, used 29396K [0x00000000eb600000, 0x00000000ef800000, 0x0000000100000000)
eden space 65536K, 44% used [0x00000000eb600000,0x00000000ed243a50,0x00000000ef600000)
from space 1024K, 44% used [0x00000000ef700000,0x00000000ef771620,0x00000000ef800000)
to space 1024K, 0% used [0x00000000ef600000,0x00000000ef600000,0x00000000ef700000)
ParOldGen total 41984K, used 0K [0x00000000c2200000, 0x00000000c4b00000, 0x00000000eb600000)
object space 41984K, 0% used [0x00000000c2200000,0x00000000c2200000,0x00000000c4b00000)
PSPermGen total 21504K, used 2619K [0x00000000bd000000, 0x00000000be500000, 0x00000000c2200000)
object space 21504K, 12% used [0x00000000bd000000,0x00000000bd28ed98,0x00000000be500000)
結果、毎回インスタンスするのとほぼ同じ結果。
##結論?
ループ内ではインスタンス生成をさせることはもちろん、インスタンス生成を含むprivateメソッドの使用も避けたほうが良い?
でもそれでメソッド切り出しをしないのも変な気がする。
参照渡しで実装するのでしょうか?