类加载与类加载器 学习笔记

类加载

class文件的生命周期有:加载-验证-准备-解析-初始化-使用-卸载。其中验证、准备、解析称为连接阶段。
接下来了解类文件加载的过程:加载、验证、准备、解析和初始化。

加载

在此阶段,JVM需要完成三件事:

  1. 通过类的全限定名获得类的二进制字节流。
    我们可以从多个途径来获取class的二进制字节流,如
    • zip压缩包读取:这是日后JAR、WAR格式的基础。
    • 运行时计算生成:动态代理技术,可以为特定接口生成代理类的二进制字节流。
    • 由其他文件生成,如JSP应用。
    • 从加密文件读取,可以防止class文件被反编译。
  2. 将此字节流所表示的静态存储结构转换为 方法区的运行时数据结构。
  3. 在内存中生成一个代表此类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

加载阶段结束后,二进制字节流就按照VM设定的格式存储在方法区之中了,存储格式可由虚拟机自行定义。加载阶段与连接阶段的部分操作是交叉进行的。

验证

此阶段的目的是保证class文件字节流中包含的信息符合《JVM规范》,同时保证这些信息被当做代码运行后不会危害虚拟机安全。分为:

  1. 文件格式验证:保证输入的字节流能被正确解析并存储于方法区之中,其格式上符合一个java类型信息的结构的描述。
  2. 元数据验证:如是否继承了Object,是否继承了不允许继承的类等等。此操作是对元数据的语义进行验证。
  3. 字节码验证:最为复杂的阶段。对方法体进行验证,确保程序语义是合法的,不会危害虚拟机。
  4. 符号引用验证:发生在解析阶段将符号引用转换为直接引用的过程中。此操作是为了检查类是否缺少或被禁止 访问它依赖的外部类、字段、方法等资源。

准备

在方法区中为类的静态变量分配内存并设置零值。若是final修饰则设置为程序中给定的值。

解析

将符号引用转换为直接引用。
符号引用:一组符号描述引用目标的字面量。
直接引用:指向目标的指针或能定位到目标的句柄。

初始化

真正执行程序员编写的Java程序代码,将主导权移交给应用程序。

静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语块可以赋值,但是不能访问,如代码清单7-5所示。

public class Test {
static {
i = 0; // 给变量复制可以正常编译通过
System.out.print(i); // 这句编译器会提示“非法向前引用”
}
static int i = 1;
}

JVM在多线程环境下会对初始化<clinit>指令加锁,保证只有一个线程能初始化成功,其他线程都会阻塞等待。所以如果初始化方法有耗时很长的操作,那么可能造成多个线程阻塞。

类加载器

用于实现类的加载。任意一个类,都必须由加载它的类加载器和类本身确立在JVM中的唯一性。即使是同一个class 文件,如果被不同的类加载器加载,那么这两个类必定不等。

双亲委派模型

JVM中只有两种类加载器,启动类加载器 和 其他类加载器(由Java语言实现,都继承自java.lang.ClassLoader)。

  • 启动类加载器(Bootstarp ClassLoader):是JVM自身的一部分,用于加载/lib下的库。无法直接被Java程序直接引用。
  • 扩展类加载器:加载/lib/ext下的类库。是Java系统类库的一种扩展机制。允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能,开发者可以直接在程序中使用扩展类加载器来加Class文件。在JDK9之后被模块化的扩展能力取代。
  • 应用程序类加载器:用于加载用户类路径下的所有类库。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

在这里插入图片描述
除了顶层的启动类加载器,其他类加载器都应有自己的父类加载器,不过这里的父子关系是通过组合关系来实现的。

双亲委派模型的工作流程是:

  1. 一个类加载器要加载一个类,它不会首先自己加载,而是将加载请求委派给父类加载器完成。每一层都是如此,因此所有类加载请求都会委派到启动类加载器中。
  2. 如果父类加载器无法完成,再尝试自己去加载

优点

  1. 使得类具有一种优先级的层次关系
  2. 防止了程序的混乱,保证了Java程序的稳定运作。

破坏双亲委派

线程上下文类加载器

它解决了双亲委派模型的一个缺陷:基础类无法调用回用户的代码,如JNDI服务,JNDI存在的目的就是对资源进行查找和集中管理。JDNI服务是由启动类加载器加载的,属于一个基础的类型,但它需要调用由其他厂商实现并部署在应用程序的ClassPath下的SPI代码,那启动类加载器肯定是不认识这些代码的,这就是传统双亲委派模型的缺陷。因此有了线程上下文类加载器。

这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

JNDI服务可以使用线程上下文类加载器来加载所需的SPI服务代码,这是父类加载器请求子类加载器完成类加载的行为。已经违背了双亲委派模型的一般原则。

对程序动态性的追求

希望Java应用程序在无需重启的情况下就能立即使用更新后的代码。如代码热替换模块热部署

OSGi实现模块化热部署的关键是它自定义的类加载器机制的实现,每一个程序模块(OSGi中称为Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi环境下,类加载器不再是双亲委派模型推荐的树状结构,而是进一步发展为更加复杂的网状结构。当收到类加载请求时,OSGi的查找顺序中只有开头两点仍然符合双亲委派模型的原则,其余的类查找都是在平级的类加载器中进行的。

来源

《深入理解JVM》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值