JVM虚拟机—类加载机制

JVM虚拟机—类加载机制

类加载过程

类的加载很复杂,主要有几个过程:加载、验证、准备、解析、初始化

  • 加载

    加载是将外部的.class文件加载到java的方法区内。

  • 验证

    验证是为了防止恶意代码,如果代码不符合规范,会抛出 java.lang.VerifyError异常。像低版本的JVM是无法加载高版本的类库的

  • 准备

    将为一些类变量分配内存,并将其初始化为默认值。此时,实例对象还没有分配内存。这些动作都是在方法区上的

  • 解析

    解析是将符号引用替换为直接引用的过程。这个阶段都做了哪些工作呢?大体可以分为:

    • 类或接口的解析
    • 类方法解析
    • 接口方法解析
    • 字段解析

    几个常见的异常:

    ​ java.lang.NoSuchFieldError 根据继承关系从下往上,找不到相关字段时的报错。
    ​ java.lang.IllegalAccessError 字段或者方法,访问权限不具备时的错误。
    ​ java.lang.NoSuchMethodError 找不到相关方法时的错误。

    解析过程保证了相互引用的完整性,将继承与组合推到运行时。

  • 初始化

    ​ 初始化成员变量。static 语句块,只能访问到定义在 static 语句块之前的变量。所以下面的代码是无法通过编译的。JVM 会保证在子类的初始化方法执行之前,父类的初始化方法已经执行完毕。

    ​ 方法和 方法有什么区别?

    ​ static对应的是cinit方法,创建新对象的构造方法对应init方法。

类加载器

​ 整个类加载的过程非常繁重。类加载器做的就是上面5步。下面介绍了几种不同的类加载器。

  • Bootstrap ClassLoader

    任何类的加载行为,都要经过它。它的作用是加载核心类库,这个加载器是C++写的,随着JVM启动

  • Extention ClassLoader

    扩展类加载器,主要用于加载lib/ext文件下的jar包、.class文件。这个加载器是Java类,继承自URLClassLoader

  • App ClassLoader

    加载我们写的Java类的加载器,有时也叫System ClassLoader。一般用来加载classpath下的其他所有jar和.class文件。

  • Custom ClassLoader

    自定义加载器,支持一些个性化的拓展功能

双亲委派机制

​ 双亲委派机制的意思是除了顶层的启动类加载器以外,其余的类加载器,在加载之前,都会委派给它的父加载器进行加载。这样一层层向上传递,直到祖先们都无法胜任,它才会真正的加载。

​ 双亲委派机制在源码中看到是可以被覆盖的,所以不一定生效。下面是一些案例

  • tomcat

    tomcat 通过 war 包进行应用的发布,它其实是违反了双亲委派机制原则的。对于一些需要加载的非基础类,会由一个叫作 WebAppClassLoader 的类加载器优先加载。等它加载不到的时候,再交给上层的 ClassLoader 进行加载。这个加载器用来隔绝不同应用的 .class 文件,比如你的两个应用,可能会依赖同一个第三方的不同版本,它们是相互没有影响的。

  • SPI

    ​ 在使用 JDBC 写程序之前,通常会调用下面这行代码,用于加载所需要的驱动类。Class.forName(“com.mysql.jdbc.Driver”)

    但是你会发现,即使删除了 Class.forName 这一行代码,也能加载到正确的驱动类,什么都不需要做,非常的神奇,它是怎么做到的呢?

    通过在 META-INF/services 目录下,创建一个以接口全限定名为命名的文件(内容为实现类的全限定名),即可自动加载这一种实现,这就是 SPI。

    SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,主要使用 java.util.ServiceLoader 类进行动态装载。

  • OSGi

    OSGi 曾经非常流行,Eclipse 就使用 OSGi 作为插件系统的基础。OSGi 是服务平台的规范,旨在用于需要长运行时间、动态更新和对运行环境破坏最小的系统。

    OSGi 规范定义了很多关于包生命周期,以及基础架构和绑定包的交互方式。这些规则,通过使用特殊 Java 类加载器来强制执行,比较霸道。

    比如,在一般 Java 应用程序中,classpath 中的所有类都对所有其他类可见,这是毋庸置疑的。但是,OSGi 类加载器基于 OSGi 规范和每个绑定包的 manifest.mf 文件中指定的选项,来限制这些类的交互,这就让编程风格变得非常的怪异。但我们不难想象,这种与直觉相违背的加载方式,肯定是由专用的类加载器来实现的

如何替换JDK的类

​ 让我们回到本课时开始的问题,如何替换 JDK 中的类?比如,我们现在就拿 HashMap为例。

当 Java 的原生 API 不能满足需求时,比如我们要修改 HashMap 类,就必须要使用到 Java 的 endorsed 技术。我们需要将自己的 HashMap 类,打包成一个 jar 包,然后放到 -Djava.endorsed.dirs 指定的目录中。注意类名和包名,应该和 JDK 自带的是一样的。但是,java.lang 包下面的类除外,因为这些都是特殊保护的。

因为我们上面提到的双亲委派机制,是无法直接在应用中替换 JDK 的原生类的。但是,有时候又不得不进行一下增强、替换,比如你想要调试一段代码,或者比 Java 团队早发现了一个 Bug。所以,Java 提供了 endorsed 技术,用于替换这些类。这个目录下的 jar 包,会比 rt.jar 中的文件,优先级更高,可以被最先加载到

你可能感兴趣的:(JVM虚拟机)