あなたは、Javaクラスがそれをロードする方法を知っていますか?

ワン:はじめに

最近、友人の指示の下で非Javaを与えた親の委譲モデルに語った、友人が深さ研究論文でクラスローダJVMを書くために私に尋ねた、と確かに私はJVM関連の記事を書いていない長い時間、少し手のかゆみのために、またパイヤンPingの抑制を描い住んでいません。

私は友人に説明するとそう言う:両親のデリゲートモデル、クラスローダにロードするクラスを、最初の親クラスローダ負荷の場合は、失敗したときにのみロードされた親クラスローダと呼ばれます、自分自身をロードしようとします。これは、クラス多重部を可能にし、異なるクラスローダロードされたクラスは、互いに分離されているので、分離部は、クラスを実装することができます。

しかし、他の人が急いで両親の委任モデルに説明があれば、JVMのクラスローディング機構を知らなくても、間違っているが、また、どのようによく理解バイブル「は、異なるクラスローダロードされたクラスは、互いに分離されていますか」?だから、両親デリゲートを理解するために、最善の方法は、最初のクラスローダの下でロードプロセスを理解することです。

2:Javaクラスがどのようにロードされます

2.1:クラスをロードするとき
、彼らがロードされます際には、まず、Javaクラスを知っている必要がありますか?

「Java仮想マシンの深い理解は、」与えられた答えは:

1:新しい、getstatic、putstaticや他の命令を経験。
2:クラスは、反射時間と呼ばれています。
3:クラスのサブクラスを開始します。
4:仮想マシンの起動時にメインクラスローダは最初に設定されます。
5:1.7倍動的言語のサポートJDKを使用します。
時間はこのクラスの操作中に必要な場合は:実際には、私はほとんどのユーザーフレンドリーな答えがあると思います。

その後、我々は、クラスをロードする方法について話し始めるかもしれません。

2.2:どのようにクラスをロードするために
クラスをロードするクラスローダを使用することが非常に簡単で、ダイレクトコールのloadClassのClassLoder()メソッド、私たちは信じていますが、それでも栗を与えることを持つことができます。

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        Test.class.getClassLoader().loadClass("com.wangxiandeng.test.Dog");
    }
}复制代码

クラスローダは「com.wangxiandeng.test.Dog」をロードするようになるように上記のコードでは、このクラスはそう簡単ではありません達成されるであろう。しかし、氷山の一角が提供するAPI JDKは、それを行うのはほとんどが、パッケージのAPIを発見、それをチェックアウトすることであるように非常に単純な呼び出しは、実際には、詳細、私の多くの人々を隠しようです。

2.3:JVMは、クラスロードする方法である
ユーザプログラムをロードするために使用されるJVMのデフォルトのClassLoaderはAppClassLoaderですが、どんなクラスローダは、ルートクラスは、java.lang.ClassLoaderのをされません。上記の例では、のloadClass()メソッドは、最終的にはネイティブメソッドであるClassLoader.definClass1()の呼び出し。

static native Class<?> defineClass1(ClassLoader loader, String name, byte[] b, int off, int len,
                                        ProtectionDomain pd, String source); 复制代码

Moはしどろもどろネイティブメソッドを参照してください、OpenJDKのオープンソース、心配、と私はある飛行を続行しないでください!

definClass1()JNI方法Java_java_lang_ClassLoader_defineClass1(に相当)

JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass1(JNIEnv *env,
                                        jclass cls,
                                        jobject loader,
                                        jstring name,
                                        jbyteArray data,
                                        jint offset,
                                        jint length,
                                        jobject pd,
                                        jstring source)
{
    ......
    result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource);
    ......
    return result;
}复制代码

