jvm(3) 类文件的加载

具体描述过程需要参考jvm规范,本文只是追了一下代码
https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-5.html#jvms-5.3

类加载,jvm启动后,对必须要的类进行了初始化,main方法运行加载时需要LauncherHelper
这里主要看了一下系统类加载器加载类过程,class文件读取成vm对象后进行了java class对象的转换,后续java才能使用类的class信息进行初始化

jclass
GetLauncherHelperClass(JNIEnv *env)
{
    if (helperClass == NULL) {
        NULL_CHECK0(helperClass = FindBootStrapClass(env,
                "sun/launcher/LauncherHelper"));
    }
    return helperClass;
}

//find的实现
static FindClassFromBootLoader_t *findBootClass = NULL;

jclass
FindBootStrapClass(JNIEnv *env, const char* classname)
{
   if (findBootClass == NULL) {
       findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,
          "JVM_FindClassFromBootLoader");
       if (findBootClass == NULL) {
           JLI_ReportErrorMessage(DLL_ERROR4,
               "JVM_FindClassFromBootLoader");
           return NULL;
       }
   }
   return findBootClass(env, classname);
}
//连接到jvm.cpp
JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,
                                              const char* name))
  // Java libraries should ensure that name is never null or illegal.
  if (name == nullptr || (int)strlen(name) > Symbol::max_length()) {
    // It's impossible to create this class;  the name cannot fit
    // into the constant pool.
    return nullptr;
  }
  assert(UTF8::is_legal_utf8((const unsigned char*)name, (int)strlen(name), false), "illegal UTF name");

  TempNewSymbol h_name = SymbolTable::new_symbol(name);
  Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);
  if (k == nullptr) {
    return nullptr;
  }

  if (log_is_enabled(Debug, class, resolve)) {
    trace_class_resolution(k);
  }
  return (jclass) JNIHandles::make_local(THREAD, k->java_mirror());
JVM_END

Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);
获取到kclass,方法调用到systemDictionary.cpp

if (loaded_class == nullptr) {
  // Do actual loading 
  loaded_class = load_instance_class(name, class_loader, THREAD);
}
InstanceKlass* SystemDictionary::load_instance_class(Symbol* name,
                                                     Handle class_loader,
                                                     TRAPS) {

  InstanceKlass* loaded_class = load_instance_class_impl(name, class_loader, CHECK_NULL);
  //...
  return loaded_class;
}

load_instance_class_impl中判断是否有classloader,没有的时候用的一个nullptr名字创建一个新的,
有的就是java的classloader,这里应该就是系统的loader

 if (k == nullptr) {
  // Use VM class loader
  //没用cds直接到了ClassLoader.cpp方法里面 ,ClassLoader.c是java对应类的jni,cpp是vm系统启动类
  PerfTraceTime vmtimer(ClassLoader::perf_sys_classload_time());
  k = ClassLoader::load_class(class_name, search_only_bootloader_append, CHECK_NULL);
}

classLoader.cpp中

