一、前言

之前文章

加多:ClassLoader解惑​zhuanlan.zhihu.com

从Java层面讲解了Java类加载器的原理,这里我们结合JVM源码在稍微深入讲解下。

二、Java类加载器的委托机制

Java 类加载器使用的是委托机制,也就是一个类加载器在加载一个类时候会首先尝试让父类加载器来加载。那么问题来了,为啥使用这种方式?

使用委托第一这样可以避免重复加载,第二,考虑到安全因素,下面我们看下ClassLoader类的loadClass方法:

protected Class<?> loadClass(Stringname,boolean resolve)  throws ClassNotFoundException  {  synchronized (getClassLoadingLock(name)) {  // 首先从jvm缓存查找该类Class c = findLoadedClass(name); // (1)if (c ==null) {  longt0 = System.nanoTime();  try {  //然后委托给父类加载器进行加载if (parent !=null) {  c = parent.loadClass(name,false);  (2)} else {  //如果父类加载器为null,则委托给BootStrap加载器加载c = findBootstrapClassOrNull(name);  (3)}  } catch (ClassNotFoundExceptione) {  // ClassNotFoundException thrown if class not found  // from the non-null parent class loader  }  if (c ==null) {  // 若仍然没有找到则调用findclass查找// to find the class.  longt1 = System.nanoTime();  c = findClass(name);  (4)// 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);  //(5)}  returnc;  }  }

代码(1)表示从 JVM 缓存查找该类,如果该类之前被加载过,则直接从 JVM 缓存返回该类。

代码(2)表示如果 JVM 缓存不存在该类,则看当前类加载器是否有父加载器,如果有的话则委托父类加载器进行加载,否者调用(3),委托 BootStrapClassloader 进行加载,如果还是没有找到,则调用当前 Classloader 的 findclass 方法进行查找。

代码(4)则是从本地classloader指定路径进行查找,其中findClass方法在路径找到Class文件会加载二进制字节码到内存,然后后会调用native方法defineClass1解析字节码为JVM内部的kclass对象,然后存放到Java堆的方法区。

代码(5)则是当字节码加载到内存后进行链接操作,对文件格式和字节码验证,并为 static 字段分配空间并初始化,符号引用转为直接引用,访问控制,方法覆盖等,本文对这些不进入深入探讨。

三、JVM源码之defineClass1如何解析字节码文件

本节使用的openjdk7的源码,JVM源码中defineClass1的定义是在ClassLoader.c文件,其解析时序图如下:

image.png

可知步骤(8)具体解析字节码文件,步骤(17)添加加载的类到系统词典Map里面,

void SystemDictionary::update_dictionary(int d_index, unsigned int d_hash,int p_index, unsigned int p_hash,instanceKlassHandle k,Handle class_loader,TRAPS) {// Compile_lock prevents systemDictionary updates during compilationsassert_locked_or_safepoint(Compile_lock);Symbol*  name  = k->name();ClassLoaderData *loader_data = class_loader_data(class_loader);{MutexLocker mu1(SystemDictionary_lock, THREAD);...// 当前对象已经存在了?Klass* sd_check = find_class(d_index, d_hash, name, loader_data);//不存在则添加if (sd_check == NULL) {//添加kclass到系统词典dictionary()->add_klass(name, loader_data, k);notice_modification();}...
}

其中key使用类的包路径+类名,类加载器两者确定,value则为具体加载的类对应的instanceKlassHandle对象,其中维护这kclass对象。也就是系统词典里面使用类加载器和类的包路径类名唯一确定一个类。这也验证了在Java中同一个类使用两个类加载器进行加载后,加载的两个类是不一样的,是不能相互赋值的。

四、JVM源码之findLoadedClass0如何查找一个类是否被加载过了

findLoadedClass0也是在ClassLoader.c文件里面,其查找时序图:

image.png

如上时序图主要看 SystemDictionary的find方法:

Klass* SystemDictionary::find(Symbol* class_name,Handle class_loader,Handle protection_domain,TRAPS) {...class_loader = Handle(THREAD, java_lang_ClassLoader::non_reflection_class_loader(class_loader()));ClassLoaderData* loader_data = ClassLoaderData::class_loader_data_or_null(class_loader());...unsigned int d_hash = dictionary()->compute_hash(class_name, loader_data);int d_index = dictionary()->hash_to_index(d_hash);{... return dictionary()->find(d_index, d_hash, class_name, loader_data,protection_domain, THREAD);}
}Klass* Dictionary::find(int index, unsigned int hash, Symbol* name,ClassLoaderData* loader_data, Handle protection_domain, TRAPS) {//根据类名和加载器计算对应的kclass在map里面对应的keyDictionaryEntry* entry = get_entry(index, hash, name, loader_data);//存在,并且验证通过则返回if (entry != NULL && entry->is_valid_protection_domain(protection_domain)) {return entry->klass();} else {//否者返回null,说明不存在return NULL;}
}

可知在查找一个类是否已经被加载过后,也是从系统词典里面根据类名和类加载器去查找是否存在的。

五、总结

本文从JVM源码角度分析了Java中唯一含有包路径的类名和类加载器唯一确定了一个类,在全局系统词典里面就是根据包路径的类名和类加载器计算加载的类对应的key的。

80070583类不存在_结合JVM源码谈Java类加载器相关推荐

  1. JVM源码分析--ClassLoader类加载器

    本人原创,转载请注明出处:https://www.cnblogs.com/javallh/p/10224187.html 1.JDK已有类加载器: BootStrap ClassLoader (启动类 ...

  2. 由源码深入Java类加载器(双亲委派模型)

    JVM类加载器 JVM主要有以下几种类加载器: 引导类加载器 主要加载JVM运行核心类库,位于JRE的lib目录下,如rt.jar中的类. 扩展类加载器 主要加载JVM中扩展类,位于JRE的ext目录 ...

  3. java扑克牌类游戏下载_【参考源码】Java入门第三季 7-1 简易扑克牌游戏

    先创建 PokerCard 扑克牌类,存放牌的花色.大小,还有一个关键值weight,用于定义52张牌的排位大小,这样就避免了后面比较双方牌大小的繁琐步骤 public class PokerCard ...

  4. java类加载机制为什么双亲委派_[五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的...

    Launcher启动类 本文是双亲委派机制的源码分析部分,类加载机制中的双亲委派模型对于jvm的稳定运行是非常重要的不过源码其实比较简单,接下来简单介绍一下我们先从启动类说起有一个Launcher类 ...

  5. JVM源码分析-Java运行

    最近在看Java并发编程实践和Inside JVM两本书,发现如果不真正的了解底层运作,那么永远是雾里看花.因此从http://openjdk.java.net/groups/hotspot/上下载了 ...

  6. java cms视频_【视频+源码】JAVA CMS系统项目实战

    01 CMS系统功能需求简介02 如何采用用例分析方法来理解需求03 后台管理系统用例04 实现验证码的初步思路05 生成验证码06 判断验证码是否正确07 返回登录页面时,把刚刚输入的用户名和密码回 ...

  7. JVM虚拟机详解(三)类加载器的分类

    JVM虚拟机详解(三)类加载器的分类 1. 类加载器概述 JVM严格来讲支持两种类型的类加载器 .分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defin ...

  8. java直接内存为什么快_直接内存与 JVM 源码分析

    直接内存(堆外内存) 直接内存有一种叫法,堆外内存. 直接内存(堆外内存)指的是 Java 应用程序通过直接方式从操作系统中申请的内存.这个差别与之前的堆.栈.方法区,那些内存都是经过了虚拟化.所以严 ...

  9. JVM源码阅读-本地库加载流程和原理

    前言 本文主要研究OpenJDK中JVM源码中涉及到native本地库的加载流程和原理的部分.主要目的是为了了解本地库是如何被加载到虚拟机,以及是如何找到并执行本地库里的本地方法,以及JNI的 JNI ...

最新文章

  1. NLog在Asp.Net MVC的实战应用
  2. fiddler修改接口请求返回数据Response的三种方法
  3. python1000个常用代码-Python常用代码(1)
  4. 在weka中配置自己的算法 zz
  5. vector 查找_同样是Excel中的查找函数,这个函数却比VLOOKUP功能强100倍
  6. install npm 到某个文件下执行_如何将npm安装到指定目录?
  7. LeetCode 1062. 最长重复子串(二分查找)
  8. 【html】【19】高级篇--大事件时间轴
  9. jx8net一定在所有的方方面面都更坚强更勇敢了吧
  10. Python与机器视觉(x) 颜色直方图
  11. perl中-和=作用
  12. java反编译能拿到源码吗_大牛带你解读Spring源码,编写自定义标签,您能学会吗?
  13. rust 使用fltk 的小问题
  14. Android 的CompoundButton(抽象类按钮)、StringBuffer(字符串变量)
  15. 程序装在C盘Programe file下无写文件权限问题
  16. ATX电源工作原理的学习
  17. IPAD2 恢复出厂设置
  18. Matlab画图,坐标轴范围设置和间隔设置
  19. 【noi.ac #1997】A. 制胡窜
  20. 移动内部疯传的11篇VoLTE学习笔记,看懂了你也是技术大神(二)

热门文章

  1. 【教师节福利】长大后我就成了你
  2. AV1为何有信心打败H.265?
  3. PHP开发电脑网站支付宝支付详细流程(沙箱测试篇)
  4. SevOne将SD-WAN监控扩展到VMware NSX
  5. 模拟Spring如何在WEB中运行
  6. Java删除文件和目录
  7. Linux根文件系统的制作
  8. 【转】POJ-2104(K-th Number 划分树)
  9. HDU 2841 Visible Trees
  10. Processing--鼠标响应(1)