ホームページ Java &#&チュートリアル Java のクラス ローダー クラス ローダーの詳細な分析

Java のクラス ローダー クラス ローダーの詳細な分析

Jan 13, 2017 am 09:33 AM

クラスロードのプロセス
クラスローダーの主な仕事は、クラスファイルをJVMにロードすることです。以下の図に示すように、プロセスは 3 つのステップに分かれています:

1. ロードするクラス ファイルを見つけて、そのバイト ストリームを JVM にロードします。
2. 最も基本的なものをクラスに割り当てます。メモリ構造には、プロパティ、メソッド、参照クラスなどの情報が保持されます。この段階では、クラスはまだ使用できません。
(1) 検証: フォーマットやセキュリティなど、ロードされたバイト ストリームを検証します。
(2) メモリ割り当て: このクラスのメモリ領域を準備します。その属性、メソッド、および参照されるクラスを表します。
(3) 分析: 親クラス、実装されたインターフェイスなど、このクラスによって参照される他のクラスを読み込みます。
3. 初期化: クラス変数に値を代入します。

深入解析Java中的Class Loader类加载器

クラスローダーのレベル
下図の点線の上は、JDKが提供するいくつかの重要なクラスローダーです。詳細は次のとおりです:

(1) ブートストラップクラスローダー: メインを含むクラスを開始するとき。関数、JAVA_HOME/lib ディレクトリまたは -Xbootclasspath で指定されたディレクトリに Jar パッケージをロードします。
(2) 拡張クラスローダー: JAVA_HOME/lib/ext ディレクトリまたは -Djava.ext で指定されたディレクトリに Jar パッケージをロードします。先生たち。
(3) システム クラス ローダー: クラスパスまたは -Djava.class.path で指定されたディレクトリにクラスまたは jar パッケージをロードします。

深入解析Java中的Class Loader类加载器

知っておくべきことは次のとおりです:

1. ブートストラップ クラス ローダーを除き、他のクラス ローダーは java.lang.ClassLoader クラスのサブクラスです。パーソナライズされたクラス ローダーを使用しない場合、 java.lang.String.class.getClassLoader() は null になり、拡張クラス ローダーの親ローダーも null になります。
3. クラス ローダーを取得するいくつかの方法:
(1) ) ブートストラップ クラス ローダーの取得: ブートストラップ クラス ローダーを取得しようとすると、取得するものは null である必要があります。次の方法で確認できます。rt.jar パッケージ内のクラス オブジェクトの getClassLoader メソッド (java.lang.String.class.getClassLoader() など) を使用して拡張クラス ローダーを取得または取得し、 getParent メソッドを使用して取得します。
(2) 拡張クラス ローダーを取得する: JAVA_HOME/lib/ext ディレクトリの jar パッケージ内のクラス オブジェクトの getClassLoader メソッドを使用するか、最初にシステム クラス ローダーを取得してから、その getParent を通じて取得します。メソッド;
(3) システム クラス ローダーの取得: クラス オブジェクトの main 関数 getClassLoader メソッドを呼び出すか、main 関数で Thread.currentThread().getContextClassLoader() または ClassLoader.getSystemClassLoader() を呼び出します。ユーザー定義のクラス ローダー: クラス オブジェクトの getClassLoader メソッドを呼び出すか、Thread().getContextClassLoader(); クラス ローダーの動作原理
1. エージェンシーの原則
2.原則
3. 独自性原則

4. エージェンシー原則

エージェンシー原則が指すもの クラスローダーがクラスをロードするとき、その親ローダーはプロキシロードを要求し、親ローダーもその親ローダーにプロキシロードを要求します。以下の図に示すように。


プロキシ モードを使用する理由は何ですか?まず第一に、これによりクラスの繰り返しロードが軽減されます。 (他に理由はありますか?)





誤解されやすい:

一般に、クラスローダーの代理順序は親からであると考えられています。つまり:

1. クラスをロードするとき、クラスローダーはまずクラスがロードされているかどうかを確認し、ロードされていない場合は親ローダーにプロキシを依頼します。
2.ブートストラップ クラス ローダー
3. ブートストラップ クラス ローダーはクラスをロードしようとしますが、ロードが失敗した場合は ClassNotFoundException がスローされ、サブローダーがクラスをロードします。
4. サブクラスローダーは例外をキャッチした後にロードを試み、成功した場合は戻り、失敗した場合は ClassNotFoundException をスローします。
この理解は、ブートストラップ クラス ローダー、拡張クラス ローダー、システム クラス ローダーなどのローダーには正しいですが、一部のパーソナライズされたローダーは正しくありません。たとえば、IBM Web Sphere Portal Server によって実装される一部のクラス ローダーは、親ローダーです。ロードが失敗すると、親ローダーが呼び出されます。その理由は、特定のバージョンの log4j がすべてのアプリケーションで使用されることが予想される場合、WAS の起動時にそれを WAS_HOME ライブラリーに置くことです。それをロードしてください。アプリケーションが別のバージョンの log4j を使用したい場合、Parent First を使用するとこれを実現できません。これは、log4j 内のクラスがすでに親ローダーにロードされているためです。ただし、Parent Last を使用すると、アプリケーションのロードを担当するクラス ローダーが別のバージョンの log4j を最初にロードします。


