Category Archives: Java

JVM Operation Casual Talks に参加してきました。

JVM Operation Casual Talks : ATND に参加してきました。

会場はLINE株式会社渋谷オフィス、ヒカリエの27階です。中も景色も綺麗ですね。

 

「カジュアルトーク」なんて言うものだから、きっと、どこかの「基礎勉強会」のような怖い勉強会であることを覚悟して行きました。僕みたいな現場で場当たり的にやってるエンジニアは刺されて来るのではないかと((((;゚Д゚))))ガクガクブルブル

 

コンテンツは

@stanaka15分でわかるJVMのメモリ管理

@oranieCassandra運用で実施しているJVM監視について

@waysakuSpray(Akka)運用でJVMをCPUスケールさせるために行った事

その後LTでした。

 

中でも、最初に発表された@stanakaさんのスライドが非常に網羅的に説明されていますので、JVMに関わる可能性がある方は一度目を通す価値があると思います。

 

さて、僕は、自分がJavaプログラマーとして開発したWebアプリケーションを自社のインフラチームと協力して、時にはGCオプションやインフラの調整を依頼し、時にはアプリのコード改修などを行える立場でJVMと接しています。一方、ここで発表された方々は、他人が作ったJVM上で動くアプリケーションを運用する立場の人たちでした。そういう意味ではうちのインフラチームの人たちに近い立場の方々ですが、自分でソースをいじることが容易ではない状況下での運用の苦しさもあるでしょう。

 

内容は意外と業務を通じて関わってきたことだったので、刺されるというよりは、みんな同じように苦労してるんだなと共感することが多かったです。

 

ちなみに、現時点の僕のうとい理解ではこんな感じです。

G1GC

トータルのGC時間は多く、一回あたりのGC時間が短い

6G、あるいは10GB以上のヒープサイズを割り当てている時に効果がある

Webなどの無停止で運用したいサービスで、メモリサイズが贅沢に割り当てられるときに有効

CMS

トータルGC時間が短い

コンパクションが無いため断片化する。長く無停止運用をすると遅くなる

バッチなど、トータルスループットが求められるときに有効

JVMオプションでのチューニングが必要(nekopさんのエントリを見るべし!)

 

ラストのパネルディスカッションのコーナーでは、なぜJVMを使うのかの問に対して、パネラーの皆さんは「使いたいアプリ(Hadoop、Solr、Cassandra等)がJVM上で動くから」とおっしゃっていました。特にPerlなどのLL出身の人には、GCでStop the Worldしたり、気軽に落とせないということが不満らしく、その反論として落ちないことが良い、という意見がありました。それに対し、いつ落ちてもいいように環境を構築するべきなど色々議論が深まりました。

 

僕にとっては、Javaでしか業務経験がなく、JVMを選択する理由としては「JVMが一番知っているから」というそれこそウルトラ消極的な理由しかありません。今回は、別のバックグラウンドがある人達がJVMに触れた感想を聞くことができたことは非常に有意義でした。

 

ちなみに、この発表の最中僕は下記の間違ったツイートをしたので訂正します。

既に削除していますが、Togetterに残ってしまっているのでごめんなさい。

PalallelGG => Compactionしないので時間が経つと遅くなる #jvmcasual そうなんだよね。

正しくはConcurrent Mark Sweep (CMS)の説明です。「そうなんだよね」とか知ったかもいい加減にです。orz

これは、うちのインフラチームの方がこの辺りチューニングして色々苦労していた話を聞いたので、このことかなと思ったのですが、後になって間違いに気づいた時には既にTogetterにまとめられていたので遅かったです。

 

写真

f:id:s-ishigami:20140408100444j:plain

ヒカリエオフィスエリアのエントランス(広っ!)

 

 

f:id:s-ishigami:20140408100210j:plain

 

 

 

27階景色いい

f:id:s-ishigami:20140408100408j:plain

 

 

入館証もLINE!退館時に返さないとこの会場が使えなくなるから!と言ってました。

f:id:s-ishigami:20140408100411j:plain

クリアファイル配ってました。

 

 

 

 

sun.misc.Unsafeを使う

コンストラクタを呼ばないでインスタンスを生成してリフレクションで横から強制的に初期化という行為が必要だった。sun.misc.UnsafeというAPIを使用するとできるらしいのでやってみた。あっけなく出来てしまった。まずは今回テストしたコードを下記に貼り付けます。

