クラスローダー
のおはなし
お話しすること
-
クラスローダーの分類
-
ロードのしくみ
-
mainメソッドはなぜ動く
-
JDBCドライバの怪
-
java9 jigsaw
間違ってるところも
あるかも 😇
クラスローダーの分類
-
BootStrap Class Loader
-
Extension Class Loader
-
System Class Loader
BootStrap Class Loader
-
Native Code
-
${JAVA_HOME}/jre/lib/
-
rt.jar
標準クラスライブラリ
Stringとか
-
- hotspot/src/share/vm/runtime/os.cpp
- -Xbootclasspathで変更も可能
scalaREPLとか
Extension Class Loader
- Java
-
${JAVA_HOME}/jre/lib/ext/
- nashorn.jar
JavaScript Engine - jfxrt.jar
javaFX - etc...
- nashorn.jar
System Class Loader
-
It is also known as application class loader
-
ご存知クラスパスからクラスをロード
-
java.net.URLClassLoaderのサブクラス
ロードのしくみ
- クラスローダツリー
- 検索順序
- 別々にロードしてみる
- 本丸はdefineClass
クラスローダツリー
検索順序
親から順番に探すため、親子で同一クラスを別々にロードは基本的に出来ない
URL jarUrl = new File(jarPath).toURI().toURL();
URLClassLoader cl =
URLClassLoader.newInstance(new URL[]{jarUrl});
Class<?> clazz = cl.loadClass("java.lang.String");
clazz.getClassLoader(); =====> null
Bootstrap Class Loaderでロードされたクラスは、
getClassLoader()の戻りが null になる
別々にロードしてみる
package com.company;
public class Singleton {
private Singleton(){}
public static final Singleton INSTANCE = new Singleton();
}
普通のシングルトン
親の検索対象とならないよう起動時のクラスパスからは外しておき、実行時に検索
URL[] urls = { new File("out/production/singleton/").toURI().toURL() };
ClassLoader cl1 = URLClassLoader.newInstance(urls);
ClassLoader cl2 = URLClassLoader.newInstance(urls);
Object instance1
= cl1.loadClass("com.company.Singleton").getField("INSTANCE").get(null);
Object instance2
= cl2.loadClass("com.company.Singleton").getField("INSTANCE").get(null);
System.out.println(instance1.getClass());
System.out.println(instance2.getClass());
System.out.println(instance1 == instance2);
System.out.println(instance1.getClass().equals(instance2.getClass()));
class com.company.Singleton
class com.company.Singleton
false
false
- 完全修飾名が同じでもロードしたクラスローダが違えば別扱い
-
代表的な物
- Servletコンテナ
Tomcatに複数のアプリを載せても、ちゃんと独立して動く
- Servletコンテナ
- URLClassLoader#addURL で探索対象のパスを追加すれば、後付も出来る
- ただし、迂闊にあれこれするとメモリリークに繋がるらしい
- https://www.slideshare.net/agetsuma/tomcat-java
- URLClassLoaderは、初期化時のjava.io.Fileインスタンスを保持している -> 上書き時の NoClassDefFound ?
本丸はdefineClass
protected Class<?>
defineClass(String name,
byte[] b,
int off,
int len)
findClassメソッドは、.classファイルをbyte[]に格納後、このメソッドへ渡す
バイトコード操作系
ライブラリ
- https://github.com/rzwitserloot/lombok
- https://github.com/jboss-javassist/javassist
- https://github.com/raphw/byte-buddy
Mockitoが依存している
これら全て、defineClassを呼んでいる
バイト配列を直接読込む検証もやってみたい
mainメソッドはなぜ動く
sun.launcher .LauncherHelper.java
public static Class<?> checkAndLoadMain(boolean printToStderr, int mode, String what)
static void validateMainClass(Class<?> mainClass)
mainClass.getMethod("main", String[].class)
mainClass は起動引数で指定されたクラスを ClassLoader.findClassで検索したもの
おなじみ?Reflectionで探している
余談
public enum LauncherHelper {
INSTANCE;
// 以下、全て staticメンバ
checkAndLoadMainはJNI経由でCから呼ばれる
Utility的なクラス
public final class LauncherHelper {
// No instantiation
private LauncherHelper() {}
java9のブランチでは普通にclass...
リファクタ?
JDBCドライバの怪
以前、JDBCの話をした時に、JDBC Ver.4以降であれば
Class.forName("com.mysql.jdbc.Driver")
は不要とお話しましたが...
実は限定的な条件でのみでした。すみません。
条件とは
- DriverがSystemClassLoaderでロードされた
言い換えると、JVM起動時のクラスパスに含まれていた - java.sql.DriverManager.getConnectionを呼び出すクラスが同クラスローダよりロードされたクラスである
Servletコンテナにデプロイされたwarや独自クラスローダを持つPlayなどは上記に当てはまらないため、Class.forNameが必要
play.runsupport.NamedURLClassLoader.scala
とはいえ
フルスタックフレームワークならば、
設定ファイルに書いておけば基本的に勝手にやってくれる
Java9 Modules
-
jmod形式
-
rt.jarにあたるものは
jmods/java.base.jmod
-
- 互換性のため classpath も暫くは使える
モジュール
- findClass(String moduleName, String name)
- findResource(String moduleName, String name)
- getUnnamedModule()
などが増えている - 諸々揉めている最中…
module hoge.piyo {
exports hoge.piyo;
requires hoge.fuga;
}
module-info.java
参考にしたもの
-
https://github.com/dmlloyd/openjdk
-
jdkのバージョンごとにブランチが切られていて便利
-
クローンはめっちゃ時間かかる
-
-
http://download.java.net/java/jdk9/docs/api/index.html?overview-summary.html
-
http://www.nminoru.jp/~nminoru/java/class_unloading.html
クラスローダーのおはなし
By yohei yamana
クラスローダーのおはなし
- 1,331