gradle daemonを無効にする

javaのバックグラウンドを長く持つ自分にとって最近のお気に入りの言語はgroovyです。

もともと自分は言語の習得にコストを掛けたくなく、新しい言語が少なからず生み出される現状に少なからず嫌悪感を持っているところがなくはないです。
歴史的な経緯で高級言語が生み出されるようなことは好ましいのですが、同時代に構文だけが異なる似たような言語や、今ままでの低級言語のAPIから何故かひどく乖離した構文を提供する言語は疎ましくも思います。

もともとjavaは広く普及していたので自然と使わざるを得なかったのですが、広く使われているだけあってかなり洗礼されています。
javaはやはりLLと比較した時に型付き言語であるためコンパイル時点でかなり品質を担保できます。
またそれでいてCやC++などのレガシーな言語ほどコストを掛けずに開発できることから自分の中では攻撃力/防御力ともに優れた言語、と言った印象でした。
もっというと優れたIDEがあるのがよいですね。

なんですが欲を言うとjavaはその防御力の高さや歴史的には比較的古くから生み出された故にそのAPIに一部不満がある点もなくはないのです。
java8などの新しいAPIでは新しい流れをかなり汲み取っているようで不満があるかというとそういうわけでもないですが、いかんせん他の流れが早すぎるのだと思います。
新しい言語と比べると記述がかったるいことがあります。

このgroovyという言語はjavaというバックエンドを全く損なうことなく、実装速度をぐっとお仕上げてくれるようなものです。
というわけですごく気に入っているのです。

が、良いことばかりかなというと深い部分に行ったりするとちょっと不明な挙動を示したりすることもあるんですが、今回取り上げるのは gradle daemon と呼ばれる仕組みです。

groovyは様々なビルドシステムを利用することができますが、その中でも有名なものの一つにgradleと呼ばれるものがあります。
gradleはgroovyで開発されており、javaやgroovyのビルドツールとして利用できます。

ビルとツールによりgroovyソースはコンパイルされjavaのclassファイルへと変換されたりと様々な事が行われるのですが、その際に多くのオーバーヘッドを含みます。
そのためgradleではそのビルドコストを少なくするためにgradle daemonという仕組みを提供しています。
このdaemonは常駐することでビルドにかかるコストを削減してくれます。

今回この gradle daemon によりちょっとした不都合が起こりました。
現在個人として開発中のシステムで、ものすごくメモリを食うプロセス(javaではない)を起動しなければなく、しかしメモリを食いすぎるゆえに oom-killer によってプロセスが殺されることが続きました。

はじめはそのプロセスのメモリの消費量を抑えることを考えていたので、それが非常に困難であることがわかり、発想を変えて他のプロセスの精査を行うことにしました。

で、TOPコマンドで見てみるといるじゃないですか。でかいのが。

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
15032 z   20   0 3495m 433m  14m S 130.3 23.1   1:09.40 java

上記のプロセスの実態は gradle daemon です。

メモリを大量に消費していますね。物理メモリのおよそ25%をgradle daemonが抱えていることになります。
上記はコンパイル時の状態なんですが、daemonはその目的達成のために各種リソースを抱えたまま常駐します

これはとんでもないということで即見直しの対象として検討しました。
使用されなければswapに吐き出されるんですが、swapに吐き出されるとしてもswapを消費してしまって結構邪魔だし、そこまで頻繁にビルドしない。
そして結局 gradle daemon も oom-killer に殺されているという始末。
というわけでdaemonを起動しないようにしました。

実装としては gradle.properties に下記のように記述を加えました。

org.gradle.daemon=false

これにより無事にメモリを確保することができ、目的のプロセスを起動することができました。

gradleは便利さ故に色々できるのですが、マニュアルがかなり膨大です。
しかも失礼ですが和訳は結構わかりにくいです。おそらく、原文を読んだほうが良いでしょう。

便利なものですが気をつけて使わないとトラブルの温床となりますね。


javaのクラスローダの仕組みについて

javaのクラスローダに関して下記のようなコードが存在するときの内部動作がよくわからなかったので調べ所を残すことにしてみました。

Class clazz = App.class;
ClassLoader cl = clazz.getClassLoader();

クラスとクラスローダについて

さてこの getClassLoader というメソッドは何なのか、ということですが文字通りクラスローダを取得することができます。
このメソッドは Class クラスのメソッドですが、例えばアプリケーションを実装していて同プロジェクトに B というクラスと C というクラスが作成したときに下記のようなコードが存在したときに、この違いはあるんでしょうか?