InstanceKlass* ClassLoader::load_class(Symbol* name, bool search_append_only, TRAPS) {
//...
//上一步搜索是从cds,本次从jimage(就是jar的升级),这里的_jrt_entry在vm初始化 Arguments::init_system_properties()进行的赋值
//jdk9以后的lib包下文件存储方式已经发生变化了,比如rt.jar不见了,jar被Modules优化替代了,用jimage方式加载jar
// Load Attempt #2: [jimage | exploded build]
  if (!search_append_only && (nullptr == stream)) {
    //java runtime 判断是不是实时的jar包加载方式,是的话从实时包里面搜
    if (has_jrt_entry()) {
      e = _jrt_entry;
      stream = _jrt_entry->open_stream(THREAD, file_name);
    } else {
      // Exploded build - attempt to locate class in its defining module's location.
      assert(_exploded_entries != nullptr, "No exploded build entries present");
      stream = search_module_entries(THREAD, _exploded_entries, class_name, file_name);
    }
  }

// Load Attempt #3: [-Xbootclasspath/a]; [jvmti appended entries]
//从classpath的lib找对应的class文件
/**
这里的Xbootclasspath是系统获取额外的jar地址,通过不同的方式比如jvmti事件监听通过sun.instrument.InstrumentationImpl将一个jar包添加进启动包
ClassPathZipEntry* zip_entry = ClassLoader::create_class_path_zip_entry(segment, true);
里面调用到系统的路径方法get_canonical_path获取到系统的jar在文件系统的位置
*/
if (search_append_only && (nullptr == stream)) {
    // For the boot loader append path search, the starting classpath_index
    // for the appended piece is always 1 to account for either the
    // _jrt_entry or the _exploded_entries.
    assert(classpath_index == 0, "The classpath_index has been incremented incorrectly");
    classpath_index = 1;

    e = first_append_entry();
    while (e != nullptr) {
      stream = e->open_stream(THREAD, file_name);
      if (nullptr != stream) {
        break;
      }
      e = e->next();
      ++classpath_index;
    }
  }
//...
//将class从包里面搜索出来成为stream文件转换成kclass对象
InstanceKlass* result = KlassFactory::create_from_stream(stream,
                                                           name,
                                                           loader_data,
                                                           cl_info,
                                                           CHECK_NULL);
}

上面就是一个搜索解析class名称拿到文件流的过程,主要解析还在classFileParser.cpp里面

//构造方法在通过parse_stream方法解析常量池等,父类也会被关联初始化,主要就是初始化静态的常量表
ClassFileParser parser(stream,
                         name,
                         loader_data,
                         &cl_info,
                         ClassFileParser::BROADCAST, // publicity level
                         CHECK_NULL);
//隐藏类,后面jdk引入的
const ClassInstanceInfo* cl_inst_info = cl_info.class_hidden_info_ptr();
InstanceKlass* result = parser.create_instance_klass(old_stream != stream, *cl_inst_info, CHECK_NULL);

解析出来的InstanceKlass是虚拟机层面的,通过mirror映射出来java的class对象

InstanceKlass* ClassFileParser::create_instance_klass(bool changed_by_loadhook,
                                                      const ClassInstanceInfo& cl_inst_info,
                                                      TRAPS) {
  if (_klass != nullptr) {
    return _klass;
  }
  //根据解析出来的字段分配对应的metaspace大小
  InstanceKlass* const ik =
    InstanceKlass::allocate_instance_klass(*this, CHECK_NULL);

  if (is_hidden()) {
    mangle_hidden_class_name(ik);
  }
  //元空间分配,创建java mirror
  fill_instance_klass(ik, changed_by_loadhook, cl_inst_info, CHECK_NULL);

  assert(_klass == ik, "invariant");

  return ik;
}

初始化class对象和静态字段信息

void java_lang_Class::allocate_mirror(Klass* k, bool is_scratch, Handle protection_domain, Handle classData,
                                      Handle& mirror, Handle& comp_mirror, TRAPS) {
  // Allocate mirror (java.lang.Class instance)
  //Universe::heap中为java class分配空间地址
  oop mirror_oop = InstanceMirrorKlass::cast(vmClasses::Class_klass())->allocate_instance(k, CHECK);
  //当前也就是主线程的HandleArea保存class句柄,方便资源管理
  mirror = Handle(THREAD, mirror_oop);

  // Setup indirection from mirror->klass
  set_klass(mirror(), k);

  InstanceMirrorKlass* mk = InstanceMirrorKlass::cast(mirror->klass());
  assert(oop_size(mirror()) == mk->instance_size(k), "should have been set");

  set_static_oop_field_count(mirror(), mk->compute_static_oop_field_count(mirror()));

  //...
    //初始化static字段,所以static代码块要比静态字段晚一点执行
    initialize_mirror_fields(k, mirror, protection_domain, classData, THREAD);
   
  //...
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容