主にソースコードと一緒に下る、クラスをロードするために)(JVM_DefineClassWithSourceと呼ばれるJava_java_lang_ClassLoader_defineClass1は、あなたが最終呼び出しがjvm_define_class_common()メソッドでjvm.cppあるでしょう。

static jclass jvm_define_class_common(JNIEnv *env, const char *name,
                                      jobject loader, const jbyte *buf,
                                      jsize len, jobject pd, const char *source,
                                      TRAPS) {
  ......
  ClassFileStream st((u1*)buf, len, source, ClassFileStream::verify);
  Handle class_loader (THREAD, JNIHandles::resolve(loader));
  if (UsePerfData) {
    is_lock_held_by_thread(class_loader,
                           ClassLoader::sync_JVMDefineClassLockFreeCounter(),
                           THREAD);
  }
  Handle protection_domain (THREAD, JNIHandles::resolve(pd));
  Klass* k = SystemDictionary::resolve_from_stream(class_name,
                                                   class_loader,
                                                   protection_domain,
                                                   &st,
                                                   CHECK_NULL);
  ......

  return (jclass) JNIHandles::make_local(env, k->java_mirror());
}复制代码

このロジックは、ファイルストリームにクラスファイルをロードするために上記メインClassFileStreamの使用で、その後、SystemDictionaryを呼び出す:: resolve_from_stream()、JVMクラスの代表の生成:クラースを。クラースのために、彼らは馴染みがないかもしれませんが、ここで彼を降りてきました。端的に言えば、データ構造のJVM Javaクラスを定義するために使用されます。クラースだけ基底クラスは、Javaクラス真のデータ構造はInstanceKlassで定義されています。

class InstanceKlass: public Klass {
 
 protected:
 
  Annotations*    _annotations;
  ......
  ConstantPool* _constants;
  ......
  Array<jushort>* _inner_classes;
  ......
  Array<Method*>* _methods;
  Array<Method*>* _default_methods;
  ......
  Array<u2>*      _fields;
}复制代码

ノート、メソッド、フィールド、内部クラス、定数プール情報を含むJavaクラスのすべてのプロパティに記録可視InstanceKlass。この情報は、もともとように、InstanceKlassは、Javaクラスファイルはフォームの後にメモリにロードされている、クラスファイルに記録されました。

Backクラスのロード処理の先頭に、ここSystemDictionary :: resolve_from_stream()と呼ばれる、メモリクラスクラースにファイルをロードします。

resolve_from_stream()は最も重要です!メインロジックは、以下のステップがあります。

1:パラレル・ロード・クラスを許可するか否かを判定し、判定結果に応じてロックします。

bool DoObjectLock = true;
if (is_parallelCapable(class_loader)) {
  DoObjectLock = false;
}
ClassLoaderData* loader_data = register_loader(class_loader, CHECK_NULL);
Handle lockObject = compute_loader_lock_object(class_loader, THREAD);
check_loader_lock_contention(lockObject, THREAD);
ObjectLocker ol(lockObject, THREAD, DoObjectLock);复制代码

あなたは、並列ロードを許可した場合、クラスローダは、唯一のロックSystemDictionaryをロックされません。それ以外の場合は、クラスローダは、あなただけのクラスをロードすることができ、同時に同じことを保証する、ClassLoaderをロックするObjectLockerを使用します。ObjectLockerは、コンストラクタとデストラクタのリリースのロックロックを取得します。

ロックの粒度の並列ロードの利点は結構ですので、あなたが同じタイムクラスに複数のファイルを読み込むことができます許可します。

2:InstanceKlassを生成するために、ファイルストリームを解析します。

InstanceKlass* k = NULL;

k = KlassFactory::create_from_stream(st,
                                         class_name,
                                         loader_data,
                                         protection_domain,
                                         NULL, // host_klass
                                         NULL, // cp_patches
                                         CHECK_NULL);复制代码

3:使用クラースSystemDictionary生成登録。

SystemDictionaryクラス情報をロードしたクラスローダをオフに保持するために使用されます。正確には、SystemDictionaryは、コンテナ、クラス情報についても同様である辞書の容器は、各ClassLoaderDataは、プライベート辞書に保存されていない、とSystemDictionaryだけのツールクラスは静的メソッドのみをたくさん持っています。

のは、登録コードを見てみましょう:

if (is_parallelCapable(class_loader)) {
  InstanceKlass* defined_k = find_or_define_instance_class(h_name, class_loader, k, THREAD);
  if (!HAS_PENDING_EXCEPTION && defined_k != k) {
    // If a parallel capable class loader already defined this class, register 'k' for cleanup.
    assert(defined_k != NULL, "Should have a klass if there's no exception");
    loader_data->add_to_deallocate_list(k);
    k = defined_k;
  }
} else {
  define_instance_class(k, THREAD);
}复制代码

あなたは、並列ロードを許可した場合、同時に、それは同じクラスファイルに対して複数回ロードすることができるので、フロントには、クラスローダロックされていません。最初SystemDictionaryクラスローダがすでに同じクラスに取り付けられたかどうかをクエリが使用されますので、しかし、同じクラスは、同じクラスローダ内で一意でなければなりません。

すでにロードされている場合は、それが現在のスレッドがちょうどロードされます回復リストに追加するInstanceKlass、およびInstanceKlass *は、kはSystemDictionary InstanceKlassを使用するようにクエリをリダイレクト。
あなたがいずれかを見つけることができない場合には、それだけで辞書のClassLoaderに登録InstanceKlassをロードします。
並列ロードがClassLoaderをロックしますが、SystemDictionary InstanceKlassの登録時にロックされていないが、登録時にInstanceKlass並行操作を心配する必要はありません。

あなたはパラレル・ロードを無効にした場合、その後、SystemDictionaryの直接の使用は辞書クラスローダに登録InstanceKlassますすることができます。

メインプロセスのresolve_from_streamは()上記の3つのステップで、最も重要なことは、第二のステップは、ファイルからフロー生成InstanceKlassであることは明らかです。

InstanceKlassコールは、メインロジックコードを下回っているKlassFactory :: create_from_stream()メソッドを、生成されます。

ClassFileParser parser(stream,
                       name,
                       loader_data,
                       protection_domain,
                       host_klass,
                       cp_patches,
                       ClassFileParser::BROADCAST, // publicity level
                       CHECK_NULL);

InstanceKlass* result = parser.create_instance_klass(old_stream != stream, CHECK_NULL);复制代码

元ClassFileParserは本当の英雄ああです!それは兄の後ろにInstanceKlassに昇華クラスファイルです!

2.4:ClassFileParser言っている
)(ClassFileParserロード入り口はcreate_instance_klassクラスファイルです。名前が示すように、InstanceKlassを作成するために使用されます。

create_instance_klass()は二つの主要なものをやっているだろう。

(1):InstanceKlassためのメモリを割り当てます

InstanceKlass* const ik =
    InstanceKlass::allocate_instance_klass(*this, CHECK_NULL);复制代码

(2):クラス分析ファイルメモリ領域がInstanceKlass満たされています

fill_instance_klass(IK、changed_by_loadhook、CHECK_NULL) ;
私たちはInstanceKlassにメモリを割り当てるために、最初の道の最初の事を言いました。

メモリ割り当てのコードは次のとおりです。

const int size = InstanceKlass::size(parser.vtable_size(),
                                       parser.itable_size(),
                                       nonstatic_oop_map_size(parser.total_oop_map_count()),
                                       parser.is_interface(),
                                       parser.is_anonymous(),
                                       should_store_fingerprint(parser.is_anonymous()));
ClassLoaderData* loader_data = parser.loader_data();
InstanceKlass* ik;
ik = new (loader_data, size, THREAD) InstanceKlass(parser, InstanceKlass::_misc_kind_other);复制代码

まず、サイズ、ここでのメモリでInstanceKlassを計算し、あなたが知っている、このサイズは、後にコンパイル済みのクラスファイルで決定されます。

そして、それは新しいInstanceKlassオブジェクトを新しいです。new演算子のためのクラースが過負荷になっていることに留意すべきである、ヒープ上に割り当てられた単純なメモリではありません。

void* Klass::operator new(size_t size, ClassLoaderData* loader_data, size_t word_size, TRAPS) throw() {
  return Metaspace::allocate(loader_data, word_size, MetaspaceObj::ClassType, THREAD);
}复制代码

コール分布InstanceKlassメタスペースは::)(割り当てた場合:

                              MetaspaceObj::Type type, TRAPS) {
  ......
  MetadataType mdtype = (type == MetaspaceObj::ClassType) ? ClassType : NonClassType;
  ......
  MetaWord* result = loader_data->metaspace_non_null()->allocate(word_size, mdtype);
  ......
  return result;
}复制代码

