Javaのクラスローダに理解

0クラスのライフサイクル

クラスローダは、Java仮想マシンは、プログラムの柔軟性を向上、実装の過程でJava仮想マシンのクラスに読み込むことができます革新的なJavaの、です。Javaでは、クラス情報は、メソッド領域に格納されます。クラスローダを導入する前に、まずJavaクラスのライフサイクルを見てください。Javaクラスのライフサイクルは、以下の6つのステップに分けることができます。

  1. 負荷は、クラスローダによって、バイナリは、仮想マシンのクラスに読み込まれ、最終的にクラスインスタンスオブジェクトを生成します。
  2. リンク、行くために仮想マシンの状態を実行しているマージするバイナリデータは、この手順は、次の3つの部分に分けることができます:
    1. 検証、正しいバイナリ形式を確実にします。
    2. 準備ができて、それが法の領域にクラスのためにメモリを割り当てる必要があります。
    3. 基準に一定のプールの符号を解析する(このステップは、さらに変数を使用することができる、すなわち、遅延ロード)
  3. 初期化:
    • 親クラスのクラスが初期化されていない場合は、親クラスを初期化します。
    • クラス初期化メソッドがある場合<clinit>()、この方法を実行し、(コンパイラによって生成される初期化するための方法を、プログラマは手動でJavaソースコードに追加することができません)。
  4. プログラムは次のキーワードを見つけた場合は、オブジェクトを作成しnewnewInstanceclonegetObject、それはあなたがヒープメモリ内のオブジェクトを作成する必要があることを意味する場合、オブジェクトを作成するために呼び出します<init>()(クラスのコンストラクタに相当する)方法を、初期化メソッドが親を呼び出す必要があります前に実行クラスの初期化メソッド。
  5. オブジェクトがもはや参照されている場合、オブジェクト収集するガベージコレクタは、その明示的なコールするときに、オブジェクトの端が、それは、ガベージコレクションのプログラムの実行にガベージコレクトしないであろうvoid finalize()定義されている場合、方法(方法);
  6. クラスがもはや使用されている場合はアンロードのクラスは、それはゴミ収集できなくなります。唯一のクラスローダは、ユーザ定義のクラスがアンロードされます読み込まれ、BootStrapClassLoaderは、クラスがアンインストールすることはできませんロード。

ここで、クラスが初期化され、アンロードされていない場合、クラスの初期化は、一度だけ(以下の5つの場合にのみ、クラス初期化を発生することがあり、上記1〜3をまとめてクラス初期化と呼ぶことができる次の使用ステップ)初期化する必要はありません。

  1. 遭遇したnewgetstaticputstaticinvokestaticときキーワード。
  2. 使用java.lang.reflect呼び出されたときに、クラスを反映したパッケージ。
  3. クラスを初期化する場合、親クラスが初期化されていない場合、親クラスが初期化されます。
  4. 仮想マシンが起動されると、それが含まれているmain()クラスメソッドが初期化されます。
  5. 場合動的言語サポートJDK1.7を使用する場合java.lang.invoke.MethodHandle、最終的な例では、解析結果であるREF_getStaticREF_putStaticREF_invokeStaticメソッドハンドル時間;

基本概念クラスローダ

ローディング:この資料に記載されるクラスローダは、クラスを対応のライフサイクルの最初のステップです。

名前が示すように、JVMにJavaバイトコードデータをロードするクラスローダの役割であり、生成しjava.lang.Classたクラスのインスタンスを。この例により、表現するために使用されるJavaクラスのような各インスタンスnewInstance()メソッドは、そのクラスのオブジェクトを作成するために使用することができます。

我々はできるjava.lang.ClassLoader、次の主要なクラスのロードおよび関連する方法を含み、バイトコードクラスにデータをロードすること:

方法 説明
getParent() 親クラスローダクラスローダを返します。
loadClass(文字列名) 名前のクラス名をロード、返される結果は、java.lang.Classクラスのインスタンスであります
findClass(文字列名) クラスの名前の名前を検索し、返された結果は、java.lang.Classクラスのインスタンスであります
findLoadedClass(文字列名) 返された結果の名前がす​​でにロードされていた検索クラス名はjava.lang.Classクラスのインスタンスであります
defineClass(文字列名、バイト[] B、オフint型、int型のlen) JavaクラスBバイト配列にコンテンツを変換し、返された結果は、java.lang.Classクラスのインスタンスです。このメソッドは、finalとして宣言されています
resolveClass(クラス<?> c)の リンク指定されたJavaクラス

方法であって、そのコアは、defineClassバイナリコードへのデータのバイトの一連の原因である方法、Classこれらはバイトコードでどこから来るかどうか、クラスのインスタンス。

提供2. JVMクラスローダー