ClassLoader clB = B.class.getClassLoader();
ClassLoader clC = C.class.getClassLoader();

先に答えを言ってしまうと、この2つで返却されるクラスローダは同じものになります。

理由ですがこの getClassLoader() を呼び出すとクラスは自分の参照するクラスローダを返却します。
すべてのクラスは必ず一つのクラスローダへの参照を持っており、それは自クラスをロードしたときに使用されたクラスローダを参照するようになっています。

そのため B, C は同プロジェクトのアプリケーションとして実装されているので、この2つは同じクラスローダによってロードされるため上記のような場合は大抵は同じクラスローダが返却されます。

クラスローダによるクラスの解決について

またクラスローダは自分の解決するクラスの領域というものが決まっており、階層構造を持ちます。
これはDNSで各ネームサーバが自分のゾーンだけを管理して、解決できない場合は親ネームサーバへと処理を移譲する仕組みと同じようなものです。

javaのデフォルトパッケージを解決するクラスローダのことを「ブートストラップクラスローダ」と呼び、これは一番親のクラスローダとして位置します。
さらにその下にアプリケーションのクラスを解決するための「システムクラスローダ」というクラスローダが存在します。
そんなに複雑でないシステムの場合はこの「システムクラスローダ」がアプリケーションクラスのクラスローダとして機能すると考えていて問題ないと思います。

クラスローダはクラスロードの依頼を受けた際にはまず親のクラスローダに対して解決を移譲します。
もしクラスが解決できないようであれば自分自身の管理する空間を探索します。

例えば下記のようなサンプルコードを用意して実行してみましょう。
ちなみにこのコードはgroovyにて記述してあります。

class Main {
    static void main(String[] args) {
        ClassLoader cl = Main.class.getClassLoader()
        println cl
    }
}

この実行結果は下記のように出力されました

sun.misc.Launcher$AppClassLoader@18b4aac2

「システムクラスローダ」というのは一般的な機能に対する名称のようで、クラス実態としては sun.misc.Launcher.AppClassLoader というクラスが実態のようです。
通常はこのシステムクラスローダがすべてのアプリケーションクラスのクラスローダとして機能します。

異なるクラスローダが用いられるアプリケーションはあるのか

主にJ2EEサーバなどにおいてはwarをおいただけで異なるアプリケーションをホストできる仕組みを提供しているため、各種ウェブアプリケーション単位でシステムクラスローダが異なるような形になると思われます。
設置した複数のwarファイルの中で同じ名称のクラスが存在する際に、うまく解決できないようではアプリケーションとして致命的ですので、このような場合にうまく機能してくる仕組みだと思われます。

通常はひとつのプロジェクトが固有の一つのクラスローダを持つ、、的な理解で問題ないと思います。

参考ページ

https://docs.oracle.com/javase/jp/8/docs/api/java/lang/ClassLoader.html
http://www.nminoru.jp/~nminoru/java/class_unloading.html


javaアプリケーションのcron起動時の文字化けに対応する

概要

javaアプリケーションの起動を行った際に、ある部分で文字化けが起こったので対応について調査する。

具体的には不具合が起こった箇所はjavaアプリケーションから外部プロセスを起動する部分や、メールを送信する機能の本文にVelocityのテンプレート機能を利用しており、これらを利用する箇所でそれぞれ日本語箇所が文字化けしていた。

原因

まず注目したいのは文字化けが起こっているケースがcronによるバッチからの起動に限定されることである。
バッチとは別にwebプロセスも動作しており、これはfat jarを作成してjavaコマンド経由でbashインタプリタ経由で起動している。が、こちらのwebプロセスは特に問題はない。

また先程メールが文字化けすると述べたが、実は文字化けするのは本文だけで、タイトルに関しては文字化けしていなかった。両者の違いは本文は外部テンプレートファイルから読み込んでいるのに対して、タイトルはソースコードに日本語をハードコーディングしている。

このことから大きく予想できるのはcron経由で外部ファイルを読み込むときに何らかの不具合が生じていることである。
cronはenvなどが特殊なんだろうかと当たりをつけて軽くウェブを検索するとこちらの記事を発見

どうやらcron経由でのコマンドはユーザの環境変数は一切適用されない模様。

解決

わかってしまえばなんてことはない。今回は参考サイトに則ってcrontabの記述の上にenv設定を行うことにします。
今回の場合はjavaがおそらく外部ファイルの文字コードを認識できていないのでcrontabの上部に下記のようにファイルエンコーディングを追記しました。

_JAVA_OPTIONS=-Dfile.encoding=UTF-8

無事にアプリケーションが動きましたとさ