したがって、InstanceKlassメソッド領域は、クラスローダのメタスペース(素子空間)に分配されます。JDK8は初めから、ホットスポットは、恒久的な世代が、クラスがでメタスペースを割り当てられませんがあります。メタスペースと永続的な世代は、ネイティブメモリ、メモリがメモリ不足には十分ではありませんMaxPermSizeを、によって制限される恒久的な代理を使用して、同じではありません。

InstanceKlassメモリ割り当ての後、我々はInstanceKlassメモリ領域を埋める、二つ目、クラスのドキュメントの分析を開始する必要があります。

建設は、クラスファイルの分析を開始、そうfill_instance_klass()しますClassFileParserだけで埋める必要があります。充填後、それは、Java層でInstanceKlass Classオブジェクトを作成するためにjava_lang_Class :: CREATE_MIRROR()を呼び出します。

void ClassFileParser::fill_instance_klass(InstanceKlass* ik, bool changed_by_loadhook, TRAPS) {
  .....
  ik->set_class_loader_data(_loader_data);
  ik->set_nonstatic_field_size(_field_info->nonstatic_field_size);
  ik->set_has_nonstatic_fields(_field_info->has_nonstatic_fields);
  ik->set_static_oop_field_count(_fac->count[STATIC_OOP]);
  ik->set_name(_class_name);
  ......

  java_lang_Class::create_mirror(ik,
                                 Handle(THREAD, _loader_data->class_loader()),
                                 module_handle,
                                 _protection_domain,
                                 CHECK);
}复制代码

