I.はじめに
このプログラムは、クラスを使用すると、クラスがまだメモリにロードされていない場合、負荷によってJVMの意志を、リンク初期化の 3つの手順を実施するクラスローディングのタイプという。
第二に、負荷の種類、リンク初期化
1、ロード
クラスロードはjava.lang.Classオブジェクトを作成するために、クラスファイルのクラスがメモリに読み込まれることをいい、誰。クラスロード処理は、クラスローダ、JVMによって提供されたクラスローダによって達成されます。我々開発者はまた、継承クラスローダを通じて、独自のクラスローダを実装することができます。
1.1、ロードされたクラスのソース
- ローカルファイルシステムからロードしたクラスファイル
- パッケージJARからロードクラスファイル
- ネットワーク上の負荷クラスファイル
- 動的にコンパイルされたJavaソースファイルを入れて、ロードを実行。
2、クラスリンク
クラスをロードすることにより、メモリは、Classオブジェクトを作成しました。リンクは、JREにバイナリデータをマージする責任があります。リンクは、三つの段階を解決するために、準備ができて、確認する必要があります。
2.1、検証
ステージがロードされたクラスをチェックするために使用される正確な内部構造とコヒーレンス、および他のクラスを有していることを確認します。これは、制約のJava仮想マシンを満たしているかどうか、です。
2.2、準備ができて
クラス準備フェーズは、クラス変数の種類のメモリを割り当てると、デフォルトの初期値を設定する責任があります。
2.3、構文解析
私たちは知っている、実際には、参照メモリアドレスに対応しています。この問題を考える、書き込みコード、参照方法を使用して、クラスのメモリアドレスの参照方法は何を知っていますか?もちろんクラスが仮想マシンにロードされていませんので、あなたはこれらのアドレスを取得することはできません、知りません。例えば、メソッド呼び出しのために、コンパイラは、方法は、ターゲット、ターゲットメソッド名、戻り値の型を含み、シンボル参照のパラメータ値型を受け取るクラスを生成し、世代と呼ばれるメソッドを指します。
解決フェーズの目的は、実際の基準にこれらのシンボリック参照を解決することです。クラスのシンボルの基準点がロードされていないか、クラスフィールドまたはメソッドをロードされていない場合は、解像度は、クラスがロードされるトリガする(しかし必ずしもトリガ分析および初期化)。
3開始、
クラスの初期化フェーズでは、仮想マシンのメインクラス変数が初期化されます。仮想マシンを呼び出し、<clinit>メソッド、初期クラス変数。
Javaクラスのクラス変数は、2つの方法で初期化:
- あなたは、初期化を定義するとき
- 静的初期化ブロックの初期化
3.1、<clinit>関連
- そして、順序が初期化定義によると、組み合わせ<clinit>メソッドのための仮想マシンベースの親クラス変数とクラスメソッドを集めます。仮想機会が実行する前に、<clinit>のサブクラスは、<clinit>親クラスが最初に終了したことを確認します。したがって、第1の仮想マシンは、java.lang.Objectの方法でなければならない<clinit>の方法で行われています。
パブリッククラスTest { 静的INT A = 10。 静的{ A = 20。 } } クラスTest1をテスト{延び B = A INTプライベート静的。 パブリック静的無効メイン(文字列[] args){ のSystem.out.println(Test1.B)。 } } //输出结果 // 20
出力から、サブクラスの静的初期化する前に初期化された親クラスの静的初期化ブロックは、出力結果は20ではなく、10です。
- あなたは、静的変数やメソッドを持っていない場合、仮想マシンは、クラスまたは親クラスで、<clinit>メソッドのために生成されることはありません。
- 様々なインタフェースおよびクラスその実装インタフェース<clinit>メソッド親インターフェイスを実行する<clinit>メソッドを必要としません。親インターフェイスを使用してインターフェイスで定義された親変数が初期化されますときにのみ。さらに、初期のインターフェイスの実装クラスは、<clinit>メソッド・インターフェース実行されませんでした。
パブリックインターフェースInterfaceInitTest { 長いA = CurrentTime.getTime()。 } InterfaceInitTest1インタフェースはInterfaceInitTest {延び int型B = 100。 } クラスInterfaceInitTestImpl実装InterfaceInitTest1 { パブリック静的無効メイン(文字列[] args){ のSystem.out.println(InterfaceInitTestImpl.B)。 System.out.println( "--------------------------"); System.out.println( "当前时间:" + InterfaceInitTestImpl.A)。 } } クラスCurrentTimeを{ 静的長いgetTime(){ System.out.printlnは( "加载了InterfaceInitTest接口")。 リターンのSystem.currentTimeMillis(); } } //输出结果 // 100 // --------------------------- // InterfaceInitTestインターフェイスのロード //現在の時間を:1560158880660
以下からの出力は検証:インタフェースのために、インターフェイスは本当に親のインタフェースをロードする親クラスの変数を使用します。これは、通常のクラスのロードと同じではありません。
- 複数のスレッドが、<clinit>このタイプの方法を実行するので、唯一のスレッドを1つのクラスを初期化する場合は、クラス<clinit>メソッドが正しく、マルチスレッド環境でロックされ、同期され、他のスレッドであることを確実にするために、仮想機会アクティブなスレッドの実行<clinit>メソッドが完了するまでブロックする必要があります。
パブリッククラスMultiThreadInitTest { 静的INT A = 10。 静的{ のSystem.out.println(にThread.currentThread()+ "MultiThreadInitTest INIT")。 {試みる TimeUnit.SECONDS.sleep(10)。 }キャッチ(InterruptedExceptionある電子){ e.printStackTrace(); } } パブリック静的無効メイン(文字列[] args){ Runnableを実行可能=() - > { のSystem.out.println(にThread.currentThread()+ "開始")。 System.out.println(MultiThreadInitTest.A)。 System.out.println(にThread.currentThread()+ "上で実行"); }。 新しいスレッドを(実行可能)=スレッド1スレッド。 新しいスレッドを(実行可能)=スレッド2スレッド。 thread1.start(); thread2.start(); //输出结果 //スレッド[メイン、5、メイン]のinit MultiThreadInitTest //スレッド[スレッド0,5、メイン]スタート // 10 //スレッド[スレッド0,5、メインは]轢か //スレッド[スレッド1,5、メイン]開始 // 10 //スレッド[スレッド1,5、メイン]上で実行します
検証済みの出力から参照してください:MultiThreadInitTestを初期化するための唯一の最初のスレッドを第二のスレッドがランキングを待ってブロックされて行って初期化したスレッドとなっています。
3.2、クラスの初期化タイミング
- 仮想マシンが起動すると、ユーザーが指定したメインクラスを初期化します。
- ターゲットクラスの新しいインスタンスのための新しい命令に直面したとき、ターゲットクラスは、新しい命令を初期化します。
- 静的メソッド呼び出しまたは静的変数またはクラス静的変数の初期化方法を使用して直面したとき、
- サブクラスの初期化プロセスは、親クラスの初期化がトリガされます。
- インタフェースは、デフォルトのメソッドを定義する場合、直接または間接的にこのインタフェースを実装するクラスの初期化、インターフェースの初期化がトリガされます。
- クラスは、リフレクションAPIを使用して呼び出しを反映したとき、このクラスを初期化します。
- Class.forNameの()は、初期化クラスをトリガーします
3.3、最終の初期化
注:コンパイル時で、決定されているので、コンパイル時の値ではなく、トリガーの初期化を参照して、判定された場合、一定の最終用途の定義については、「マクロ変数」。あなたはコンパイル時に判断できない場合は、最初の使用の初期化につながります。
{クラスStaticInnerSingleton公開 / ** *単一静的内部クラスの例を使用して実装: * 1:セキュリティスレッド * 2:遅延ロード すなわちオブジェクトおよび非デシリアライズシリアライズセキュリティは、得られたデシリアライズオブジェクトシングルトン:3 *実施例の原理のない同じ、単一違反 * / プライベート静的クラスLazyHolder { プライベート静的最終StaticInnerSingleton INNER_SINGLETON新しい新しいStaticInnerSingleton =(); } プライベートStaticInnerSingleton(){ } パブリック静的StaticInnerSingletonのgetInstance(){ LazyHolder.INNER_SINGLETONを返します; } }
たとえば、シングルトンの静的内部クラスの実装を参照してください。私たちは、シングルトンインスタンス最終定義を参照することができますが、コンパイル時のダウン、あなたが遅延ロードされたトリガ負荷の静的内部クラスにStaticInnerSingleton.getInstance()メソッドを使用して最初の時点で決定することはできません。ここで私は、変数の最終的な定義はコンパイル時に決定することができない場合に使用する場合、またはクラスが初期化されることを指摘します。
3.4は、ClassLoaderクラスがロードされ、初期化されません
クラステスター{パブリック 静的{ するSystem.out.println( "静的クラス初期化ブロックテスター"); } } クラスClassLoaderTest { パブリック静的無効メイン(文字列[] args){スローAにClassNotFoundException のClassLoader ClassLoader.getSystemClassLoaderクラスローダ=(); / /次の文は、単にテスタークラスにロードされ 、ClassLoader.loadClass(「loader.Tester」) のSystem.out.println(「システム負荷テスタークラスを」); //次の文を初期化しますテスタークラス にClass.forName(「loader.Tester 「); } } //出力 //ローディングシステムテスタークラス //静的初期化子ブロックテスタークラス
:出力が証明;クラスの初期化原因の使用にClass.forName()軍ClassLoaderクラスがない初期化を行い、ロードされます。
第三に、クラスローダ
クラスローダは、(等ジャー、またはローカルディスク、またはネットワークの取得は、かどうか)のためにメモリにロードされるの.classファイルを担当し、対応のjava.lang.Classオブジェクトを生成します。クラスを2回ロードされませんJVMにロードされます。
それは同じクラスであると判断するには?
完全修飾クラス名(パッケージ名+クラス名)とJVM関節のクラスローダを使用して、各クラスは、異なるクラスローダ同じクラス場合、仮想マシンにではなく、相互にロードすることができるように、一意のIDであります互換性。
1、JVMのクラスローダ分類
1.1、ブートストラップクラスローダ
ブートストラップクラスローダは、ルートクラスローダは、Javaのコアライブラリをロードするための責任です。サブクラスのクラスローダローダはC ++の実装があり、根ではありません。
{クラスBootstrapTest公共 のpublic static無効メイン(文字列[] args){ //すべてのルートURLクラスローダ負荷のアレイを取得 URL [] = Launcher.getBootstrapClassPath URLS()getURLs();. Arrays.stream(URLS)。 forEachの(のSystem.out ::のprintln); } } //出力 //file:/C:/SorftwareInstall/java/jdk/jre/lib/resources.jar //ファイル:/ C:/ SorftwareInstall / Javaの/ JDK /jre/lib/rt.jar //file:/C:/SorftwareInstall/java/jdk/jre/lib/sunrsasign.jar //file:/C:/SorftwareInstall/java/jdk/jre/lib/jsse.jar //file:/C:/SorftwareInstall/java/jdk/jre/lib/jce.jar //file:/C:/SorftwareInstall/java/jdk/jre/lib/charsets.jar //ファイル:/ C:/ SorftwareInstall / Javaの/ JDK / JRE / libに/ jfr.jar //ファイル:/ C:/ SorftwareInstall /のJava / JDK / JRE /クラス
ルートクラスローダは、(仮想マシンによる-Xbootclasspathパラメータ、およびクラスが指定された)%JAVA_HOME%/ JRE / libに次のパックジャーをロードする責任があります。
私たちは、ライブラリーは、我々は多くの場合、このjarパッケージに使用見ることができ、解凍をrt.jarのだろう。
1.2、拡張クラスローダー
拡張クラスローダのための拡張クラスローダは、おなじみの指定されたディレクトリのjarパッケージ%JAVA_HOME%/ JRE /内線またはのjava.ext.dirsシステムをロードする責任があります。あなたは簡単に自分の使用することができ、このディレクトリの下に、独自のキットを一緒に書くことができます。
1.3、システムのClassLoader
システムクラスローダシステム(アプリケーション)クラスローダがロード-classpath javaコマンドのオプション、のjava.class.pathシステムプロパティまたはCLASSPATH環境変数とJARファイル指定されたクラスパスからロードする責任があります。プログラムは、システムクラスローダClassLoader.getSystemClassLoaderを介して取得することができます()。指定しない場合、ユーザ定義のクラスローダは、親クラスローダーローダーとしてシステムがデフォルトになります。
第四に、クラスローディング機構
1.1、JVMの主要なクラスローディング機構。
- 全体的な責任:クラスローダがクラスをロードするための責任がある、とクラスが依存している場合にも、他のクラスを担当する引用は、負荷への別のクラスローダとの表示がない限り、クラスローダによってロードされます。
- 親信託(両親の代理人):親クラスローダはローダは、独自のクラスパスからクラスをロードしようとロードできないときにのみ、クラスをロードするために、親ローダーの試みをしてみましょう。
- キャッシュの仕組み:プログラムは、クラスの検索におけるバッファゾーンから最初に、クラスローダをクラスの使用を必要とする場合にキャッシュ機構がすでにロードされたクラスは、キャッシュされ、クラスはキャッシュが存在しない場合のみ、システムが読み込まれますそのようなバイナリデータを取り、キャッシュに保存されているクラスのオブジェクトに変換します。これは、クラスを変更する理由は、あなたを有効にするJVMの原因を再起動する必要があります。
注:親子関係のクラスローダ間の親子関係は、クラスの継承ではなく、インスタンス間の親子関係。
public class ClassloaderPropTest { public static void main(String[] args) throws IOException { //获取系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println("系统类加载器:" + systemClassLoader); /* 获取系统类加载器的加载路径——通常由CLASSPATH环境变量指定,如果操作系统没有指定 CLASSPATH环境变量,则默认以当前路径作为系统类加载器的加载路径 */ Enumeration<URL> eml = systemClassLoader.getResources(""); while (eml.hasMoreElements()) { System.out.println(eml.nextElement()); } //获取系统类加载器的父类加载器,得到扩展类加载器 ClassLoader extensionLoader = systemClassLoader.getParent(); System.out.println("系统类的父加载器是扩展类加载器:" + extensionLoader); System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs")); System.out.println("扩展类加载器的parant:" + extensionLoader.getParent()); } } //输出结果 //系统类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2 //file:/C:/ProjectTest/FengKuang/out/production/FengKuang/ //系统类的父加载器是扩展类加载器:sun.misc.Launcher$ExtClassLoader@1540e19d //扩展类加载器的加载路径:C:\SorftwareInstall\java\jdk\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext //扩展类加载器的parant:null
从输出中验证了:系统类加载器的父加载器是扩展类加载器。但输出中扩展类加载器的父加载器是null,这是因为父加载器不是java实现的,是C++实现的,所以获取不到。但扩展类加载器的父加载器是根加载器。
1.2、类加载流程图
图中红色部分,可以是我们自定义实现的类加载器来进行加载。
五、创建并使用自定义类加载器
1、自定义类加载分析
除了根类加载器,所有类加载器都是ClassLoader的子类。所以我们可以通过继承ClassLoader来实现自己的类加载器。
ClassLoader类有两个关键的方法:
- protected Class loadClass(String name, boolean resolve):name为类名,resove如果为true,在加载时解析该类。
- protected Class findClass(String name) :根据指定类名来查找类。
所以,如果要实现自定义类,可以重写这两个方法来实现。但推荐重写findClass方法,而不是重写loadClass方法,因为loadClass方法内部回调用findClass方法。
我们来看一下loadClass的源码
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { //第一步,先从缓存里查看是否已经加载 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { //第二步,判断父加载器是否为null if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { //第三步,如果前面都没有找到,就会调用findClass方法 long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
loadClass加载方法流程:
- 判断此类是否已经加载;
- 如果父加载器不为null,则使用父加载器进行加载;反之,使用跟加载器进行加载;
- 如果前面都没加载成功,则使用findClass方法进行加载。
所以,为了不影响类的加载过程,我们重写findClass方法即可简单方便的实现自定义类加载。
2、实现自定义类加载器
基于以上分析,我们简单重写findClass方法进行自定义类加载。
public class Hello { public void test(String str){ System.out.println(str); } } public class MyClassloader extends ClassLoader { /** * 读取文件内容 * * @param fileName 文件名 * @return */ private byte[] getBytes(String fileName) throws IOException { File file = new File(fileName); long len = file.length(); byte[] raw = new byte[(int) len]; try (FileInputStream fin = new FileInputStream(file)) { //一次性读取Class文件的全部二进制数据 int read = fin.read(raw); if (read != len) { throw new IOException("无法读取全部文件"); } return raw; } } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = null; //将包路径的(.)替换为斜线(/) String fileStub = name.replace(".", "/"); String classFileName = fileStub + ".class"; File classFile = new File(classFileName); //如果Class文件存在,系统负责将该文件转换为Class对象 if (classFile.exists()) { try { //将Class文件的二进制数据读入数组 byte[] raw = getBytes(classFileName); //调用ClassLoader的defineClass方法将二进制数据转换为Class对象 clazz = defineClass(name, raw, 0, raw.length); } catch (IOException e) { e.printStackTrace(); } } //如果clazz为null,表明加载失败,抛出异常 if (null == clazz) { throw new ClassNotFoundException(name); } return clazz; } public static void main(String[] args) throws Exception { String classPath = "loader.Hello"; MyClassloader myClassloader = new MyClassloader(); Class<?> aClass = myClassloader.loadClass(classPath); Method main = aClass.getMethod("test", String.class); System.out.println(main); main.invoke(aClass.newInstance(), "Hello World"); } } //输出结果 //Hello World
ClassLoader还有一个重要的方法defineClass(String name, byte[] b, int off, int len)。此方法的作用是将class的二进制数组转换为Calss对象。
この例は非常に単純ではありません、このファイルならば、私はテストHelloクラスを書いた、と後に現在のパス上でコンパイル(誰もがにfindClassの判断で参加することができ、あなたがた.javaファイルを見つけることを試みることとコンパイルされた.classファイルが取得します;又は等の.classファイルよりも大きなファイルの.java最終更新時間決意最終更新時間、次いで再コンパイルロジック)。