クラスローダー

のおはなし

お話しすること

  • クラスローダーの分類

  • ロードのしくみ

  • 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...

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に複数のアプリを載せても、ちゃんと独立して動く
  • 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