システムクラスローダは、JVMによって提供され、クラスローダとして使用することができ、JVMシステムのクラスローダは、次の3つがあります。

  1. ブートストラップクラスローダ(クラスブートストラップ・ローダ):Javaのコアライブラリをロードするために使用されているが、ローダはjava.lang.ClassLoaderのから継承されていない、C ++で実装されています。これは主に、ロードJavaの基礎クラスを担当して%JRE_HOME/lib/ディレクトリの下にrt.jarresources.jarR charsets.jaclassあなたは私たち自身のjarパッケージをロードするために、ブートストラップクラスローダを使用したい場合は、あなたが達成するために、次の方法を使用することができ、その上

    我们可以在运行时使用如下参数:
    
    -Xbootclasspath:完全取代系统Java classpath.最好不用。
    -Xbootclasspath/a: 在系统class加载后加载。一般用这个。
    -Xbootclasspath/p: 在系统class加载前加载,注意使用,和系统类冲突就不好了.
    win32     java -Xbootclasspath/a: some.jar;some2.jar;  -jar test.jar
    unix          java -Xbootclasspath/a: some.jar:some2.jar:  -jar test.jar
    win32系统每个jar用分号隔开,unix系统下用冒号隔开
    
  2. 拡張クラスローダ(拡張クラスローダー):Javaの拡張ライブラリをロードするために使用され、ほとんど%JRE_HOME/lib/extのディレクトリにjarファイルとクラスファイルは、jarファイルをロードする必要があるかもしれませんスローされ%JRE_HOME%/lib/ext、このディレクトリに、以下のjarパッケージを意志ブートストラップ拡張クラスローダーをロードするための作業の後にクラスローダ。
  3. システムクラスローダ(クラスローダーシステムは):これは、Javaアプリケーションのパスをロードするためのクラス(CLASSPATH)によるJavaクラスです。一般的には、クラスのJavaアプリケーションは、それがロードを完了するために作られて、そしてできるClassLoader.getSystemClassLoader()ことを獲得します。あなたが指定したjarファイルがロードされるようにしたい場合は、のみする必要がMANIFEST.MF次のコードを追加します。Class-Path: lib/demo.jar lib/demo1.jarそれはCLASSPATHで指定されたjarファイルに追加することができます。

ここで、ブートストラップクラスローダは、JVMによって開始され、初期化sun.misc.Launcher、sun.misc.Launcher初期拡張クラスローダ、アプリケーションのClassLoader。ブートストラップクラスローダに加えて、クラスローダ自体の残りの部分は、親クラスローダークラスローダーは、クラスローダをロードローダーですので、別のクラスローダによってロードされます。提供JVMローダでは、親クラスローダシステムクラスローダは、クラスローダを拡張することで、親クラスローダ拡張クラスローダは、ブートストラップクラスローダです。

3.カスタムクラスローダ

JVMは、私たちのニーズを満たすことができないクラスローダを提供する場合、我々は独自のクラスローダを実装する必要があります。

カスタムクラスローダは、単に呼び出すことによって、非常に簡単ですがClassLoader、クラスのClass<?> java.lang.ClassLoader.defineClass(String name, byte[] b, int off, int len)メソッドをすることができますが、プロセスであるためfinalprotected私たちは継承しなければならないClassLoaderクラスを使用してsuper.xxx()コールにフォーマットを。ここではデモがあります

1 
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
インポートのjava.io.File; 
輸入 java.io.FileInputStream;
輸入 java.lang.reflect.Methodオブジェクト;

パブリック クラス {

公共の 静的な 無効 メイン(文字列[] argsが) スロー例外 {
ClassLoaderSub classLoaderSub =新新 ClassLoaderSub();
<?> = classLoaderSub.getClassByFile clazzクラス(: "\ Users \ユーザーADMIN \デスクトップ\そのA.class C" ;)
のSystem.outを.println( "クラス名:" + clazz.getName());
のSystem.out.println( "クラスローダ:" + clazz.getClassLoader());
オブジェクトOBJ = clazz.newInstance()
メソッド、メソッド= clazz.getMethod( "テスト");
Method.invoke(OBJ、ヌル大カラム   ローダJavaクラスが理解 PAN>);
}

}

クラス ClassLoaderSubは 延び ClassLoaderを {


*コールdefineClass親インスタンスクラスを生成する
* @paramの名前
* @paramの B
* @paramの OFF
* @param lenの
* @return
* / パブリッククラス<?> DefineClassByName(文字列名、バイト [] B、INT OFF、INT LEN){ クラスclazz = <?> スーパー .defineClass(名前、B、OFF、LEN); 戻り clazz; }






*読むのバイトコードファイルとバイト配列に変換
*
* @param fileNameに
* @return
* @throws例外は、
* /
"ついに"パブリッククラス<?> GetClassByFile(文字列filename)は、スロー例外{ ファイルクラスファイル=を新しい新しいファイル(filename); バイトバイト[] = 新しい新しいバイト [ 1024 * 100 ]; FileInputStreamのFIS = ヌル ; <?>クラスclazz = ヌル ; 試み { FIS =






新しい FileInputStreamを(クラスファイル)。int型 J = 0 ; 一方、){ int型 I = fis.read(バイト)。もし - (I == 1ブレーク J + = I; } clazz = defineClassByName(ヌル、バイト、0、j)は、 } 最後に { fis.close()。リターン clazz。 } } }