可視性の原則
以下の図に示すように、クラスローダーに対する各クラスの可視性は異なります。

拡張知識として、OSGi はこの機能を利用しており、各バンドルは個別のクラス ローダーによってロードされるため、各クラス ローダーは特定のクラスのバージョンをロードできるため、システム全体で 1 つのクラスの複数のバージョンを使用できます。

深入解析Java中的Class Loader类加载器

<br/>
ログイン後にコピー

一意性の原則<br/>各クラスはローダーに最大 1 回ロードできます。

拡張知識 1: 正確には、シングルトン パターンで参照されるシングルトンは、クラス ローダーのグループ内の特定のクラスのオブジェクトの 1 つのコピーのみを指します。

拡張知識 2: クラスは複数のクラス ローダーによってロードできます。各クラス オブジェクトは独自の名前空間にあり、クラス オブジェクトを比較するとき、またはインスタンスで型変換を実行するとき、次のようにそれぞれの名前空間が同時に比較されます。

Klass クラスは、クラス オブジェクトが KlassA であると仮定すると、ClassLoaderA によってロードされます。また、クラス オブジェクトが KlassB であると仮定すると、ClassLoaderB によってもロードされます。その場合、KlassA は KlassB と等しくありません。同時に、ClassA のインスタンスが KlassB にキャストされると、ClassCastException がスローされます。 <br/>パーソナライズされたクラス ローダーの理由<br/> パーソナライズされたクラス ローダーは、Java 言語に多くの柔軟性を追加します。主な用途は次のとおりです:

1. クラスは、ネットワーク上、データベース内などの複数の場所からロードでき、さらには瞬時にコンパイルすることもできます。クラス ファイルを取得するためのソース ファイル <br/>2. パーソナライズされたクラス ローダーは、原則として実行時にクラス ファイルをロードできます。クラスローダーは、クラスをロードする前にクラスを復号化および圧縮解除できます。 <br/><br/>

クラスの暗黙的ロードと明示的ロード

暗黙的ロード: クラスが参照、継承、またはインスタンス化されるとき、ロードが失敗すると、NoClassDefFoundError がスローされます。 <br/><br/>明示的なロード: 次のメソッドを使用します。ロードが失敗した場合は、ClassNotFoundException がスローされます。

cl.loadClass()、cl はクラスローダーのインスタンスです。

Class.forName()、現在のクラスのクラスローダーを使用してロードします。

クラスの静的ブロックの実行<br/>次のクラスがあるとします:<br/>

package cn.fengd; 
  
public class Dummy { 
 static { 
 System.out.println("Hi"); 
 } 
}
ログイン後にコピー
<br/>別のテストクラスを作成します:

package cn.fengd; 
  
public class ClassLoaderTest { 
  
 public static void main(String[] args) throws InstantiationException, Exception { 
  
 try { 
  /* 
  * Different ways of loading. 
  */
  Class c = ClassLoaderTest.class.getClassLoader().loadClass("cn.fengd.Dummy"); 
 } catch (Exception e) { 
  // TODO Auto-generated catch block 
  e.printStackTrace(); 
 } 
 } 
  
}
ログイン後にコピー

実行後の効果は何ですか?

Hiは出力されません。 loadClass の使用後に Class オブジェクトが初期化されていないことがわかります。

Load ステートメントの後に c.newInstance(); を追加すると、Hi 出力があり、クラス オブジェクトはクラスがインスタンス化されたときにのみ初期化されます。

ローディングステートメントを変更した場合、 Class c = Class.forName("cn.fengd.Dummy", false, ClassLoader.getSystemClassLoader());

Hiは出力されません。パラメーター false は、このクラスのオブジェクトを初期化する必要がないことを意味するため、

Load ステートメントの後に c.newInstance(); を追加すると、Hi 出力が発生し、クラス オブジェクトはクラスがインスタンス化されます。

これを Class.forName("cn.fengd.Dummy") または new Dummy(); に変更したらどうなるでしょうか?

