【Java進化史 第10回】Java8 〜あのPermGenはどこへ消えた〜
昔、意味も分からず書いていたオプションがある。
-XX:MaxPermSize=256m
足りなければ増やす。
512m。
1024m。
それでも、死ぬ。
java.lang.OutOfMemoryError: PermGen space
あの文字列を、何度見ただろう。
■ PermGenとは何だったのか
Java7まで、JVMのメモリは大きくこう分かれていた。
Heap
├─ Young領域
├─ Old領域
└─ PermGen(Permanent Generation)
PermGenはヒープの一部で、主に
- クラスのメタ情報
- メソッド情報
- 静的変数
- (Java6までは)文字列定数プール
を保持していた。
そして最大の問題は、 サイズが固定だったことだ。
ヒープのように自動で伸びない。
だから溢れる。
■ Stringを多用するな、と言われた時代
あの頃、よく言われた。
「Stringを多用するな」
理由は曖昧だった。
だが確かにサーバはよく死んだ。
これは半分本当だ。
Java6までは、文字列定数プールがPermGenにあった。
大量の文字列リテラル、internの多用、
フレームワークが内部で生成する文字列。
それらがPermGenを圧迫することがあった。
だから、
「Stringがヤバい」
という空気は、間違いではなかった。
■ でも本質はStringそのものではない
問題は設計だった。
固定サイズのPermGenに、 クラス情報と文字列プールを押し込んでいたこと。
さらに、Stringは不変(immutable)。
String s = "";
for (int i = 0; i < 10000; i++) {
s += i;
}
このコードは、大量の中間Stringを生む。
だから我々は学んだ。
「StringBuilderを使え」
間違ってはいない。
だが根本原因はJVM側の設計にもあった。
■ GCパラメータ職人の時代
最後は、GCと戦っていた。
レスポンスが急に悪くなる。
CPUが跳ね上がる。
サーバが固まる。
Full GC。
Stop The World。
ログを吐かせる。
-XX:+PrintGCDetails
-XX:+UseConcMarkSweepGC
-XX:NewRatio=2
-XX:SurvivorRatio=8
意味を完全に理解していたか?
正直、怪しい。
だが、体で覚えていた。
パラメータを調整し、 再起動し、 負荷試験をし、 延命する。
今思えば、 あれは完全に職人技だった。
■ Java7で起きた静かな改善
Java7で文字列プールはヒープへ移動した。
PermGenに直接効くString問題は緩和された。
だが、PermGenそのものはまだ存在していた。
■ Java8でPermGenは消えた
Java8でPermGenは廃止された。
代わりに導入されたのが Metaspace。
- ヒープ外(ネイティブメモリ)を使用
- 原則、動的に拡張
固定枠で突然死ぬ設計ではなくなった。
さらにGCも成熟した。
「あれ?最近安定してない?」
それは気のせいではない。
- PermGen廃止
- Metaspaceの動的拡張
- GCアルゴリズムの成熟
足元が、ちゃんと進化していた。
■ あの頃の自分へ
-XX:MaxPermSizeを書き、
Stringを疑い、
GCログを読み、
サーバの負荷に怯えていた自分へ。
PermGenは、もういない。
Java8は文法革命だけではない。
JVMの土台も、ちゃんと進化していた。
少しだけ、戦わなくてよくなった。
ほんと、助かるよ。