ちなみに、クラスファイル構造のための学生に精通していない、2年前に見ることができ、私は記事を書きました:

「王氏:JVMが唯一のJavaクラスファイルを解析します」

ここでは、クラスのドキュメントには、活力InstanceKlassのフルメモリに、冷たいバイナリファイルで、壮大なターンを完了しました。

3:親は代理人に話します

あなたは辛抱強く上記のソースコード解析を読めば、あなたは「別のクラスローダロードされたクラスは、互いに分離されている」にする必要があり、それは理解し、より高いレベルにあります。

私たちは、要約:各クラスローダは、それがロードされInstanceKlass情報を保存するための辞書を持っています。そして、同じクラスのために、それだけで彼の辞書にInstanceKlassを登録することを確実にするためにロックを介して各クラスローダ。

上記のこれらの理由のための公式、自分のファイルでクラスをロードするために、すべてのクラスローダ場合、それは同じクラスファイルにつながる、そこに複数のコピーInstanceKlassがあるので、異なるInstanceKlasssインスタンスタイプから派生したクラスファイルは、同じでなくてもとA。

栗のために、私たちは親委任モデルを破るために使用されるClassLoaderを、カスタマイズします。

public class CustomClassloader extends URLClassLoader {

    public CustomClassloader(URL[] urls) {
        super(urls);
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        if (name.startsWith("com.wangxiandeng")) {
            return findClass(name);
        }
        return super.loadClass(name, resolve);
    }
}复制代码

クラスをロードし、インスタンス化しようとし、その後Studenの:

public class Test {

    public static void main(String[] args) throws Exception {
        URL url[] = new URL[1];
        url[0] = Thread.currentThread().getContextClassLoader().getResource("");

        CustomClassloader customClassloader = new CustomClassloader(url);
        Class clazz = customClassloader.loadClass("com.wangxiandeng.Student");

        Student student = (Student) clazz.newInstance();
    }
}复制代码

強力なターンを実行した後、例外タイプがスローされます。

Exception in thread "main" java.lang.ClassCastException:
      com.wangxiandeng.Student cannot be cast to com.wangxiandeng.Student复制代码

なぜ?

私たちは、生成されたシステムのデフォルトのClassLoaderでInstanceKlassに対応する強力なタイプのStudent.ClassをオンロードCustomClassLoaderによって属するインスタンス化InstanceKlass学生オブジェクトが生成されるため、そして、彼らは、2つの無関係InstanceKlass自然ですもちろん、ない強力なターン。