はHiを出力します。

よくある質問分析:

1.由不同的类加载器加载的指定类型还是相同的类型吗?<br/>在Java中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间.我们可以用两个自定义类加载器去加载某自定义类型(注意,不要将自定义类型的字节码放置到系统路径或者扩展路径中,否则会被系统类加载器或扩展类加载器抢先加载),然后用获取到的两个Class实例进行java.lang.Object.equals(…)判断,将会得到不相等的结果。这个大家可以写两个自定义的类加载器去加载相同的自定义类型,然后做个判断;同时,可以测试加载java.*类型,然后再对比测试一下测试结果。

2.在代码中直接调用Class.forName(String name)方法,到底会触发那个类加载器进行类加载行为?<br/>Class.forName(String name)默认会使用调用类的类加载器来进行类加载。我们直接来分析一下对应的jdk的代码:

//java.lang.Class.java
 
 publicstatic Class<?> forName(String className)throws ClassNotFoundException {
 
return forName0(className, true, ClassLoader.getCallerClassLoader());
 
}
 
//java.lang.ClassLoader.java
 
// Returns the invoker&#39;s class loader, or null if none.
 
static ClassLoader getCallerClassLoader() {
 
  // 获取调用类(caller)的类型
 
 Class caller = Reflection.getCallerClass(3);
 
  // This can be null if the VM is requesting it
 
 if (caller == null) {
 
  returnnull;
 
 }
 
 // 调用java.lang.Class中本地方法获取加载该调用类(caller)的ClassLoader
 
 return caller.getClassLoader0();
 
}
 
//java.lang.Class.java
 
//虚拟机本地实现,获取当前类的类加载器
native ClassLoader getClassLoader0();
ログイン後にコピー

3.在编写自定义类加载器时,如果没有设定父加载器,那么父加载器是?<br/>在不指定父类加载器的情况下,默认采用系统类加载器。可能有人觉得不明白,现在我们来看一下JDK对应的代码实现。众所周知,我们编写自定义的类加载器直接或者间接继承自java.lang.ClassLoader抽象类,对应的无参默认构造函数实现如下:

//摘自java.lang.ClassLoader.java
protected ClassLoader() {
 
  SecurityManager security = System.getSecurityManager();
 
  if (security != null) {
 
  security.checkCreateClassLoader();
 
  }
 
  this.parent = getSystemClassLoader();
 
  initialized = true;
 
}
ログイン後にコピー

我们再来看一下对应的getSystemClassLoader()方法的实现:

privatestaticsynchronizedvoid initSystemClassLoader() {
 
  //...
 
  sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
 
  scl = l.getClassLoader();
 
  //...
 
}
ログイン後にコピー

我们可以写简单的测试代码来测试一下:

System.out.println(sun.misc.Launcher.getLauncher().getClassLoader());
ログイン後にコピー

本机对应输出如下:

sun.misc.Launcher$AppClassLoader@197d257
ログイン後にコピー

所以,我们现在可以相信当自定义类加载器没有指定父类加载器的情况下,默认的父类加载器即为系统类加载器。同时,我们可以得出如下结论:

即时用户自定义类加载器不指定父类加载器,那么,同样可以加载如下三个地方的类:

(1)/lib下的类

(2)< Java_Runtime_Home >/lib/ext下或者由系统变量java.ext.dir指定位置中的类

(3)当前工程类路径下或者由系统变量java.class.path指定位置中的类

4.在编写自定义类加载器时,如果将父类加载器强制设置为null,那么会有什么影响?如果自定义的类加载器不能加载指定类,就肯定会加载失败吗?<br/>JVM规范中规定如果用户自定义的类加载器将父类加载器强制设置为null,那么会自动将启动类加载器设置为当前用户自定义类加载器的父类加载器(这个问题前面已经分析过了)。同时,我们可以得出如下结论:<br/>即时用户自定义类加载器不指定父类加载器,那么,同样可以加载到/lib下的类,但此时就不能够加载/lib/ext目录下的类了。<br/> 说明:问题3和问题4的推断结论是基于用户自定义的类加载器本身延续了java.lang.ClassLoader.loadClass(…)默认委派逻辑,如果用户对这一默认委派逻辑进行了改变,以上推断结论就不一定成立了,详见问题5。

5.编写自定义类加载器时,一般有哪些注意点?<br/>(1)一般尽量不要覆写已有的loadClass(…)方法中的委派逻辑<br/>一般在JDK 1.2之前的版本才这样做,而且事实证明,这样做极有可能引起系统默认的类加载器不能正常工作。在JVM规范和JDK文档中(1.2或者以后版本中),都没有建议用户覆写loadClass(…)方法,相比而言,明确提示开发者在开发自定义的类加载器时覆写findClass(…)逻辑。举一个例子来验证该问题:

//用户自定义类加载器WrongClassLoader.Java(覆写loadClass逻辑)
publicclassWrongClassLoaderextends ClassLoader {
 
 public Class<?> loadClass(String name) throws ClassNotFoundException {
 
  returnthis.findClass(name);
 
 }
 
 protected Class<?> findClass(String name) throws ClassNotFoundException {
 
  //假设此处只是到工程以外的特定目录D:/library下去加载类
 
  具体实现代码省略
 
 }
 
}
ログイン後にコピー

通过前面的分析我们已经知道,用户自定义类加载器(WrongClassLoader)的默

认的类加载器是系统类加载器,但是现在问题4种的结论就不成立了。大家可以简

单测试一下,现在/lib、< Java_Runtime_Home >/lib/ext和工

程类路径上的类都加载不上了。

问题5测试代码一

publicclass WrongClassLoaderTest {
 
 publicstaticvoid main(String[] args) {
 
  try {
 
  WrongClassLoader loader = new WrongClassLoader();
 
  Class classLoaded = loader.loadClass("beans.Account");
 
  System.out.println(classLoaded.getName());
 
  System.out.println(classLoaded.getClassLoader());
 
  } catch (Exception e) {
 
  e.printStackTrace();
 
  }
 
 }
 
}
ログイン後にコピー

(说明:D:"classes"beans"Account.class物理存在的)

输出结果:

java.io.FileNotFoundException: D:"classes"java"lang"Object.class (系统找不到指定的路径。)
 
 at java.io.FileInputStream.open(Native Method)
 
 at java.io.FileInputStream.<init>(FileInputStream.java:106)
 
 at WrongClassLoader.findClass(WrongClassLoader.java:40)
 
 at WrongClassLoader.loadClass(WrongClassLoader.java:29)
 
 at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
 
 at java.lang.ClassLoader.defineClass1(Native Method)
 
 at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
 
 at java.lang.ClassLoader.defineClass(ClassLoader.java:400)
 
 at WrongClassLoader.findClass(WrongClassLoader.java:43)
 
 at WrongClassLoader.loadClass(WrongClassLoader.java:29)
 
 at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)
 
Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object
 
 at java.lang.ClassLoader.defineClass1(Native Method)
 
 at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
 
 at java.lang.ClassLoader.defineClass(ClassLoader.java:400)
 
 at WrongClassLoader.findClass(WrongClassLoader.java:43)
 
 at WrongClassLoader.loadClass(WrongClassLoader.java:29)
 
 at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)
ログイン後にコピー

这说明,连要加载的类型的超类型java.lang.Object都加载不到了。这里列举的由于覆写loadClass(…)引起的逻辑错误明显是比较简单的,实际引起的逻辑错误可能复杂的多。<br/>问题5测试二

//用户自定义类加载器WrongClassLoader.Java(不覆写loadClass逻辑)
publicclassWrongClassLoaderextends ClassLoader {
 
 protected Class<?> findClass(String name) throws ClassNotFoundException {
 
  //假设此处只是到工程以外的特定目录D:/library下去加载类
 
  具体实现代码省略
 
 }
 
}
ログイン後にコピー

将自定义类加载器代码WrongClassLoader.Java做以上修改后,再运行测试代码,输出结果如下:

beans.Account
 
WrongClassLoader@1c78e57
ログイン後にコピー

这说明,beans.Account加载成功,且是由自定义类加载器WrongClassLoader加载。

这其中的原因分析,我想这里就不必解释了,大家应该可以分析的出来了。

(2)正确设置父类加载器<br/>通过上面问题4和问题5的分析我们应该已经理解,个人觉得这是自定义用户类加载器时最重要的一点,但常常被忽略或者轻易带过。有了前面JDK代码的分析作为基础,我想现在大家都可以随便举出例子了。<br/>(3)保证findClass(String )方法的逻辑正确性<br/>事先尽量准确理解待定义的类加载器要完成的加载任务,确保最大程度上能够获取到对应的字节码内容。

6.如何在运行时判断系统类加载器能加载哪些路径下的类?<br/>一是可以直接调用ClassLoader.getSystemClassLoader()或者其他方式获取到系统类加载器(系统类加载器和扩展类加载器本身都派生自URLClassLoader),调用URLClassLoader中的getURLs()方法可以获取到;

二是可以直接通过获取系统属性java.class.path 来查看当前类路径上的条目信息 , System.getProperty("java.class.path")

7.如何在运行时判断标准扩展类加载器能加载哪些路径下的类?<br/>方法之一:

try {
  URL[] extURLs = ((URLClassLoader)ClassLoader.getSystemClassLoader().getParent()).getURLs();
 
  for (int i = 0; i < extURLs.length; i++) {
 
   System.out.println(extURLs[i]);
 
  }
 
 } catch (Exception e) {//…}
ログイン後にコピー

本机对应输出如下:

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/dnsns.jar
 
file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/localedata.jar
 
file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunjce_provider.jar
 
file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunpkcs11.jar
ログイン後にコピー

更多深入解析Java中的Class Loader类加载器相关文章请关注PHP中文网!

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

2025年のトップ4 JavaScriptフレームワーク:React、Angular、Vue、Svelte 2025年のトップ4 JavaScriptフレームワーク:React、Angular、Vue、Svelte Mar 07, 2025 pm 06:09 PM

この記事では、2025年の上位4つのJavaScriptフレームワーク(React、Angular、Vue、Svelte)を分析し、パフォーマンス、スケーラビリティ、将来の見通しを比較します。 強力なコミュニティと生態系のためにすべてが支配的なままですが、彼らの相対的なポップ

Spring Boot Snakeyaml 2.0 CVE-2022-1471問題修正 Spring Boot Snakeyaml 2.0 CVE-2022-1471問題修正 Mar 07, 2025 pm 05:52 PM

この記事では、リモートコードの実行を可能にする重大な欠陥であるSnakeyamlのCVE-2022-1471の脆弱性について説明します。 Snakeyaml 1.33以降のSpring Bootアプリケーションをアップグレードする方法は、このリスクを軽減する方法を詳述し、その依存関係のアップデートを強調しています

Javaのクラスロードメカニズムは、さまざまなクラスローダーやその委任モデルを含むどのように機能しますか? Javaのクラスロードメカニズムは、さまざまなクラスローダーやその委任モデルを含むどのように機能しますか? Mar 17, 2025 pm 05:35 PM

Javaのクラスロードには、ブートストラップ、拡張機能、およびアプリケーションクラスローダーを備えた階層システムを使用して、クラスの読み込み、リンク、および初期化が含まれます。親の委任モデルは、コアクラスが最初にロードされ、カスタムクラスのLOAに影響を与えることを保証します

カフェインやグアバキャッシュなどのライブラリを使用して、Javaアプリケーションにマルチレベルキャッシュを実装するにはどうすればよいですか? カフェインやグアバキャッシュなどのライブラリを使用して、Javaアプリケーションにマルチレベルキャッシュを実装するにはどうすればよいですか? Mar 17, 2025 pm 05:44 PM

この記事では、カフェインとグアバキャッシュを使用してJavaでマルチレベルキャッシュを実装してアプリケーションのパフォーマンスを向上させています。セットアップ、統合、パフォーマンスの利点をカバーし、構成と立ち退きポリシー管理Best Pra

node.js 20:キーパフォーマンスが向上し、新機能 node.js 20:キーパフォーマンスが向上し、新機能 Mar 07, 2025 pm 06:12 PM

node.js 20は、V8エンジンの改善、特により速いガベージコレクションとI/Oを介してパフォーマンスを大幅に向上させます。 新機能には、より良いWebセンブリのサポートと洗練されたデバッグツール、開発者の生産性とアプリケーション速度の向上が含まれます。

Iceberg:データレイクテーブルの未来 Iceberg:データレイクテーブルの未来 Mar 07, 2025 pm 06:31 PM

大規模な分析データセットのオープンテーブル形式であるIcebergは、データの湖のパフォーマンスとスケーラビリティを向上させます。 内部メタデータ管理を通じて、寄木細工/ORCの制限に対処し、効率的なスキーマの進化、タイムトラベル、同時wを可能にします

キュウリのステップ間でデータを共有する方法 キュウリのステップ間でデータを共有する方法 Mar 07, 2025 pm 05:55 PM

この記事では、キュウリの手順間でデータを共有する方法、シナリオコンテキスト、グローバル変数、引数の合格、およびデータ構造を比較する方法を調べます。 簡潔なコンテキストの使用、記述など、保守性のためのベストプラクティスを強調しています

Javaで機能的なプログラミング技術を実装するにはどうすればよいですか? Javaで機能的なプログラミング技術を実装するにはどうすればよいですか? Mar 11, 2025 pm 05:51 PM

この記事では、Lambda式、Streams API、メソッド参照、およびオプションを使用して、機能プログラミングをJavaに統合することを調べます。 それは、簡潔さと不変性を通じてコードの読みやすさと保守性の改善などの利点を強調しています

See all articles