前回JITコンパイラで"コンパイルしたコードは戻り値で返すのではなくコードキャッシュにインストールする"と書きました。
ではコードキャッシュとは何でしょう?単純化して言うとJITコンパイル後のマシンコードをキャッシュする場所です。
コードキャッシュについてはJava Magazineの2014 JULY/AUGUSTにあるBen Evansさんの記事がわかりやすいです。日本語訳もされていますので、リンクを載せます。
http://www.oracle.com/webfolder/technetwork/jp/javamagazine/Java-JA13-Architect-evans.pdf
Java Magazine
ところでJava Magazineはご存知ですか?Oracleが2ヶ月に1度、発行しているJava Magazineというデジタル発行物です。80ページほどあり、記事が非常に良質です。英語ですが、2,3ヶ月後に日本語訳も出ます。ただし、日本語は全記事の翻訳ではなく半分ほどです。読みやすい英語ですので、英語版をおすすめします。
英語版 http://www.oracle.com/technetwork/java/javamagazine/index.html
日本語版 http://www.oracle.com/technetwork/jp/articles/java/overview/index.html
さて、コードキャッシュに戻ります。先程の記事には、
マシン・コードは、中心的な要素である CodeCache(C++ オブジェクト)内に配置されます。CodeCache はCodeBlob インスタンス(コンパイル後のメソッド・コードの表現)を保持するヒープのような構造体です。コード・ブロブがコード・キャッシュ内に配置されると、実行中のシステムが、インタプリタ・モードから、新しくコンパイルされたコードを使用するモードに切り替えられます
と説明があります。
コードキャッシュのデフォルトでの最大サイズはJava 8では240MB*1です。-XX:ReservedCodeCacheSize
で変更できます。あえてコードキャッシュを小さくして、キャッシュをフルにしてみましょう。
-XX:ReservedCodeCacheSize=1M
にしてみます。
Invalid ReservedCodeCacheSize: 1024K. Must be at least InitialCodeCacheSize=2496K.
2496K以上でないと上記のエラーとなります。-XX:ReservedCodeCacheSize=2496K
にします。フルにするためにはそれなりにJITコンパイルが走るアプリケーションである必要があります。start.spring.ioからSpring BootのWebアプリケーションを作成し、そのまま起動してみました。
$ java -XX:ReservedCodeCacheSize=2496K -jar demo-0.0.1-SNAPSHOT.jar Java HotSpot(TM) 64-Bit Server VM warning: CodeCache is full. Compiler has been disabled. Java HotSpot(TM) 64-Bit Server VM warning: Try increasing the code cache size using -XX:ReservedCodeCacheSize= CodeCache: size=2496Kb used=1980Kb max_used=1983Kb free=515Kb bounds [0x0000000103db8000, 0x0000000104028000, 0x0000000104028000] total_blobs=1104 nmethods=623 adapters=288 compilation: disabled (not enough contiguous free space left)
"CodeCache is full. Compiler has been disabled."となるので、キャッシュがフルになりJITコンパイルができない状況となっています。
さらに-XX:+PrintCompilation
もつけてみます。
$ java -XX:+PrintCompilation -XX:ReservedCodeCacheSize=2496K -jar demo-0.0.1-SNAPSHOT.jar ... 38826 2193 ! 4 java.net.URL::<init> (543 bytes) COMPILE SKIPPED: code cache is full ...
ものすごい量が出力されますが、"COMPILE SKIPPED: code cache is full"という出力がいくつも出ています。本番アプリケーションであれば、性能劣化となります。
せっかくなのでGraalのコードも見ます。org.graalvm.compiler.hotspot.CompilationTask#installMethod()
でマシンコードをコードキャッシュにインストールしています。
private void installMethod(DebugContext debug, final CompilationResult compResult) { final CodeCacheProvider codeCache = jvmciRuntime.getHostJVMCIBackend().getCodeCache(); HotSpotBackend backend = compiler.getGraalRuntime().getHostBackend(); installedCode = null; Object[] context = {new DebugDumpScope(getIdString(), true), codeCache, getMethod(), compResult}; try (DebugContext.Scope s = debug.scope("CodeInstall", context)) { installedCode = (HotSpotInstalledCode) backend.createInstalledCode(debug, getRequest().getMethod(), getRequest(), compResult, getRequest().getMethod().getSpeculationLog(), null, installAsDefault, context); } catch (Throwable e) { throw debug.handle(e); } }
org.graalvm.compiler.core.target.Backend#createInstalledCode()
です。
public InstalledCode createInstalledCode(DebugContext debug, ResolvedJavaMethod method, CompilationRequest compilationRequest, CompilationResult compilationResult, SpeculationLog speculationLog, InstalledCode predefinedInstalledCode, boolean isDefault, Object[] context) { ... InstalledCode installedCode = getProviders().getCodeCache().installCode(method, compiledCode, predefinedInstalledCode, speculationLog, isDefault); ...
jdk.vm.ci.hotspot.HotSpotCodeCacheProvider#installCode()
です。
public InstalledCode installCode(ResolvedJavaMethod method, CompiledCode compiledCode, InstalledCode installedCode, SpeculationLog log, boolean isDefault) { ... int result = runtime.getCompilerToVM().installCode(target, (HotSpotCompiledCode) compiledCode, resultInstalledCode, speculationLog); ...
jdk.vm.ci.hotspot.CompilerToVM#installCode()
です。
/** * Installs the result of a compilation into the code cache. * * @param target the target where this code should be installed * @param compiledCode the result of a compilation * @param code the details of the installed CodeBlob are written to this object * @return the outcome of the installation which will be one of * {@link HotSpotVMConfig#codeInstallResultOk}, * {@link HotSpotVMConfig#codeInstallResultCacheFull}, * {@link HotSpotVMConfig#codeInstallResultCodeTooLarge}, * {@link HotSpotVMConfig#codeInstallResultDependenciesFailed} or * {@link HotSpotVMConfig#codeInstallResultDependenciesInvalid}. * @throws JVMCIError if there is something wrong with the compiled code or the associated * metadata. */ native int installCode(TargetDescription target, HotSpotCompiledCode compiledCode, InstalledCode code, HotSpotSpeculationLog speculationLog);
nativeメソッドでした。jvmciCompilerToVM.cpp
です。
JNINativeMethod CompilerToVM::methods[] = { ... {CC "installCode", CC "(" TARGET_DESCRIPTION HS_COMPILED_CODE INSTALLED_CODE HS_SPECULATION_LOG ")I", FN_PTR(installCode)}, ...
JNIですね。ここから先は僕にはわかりません。