A.classの非常にシンプルな、次のようにコンパイルのソースコード:

1 
2
3
4
5
6
7
パブリック クラス A  {

public void test() {
System.out.println("我被加载成功并且方法执行了!");
}

}

执行main()方法,打印出以下内容:

类的名字:A
类的加载器:ClassLoaderSub@15db9742
我被加载成功并且方法执行了!

以上代码的主要功能就是把 A.class的字节码读入到JVM中并且创建一个对应该字节码所对应的类的Class实例。然后根据该类来创建一个该类的对象并且调用其test()方法,方法成功执行。自定义的类加载器的核心组件就是defineClass方法,这个需要重点理解。

4.类加载器的树状组织结构

如果把JVM类加载器和自定义类加载器结合起来看的话,那么会构成一个继承的层次结构。我们已经知道,JVM的三个类加载器有继承关系,那么加上自定义类加载器之后继承关系会变成什么样呢,下面这张图很清晰的描述了这种结构

Javaのクラスローダ構造

由于这种目录结构,JVM提出了类加载器的双亲委派机制,即

  • 如果某个类加载器需要加载一个类,那么此类加载器会调用它的父类加载器来加载这个类(如果某个类加载器的父类加载器为 null,那么就直接调用bootstrap class loader来进行类加载操作),一直向上直到bootstrap class loader被调用了,那么bootstrap class loader不会再调用父类加载器(也没有可以调用的),而是会自己对该类进行加载;
  • 如果bootstrap class loader的类加载操作失败了,那么就会调用其子类加载器进行加载;如果还是失败,就继续向下调用,直到成功为止。如果一直无法成功,则会抛出找不到类的异常。

双亲委派机制保证了JVM的安全性,因为恶意程序无法把自己伪造成JVM所信任的类。例如,我伪造了一个java.lang.Object类,想让JVM把它加载进去,但是由于双亲委派机制的存在,JVM默认会使用bootstrap class loader来加载java.lang.Object类,而因为bootstrap class loader默认会加载%JRE_HOME/lib/下的 java.lang.Object 文件,所以我的攻击自然失效。

那么,如果我更换一种攻击方式呢。我想让启动类加载器加载一个由我书写的名为java.lang.Attack的带有攻击代码类,那么我的攻击能成功吗?答案是不能。因为对于不同的类加载器所加载的类,它们将属于不同的运行时包。运行时包这个词在《Java虚拟机规范第2版》中第一次出现,如果两个类是由不同的类加载器进行加载的,那么他们就不可以进行相互访问。更典型的,如果我使用了两个类加载器加载了同一个类,那么这两个类是不一样的,如果让这两个类之中的某一个类的对象由另一个类来进行强制类型转换,会产生异常。

5. 关于Class.forName()方法:

Class.forName() 是一个静态方法,同样可以用来加载类。该方法有两种形式:Class.forName(String name, boolean initialize, ClassLoader loader)Class.forName(String className)。第一种形式的参数 name表示的是类的全名,initialize表示是否是初始化类,loader表示加载时使用的类加载器;第二种形式则相当于设置了参数 initialize的值为 true,loader的值为当前类的类加载器。

Class.forName()方法本身已经包含了类的加载过程。除此之外,Class.forName()还包括了第0节中的第2、3步操作,也就是说Class.forName()方法不仅会加载一个类,还会初始化这个类。这个方法一般被用于加载数据库的驱动,我们可以打开MySQL的驱动com.mysql.jdbc.Driver的源码看一下,可以发现如下代码:

1 
2
3
4
5
6
7
静的 { 試み {         java.sql.DriverManager.registerDriver(新しいドライバ());     } キャッチ(のSQLException E){ スロー新しい(のRuntimeExceptionの"ドライバを登録することはできません!");     } }






静的ブロック符号のみ初期化の場合に行うことができるように、使用することができるClass.forName()データベースドライバをロードする方法。あなたは、単にので、アクションクラスの初期化の不足のため、データベースドライバをロードするクラスローダを使用している場合は、ドライバがロードに失敗します。

参考:

  1. Javaのクラスローダ深度調査
  2. 深Java仮想マシン(元の本のバージョン2)
  3. 動的にロードされたクラスファイル - Javaは統合 - Javaの - ITeyeフォーラム
  4. ランジャーアプリケーションの参照は、他のjarパッケージの4つの方法です
  5. クラスローダクラスのロード処理を示しており、カスタムクラスローダ
  6. Javaのクラスローダ原則の深さ分析
  7. 方法および実施例を使用するURLClassLoader

おすすめ

転載: www.cnblogs.com/lijianming180/p/12389211.html