import java.lang.reflect.Field;
import sun.misc.Unsafe;

public class UnsafeTest {
    public UnsafeTest() {
        throw new RuntimeException("can't instantiate this class");
    }

    private String name = "";

    public void hello() {
        System.out.println("hello, " + name);
    }

    public static void main(String[] args) {
        Unsafe unsafe = getUnsafe();
        try {
            while (true) {
                UnsafeTest instance = (UnsafeTest) unsafe.allocateInstance(UnsafeTest.class);
                instance.name = "hoge";
                instance.hello();
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    private static Unsafe getUnsafe() {
        try {
            Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
            singleoneInstanceField.setAccessible(true);
            return (Unsafe) singleoneInstanceField.get(null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

 

実行すると延々と、"hello, hoge"と出力されます。

このクラスはコンストラクタを呼ぶと例外を飛ばす酷いやつですが、ちゃんとインスタンス化できていることがわかります。

 

コードUnsafeインスタンスの取得がリフレクション経由になっていますが、どうやら、この取り方が自分が調べた限り主流のようです。

理由は

  • Unsafeオブジェクトはprivateコンストラクタなのでインスタンス化できない
  • Unsafe.getUnsafe()メソッドはセキュリティ制御されていて、trustedなクラスからしか使用できない
  • けど、そいつのインスタンスはUnsafeクラスのstaticインスタンス theUnsafe に格納されているから、リフレクションで「盗む」ことができる

ということみたいです。

詳しくは http://mishadoff.github.io/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/

 

無限ループにしたのは、Unsafeという名前にビビったので、まさかメモリ管理されないとかないか、調べるためです。

実行させたまま、jvisualvmででヒープ情報を見たところ右肩上がりにはなっていないことがわかります。

結果は次の通り

なお、この結果は環境に依存するものですので、他の方の環境では同じ結果になるとは限りませんのでご了承下さい。

 

Java7

f:id:s-ishigami:20140331102547p:plain

 

Java8

f:id:s-ishigami:20140331103427p:plain

Java5

f:id:s-ishigami:20140331100738p:plain

念の為に、heap dumpも取っていましたがUnsafeTestのインスタンス数は0となっていましたので、確かにGCされているようです。

 

 

64bit環境でなんかheapを多く消費するなあと思ったら

かいつまんで話すと、100KBくらいのXMLデータをモデル化したデータ*1がJavaのheap上で10MBになった!なぜか!という話です。

DOMのような構造データは要するにこんなデータ構造を持っていれば良いです。


class Node { Map<String, String> attribute; QName qName; } class QName { URI uri; String tagName; }

URIは適切にキャッシュを共有する必要があります*2が、そこさえ気をつければ、必要な実データは要するに元のXMLテキストデータとあまり変わらない。改行コードとインデントがなくなるのでむしろ軽くなるはずです!

となると、オーバーヘッドはオブジェクトの管理データだと予想しました。
https://www.ibm.com/developerworks/jp/java/library/j-codetoheap/
を読んだ程度の理解ですが、64bitモード下でのJavaのオブジェクトは結構大きなデータなのですね。100byte以上とはでかいですね。そこで、HotspotVMには圧縮OOPというものが搭載されていて、
http://docs.oracle.com/javase/jp/7/technotes/guides/vm/performance-enhancements-7.html
>> 6u23 リリースより前の JDK 6 でこの機能を有効にするには、java コマンドで -XX:+UseCompressedOops フラグを使用します。
とのことです。つまり、

  • 32bit VMで十分なメモリの量だったら、32bit VMを使ったほうが有利(〜2GBくらいか?)
  • 64bitが必要で、Java 6u13以前だったら、VMのバージョンアップを検討すべき
  • 64bitが必要で、Java 6u14以上6u22以前だったら、-XX:+UseCompressedOops フラグを使用か、VMのバージョンアップを検討すべき

というか、GCも進化してるし、Java VMは最新を使おう!

*1:正確に言えば、MayaaのSpecificationCacheのことですが直接関係ないのでここでは深く突っ込ません

*2:そうしないと1タグ毎に "http://www.w3.org/1999/xhtml" とかを保持することになる