一部の学生は尋ねた:「システムのデフォルトのClassLoaderでInstanceKlassに対応する強力なターンタイプStudent.Classが生成された」なぜ?
実際には、非常に単純な、我々は次のバイトコードを逆コンパイル:

  public static void main(java.lang.String[]) throws java.lang.Exception;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=5, args_size=1
         0: iconst_1
         1: anewarray     #2                  // class java/net/URL
         4: astore_1
         5: aload_1
         6: iconst_0
         7: invokestatic  #3                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        10: invokevirtual #4                  // Method java/lang/Thread.getContextClassLoader:()Ljava/lang/ClassLoader;
        13: ldc           #5                  // String
        15: invokevirtual #6                  // Method java/lang/ClassLoader.getResource:(Ljava/lang/String;)Ljava/net/URL;
        18: aastore
        19: new           #7                  // class com/wangxiandeng/classloader/CustomClassloader
        22: dup
        23: aload_1
        24: invokespecial #8                  // Method com/wangxiandeng/classloader/CustomClassloader."<init>":([Ljava/net/URL;)V
        27: astore_2
        28: aload_2
        29: ldc           #9                  // String com.wangxiandeng.Student
        31: invokevirtual #10                 // Method com/wangxiandeng/classloader/CustomClassloader.loadClass:(Ljava/lang/String;)Ljava/lang/Class;
        34: astore_3
        35: aload_3
        36: invokevirtual #11                 // Method java/lang/Class.newInstance:()Ljava/lang/Object;
        39: checkcast     #12                 // class com/wangxiandeng/Student
        42: astore        4
        44: return复制代码

初期化は、クラスローディングの例を使用して見ることができる後、checkcastインデックス#12の後に型変換呼び出しcheckcastは、クラスの定数プールにおける学生オペランドです。

#12 =クラスの#52 // COM / wangxiandeng /学生
今、私たちは、ホットスポットでの実装checkcastで見ることができます。
ホットスポットは現在、テンプレートインタプリタで使用されて、私はこの記事を見てできる3つのバイトコード実行エンジンに提供されています:「王氏:JVMのテンプレートインタプリタ」
初期には、HotSpotのバイトコードインタプリタを使用しています。命令の実行のためのテンプレートインタプリタがアセンブラで記述されており、より快適に見るために行ってC ++を使用して、バイトコードインタプリタを翻訳され、我々は、コンパイル、ライン上のバイトコードインタプリタで直接見ては表示されません。私は記事を書いた前にアセンブラのスキルが良く、もちろん、あなたはまた、直接テンプレートインタプリタを見ることができた場合は、「王氏:JVMは、ソースコード解析のオブジェクトを作成し、」ここに新しい命令のために達成したテンプレートインタプリタの分析です。

ADOは、bytecodeInterpreter.cppのコード、のcheckcastを達成するためのバイトコードインタプリタを見てみましょう

CASE(_checkcast):
    if (STACK_OBJECT(-1) != NULL) {
      VERIFY_OOP(STACK_OBJECT(-1));
      // 拿到 checkcast 指令后的操作数,本例子中即 Student.Class 在常量池中的索引:#12
      u2 index = Bytes::get_Java_u2(pc+1);
      
      // 如果常量池还没有解析,先进行解析,即将常量池中的符号引用替换成直接引用,
      //此时就会触发Student.Class 的加载
      if (METHOD->constants()->tag_at(index).is_unresolved_klass()) {
        CALL_VM(InterpreterRuntime::quicken_io_cc(THREAD), handle_exception);
      }
      // 获取上一步系统加载的Student.Class 对应的 InstanceKlass
      Klass* klassOf = (Klass*) METHOD->constants()->resolved_klass_at(index);
      // 获取要强转的对象的实际类型,即我们自己手动加载的Student.Class 对应的 InstanceKlass
      Klass* objKlass = STACK_OBJECT(-1)->klass(); // ebx
     
      // 现在就比较简单了,直接看看上面的两个InstanceKlass指针内容是否相同
      // 不同的情况下则判断是否存在继承关系
      if (objKlass != klassOf && !objKlass->is_subtype_of(klassOf)) {
        // Decrement counter at checkcast.
        BI_PROFILE_SUBTYPECHECK_FAILED(objKlass);
        ResourceMark rm(THREAD);
        char* message = SharedRuntime::generate_class_cast_message(
          objKlass, klassOf);
        VM_JAVA_ERROR(vmSymbols::java_lang_ClassCastException(), message, note_classCheck_trap);
      }
      // Profile checkcast with null_seen and receiver.
      BI_PROFILE_UPDATE_CHECKCAST(/*null_seen=*/false, objKlass);
    } else {
      // Profile checkcast with null_seen and receiver.
      BI_PROFILE_UPDATE_CHECKCAST(/*null_seen=*/true, NULL);
    }复制代码

