【Jvm系列(一)】类的加载机制

类加载机制

1,类加载过程

img

ClassLoader 只负责 class 文件的加载,至于它是否可以运行,则由 Execution Engine 执行引擎决定。

加载阶段:

  1. 通过一个类的全类名获取此类的二进制字节流;
  2. 将该流所表示的静态存储结构转换为方法区的运行时数据结构;
  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的访问入口。

链接阶段:

  • 验证(Verify)

    确保 Class 文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性;

    主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

  • 准备(Prepare)

    主要工作是为类变量分配内存(到方法区)并且设置该类变量的默认初始值,即零值。

    这里的分配内存不包含用 final 修饰的类变量,因为其在编译的时候就会被分配,准备阶段会将其显式初始化;

    这里不会为实例变量分配初始化,实例变量是会随着对象一起分配到 Java 堆中。

  • 解析(Resolve)

    主要工作是将常量池内的符号引用转换为直接引用的过程。

    符号引用就是一组符号来描述所引用的目标。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

    解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的 CONSTANT_Class_info,CONSTANT_Fieldref_info、CONSTANT_Methodref_info 等。

    参考链接:

    https://www.yuque.com/u21195183/jvm/rq9lt4

2,类的加载器分类

类的加载器一般我们分为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。

这里的自定义类加载器其实包括扩展类加载器(Extension ClassLoader)和系统类加载器,AppClassLoader),只是区别于 JVM 内部由 c/c++实现的 Bootstrap ClassLoader。

当然用户可以根据需求编写自己的加载器,基本需求通过继承 URLClassLoader 类即可实现,相应方法包括 loacClass,findClass,findLoadClass 等。

img
https://www.cnblogs.com/leiqiannian/p/7922824.html

3,类加载中的双亲委派机制

类的加载机制是一层层向上委托加载的,即按照 AppClassLoader->Extension ClassLoader->Bootstrap ClassLoader 的委托形式,只有当根类 Bootstrap ClassLoader 无法加载时,才会自己尝试去加载,否则抛出 ClassNotFindException。另外一个类中引用的类也会遵循该类的加载器机制。

这种机制的优势是显而易见的:

  • 避免类的重复加载,由上级加载器优先加载,加载过后子类无需再次加载。
  • 沙箱安全机制,核心 API 必须走系统的实现类,防止恶意篡改。

与双亲委派机制相对应的就是全盘委托机制,即一个类自始至终都为一个 ClassLoader 负责,除非显示使用其他的 ClassLoader。

有没有想过这种方式是不是会导致封闭呢?双亲委派其实是个单向的加载机制,当父类的加载需要依赖子类加载时该如何解决?比如 Java 提供的服务提供者接口(Service Provider Interface,SPI)包括 JDBC、JNDI 等的加载实现。

/**
 * 向 DriverManager 注册指定的驱动程序
 */
DriverManager.registerDriver(java.sql.Driver driver)

DriverManager 首先会由系统类加载器加载,然后找到引导类加载器完成加载,其所依赖的 Driver 类则由 JDBC 厂商实现,显然引导类加载器是无法完成加载的。为此,通过委托线程上下文加载器 ContextClassLoader 可以解决此问题。也就是说 ContextClassLoader,破除了双亲委派的单向加弊端,解决了第三方类库的加载问题。

参考链接:

https://www.cnblogs.com/doit8791/p/5820037.html

https://www.cnblogs.com/lxchinesszz/p/12130531.html

4,类的加载顺序问题

一个类未被加载时,按照一下加载顺序执行:

  1. 首先加载执行类;
  2. 按照先后顺序加载静态变量和静态代码块;
  3. 加载 main 方法;
  4. new 当前类对象方法时会触发加载类中其他未加载的东西,此处会顺序加载其他变量和匿名代码块等,最后加载默认构造方法;

当然,有继承关系的类会首先按照上述方式优先加载完父类的静态变量和实例变量。

对于已被加载的类,静态代码块和静态变量无需重复执行,创建对象时只需执行相关的实例变量和构造方法即可。

参考链接:

https://www.cnblogs.com/leiqiannian/p/7922824.html

猜你喜欢

转载自blog.csdn.net/qq_40589204/article/details/120811876