上記のコードの分析を通じて、私たちは、この文を「生成されたシステムのデフォルトのClassLoaderでInstanceKlassに対応する強力なターンタイプStudent.Class」を理解していると信じています。

両親のメリットを委任すると、同じファイルがクラスInstanceKlassを生成することを保証しようとすることですが、いくつかのケースでは、我々が壊れた委任両親のために行く必要が、例えば、私たちは、クラスの分離時間を達成したいです。

ストリートフルートの学生の質問にはお答え:

//定数プールが解決されていない場合は、最初の解析定数プールは、シンボリックリファレンスへの直接参照を交換しようとしている、
//今回はStudent.Class負荷をトリガーする
場合(METHOD->定数() - > tag_at(インデックス).is_unresolved_klass()){
CALL_VM(InterpreterRuntime :: quicken_io_cc(THREAD)、handle_exceptionは);
}
私は尋ねる、なぜここStudent.Classをリロードしますか?JVMが自分のクラスがリンクをロードされていない場合、システムはクラスロードされているかどうかを確認するためのリンクをたどりますか?そして、CustomClassloaderをカスタマイズする方法を行か問い合わせリンクに追加?

最初の方法:パラメータのJava設定を開始-Djava.system.class.loader

第二の方法:使用Thread.setContextClassLoder

ここでは少しトリッキーで、コードを見て:

public class Test {

    public static void main(String[] args) throws Exception {
        URL url[] = new URL[1];
        url[0] = Thread.currentThread().getContextClassLoader().getResource("");
        final CustomClassloader customClassloader = new CustomClassloader(url);
        Thread.currentThread().setContextClassLoader(customClassloader);
        Class clazz = customClassloader.loadClass("com.wangxiandeng.ClassTest");
        Object object = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("test");
        method.invoke(object);
    }
}
public class ClassTest {

    public void test() throws Exception{
        Class clazz = Thread.currentThread().getContextClassLoader().loadClass("com.wangxiandeng.Student");
        Student student = (Student) clazz.newInstance();
        System.out.print(student.getClass().getClassLoader());

    }
}复制代码

スレッドのクラスローダセットした後、直接新しいClassTestは()を呼び出していないことに注意してください。テストを()。なぜ?定数プールの解析時に直接強い参照が、その後、Test.Classますので、順番に分析ClassTest.ClassをトリガーClassTestはロードされたシステムのデフォルトのClassLoaderを使用します。、これを防ぐために本明細書で使用ClassTest.Class CustomClassLoader、リサイクル反射テストコール()、ケースClassTest.Class定数プールを解析するとき、クラスCustomClassLoader定数プールエントリをロードするために使用するをロードすること、およびこれは、発生した例外ではありません。

4:要約

この記事を終了し、手が痒くていない、非常にクール!両親の代理人が戻っ委任について親に最終的には負荷クラスファイルのスポーク、およびからこの記事では、より良い親がに似たようなメカニズムを委任理解するためにご理解の唯一のローディング機構クラス、または単に丸暗記、実際には、少し周りのようです空の理論のいくつかの丸暗記で、理解裏返しに再生することはできません。


著者:ミドルウェアの弟

説明リンク

この記事Yunqiコミュニティのオリジナルコンテンツが許可なく複製することはできません。


おすすめ

転載: juejin.im/post/5d47eb8de51d4561ad654866