《深入理解Java虚拟机》第7章 虚拟机类加载机制

7.2 类加载的时机

类从被加载到VM内存中开始,到卸载出内存为止,它的生命周期包括了:加载,验证,准备,解析,初始化,使用,卸载,共7个阶段。其中验证,准备,解析统称为连接。

其中解析的顺序不一定,某些情况可以在初始化阶段后开始,这是为了支持Java的运行时绑定。
何时加载VM自己把握,但对于初始化,严格规定4种情况:

  1. 遇到new,getstatic,putstatic或invokestatic这4条字节码命令时。
    具体场景:用new关键字实例化对象时,读取或设置一个类的静态字段时,调用类静态方法时。
  2. 使用java.lang.reflect包的方法对类进行发射调用时
  3. 初始化类,发现其父类方法还没有初始化,先触发父类初始化(接口在初始化时,只有真正用到父接口才初始化)
  4. VM启动时,用户需要指定一个要执行的主类,VM会先初始化这个主类。

7.3 类加载的过程

加载阶段,VM完成三件事:
- 通过一个类的全限定名获取定义此类的二进制字节流(不一定从Class文件获取,ZIP包,网络,动态代理生成)
- 将此字节流代表的静态存储结构转化为方法区的运行时数据结构。
- 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

7.3.2 验证

确保Class文件的字节流包含的信息符合当前VM的要求,不会危害VM自身的安全。
Class文件不一定要求用java源码编译而来。可以使用任何途径,包括用十六进制编辑器直接编写产生。

  • 文件格式验证:字节流是否符合Class文件格式的规范
  • 元数验证:对字节码信息进行语义分析(元数据的数据类型),类是否有父类,类的父类是否继承了不允许被继承的类
  • 字节码验证:最复杂的阶段。进行数据流和控制流的分析(类的方法体校验分析)。保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作。
  • 符号引用验证:(确保解析动作能正常运行)符号引用中通过字符串描述的全限定名是否能找到对于的类。符号引用中的类,字段和方法的访问性是否可被当前类访问。

7.3.3 准备

正式为类变量(不包括实例变量)分配内存并设置类变量初始0值的阶段。这些内存都在方法区。
public static final int value=123;准备阶段赋值为123,而没有final则会设置为0

7.3.4 解析

是VM将常量池内的符号引用替换为直接引用的过程。
符号引用以一组符号描述所引用的目标,与VM实现的内存布局无关,引用的目标不一定加载到内存中。
直接引用可以是直接指向目标的指针,与VM实现的内存布局相关。引用的目标必定在内存中存在。
接口动作主要针对:类或接口,字段,类方法,接口方法四类符号引用进行。

7.3.5 初始化

类加载过程的最后一步。真正开始执行类中定义的Java程序代码,根据程序制定的主观计划初始化变量和其他资源。
初始化阶段是执行类构造器()方法的过程。
<clinit>()方法是由是编译器自动收集类中的,所有类变量的赋值动作和静态语句块中的语句合并产生的。
父类的<clinit>()方法先执行,意味着父类定义的静态语句块优先于子类的变量赋值操作。
接口中不能使用静态语句块,但仍有变量初始化的赋值操作,因此接口与类一样有<clinit>()方法。
只有当父接口中定义的变量被使用时,父接口才会初始化。
VM会保证一个类的<clinit>()方法在多线程环境中被正确地枷锁和同步。


7.4 类加载器

通过类的全限定名获取此类的二进制字节流,这个动作放到JavaVM外部去实现。实现这个动作的代码块叫”类加载器“。让应用程序自己选择如何获取所需要的类。在类层次划分,OSGI,热部署,代码加密等领域有重要作用。

7.4.1 类与类加载器

对任一个类,都需要由加载他的类加载器和这个类本身一同确立其在JavaVM中的唯一性。

7.4.2 双亲委派模型

JavaVM角度看:只有两种不同的类加载器:一是启动类加载器(Bootstrap ClassLoader),是VM自身一部分(C++实现),另一种是所有其他的类加载器,用Java实现,全部继承于java.lang.ClassLoader。

  • 启动类加载器:将存放在\lib中的类库加载到VM内存中(按照文件名识别),无法被Java程序直接引用。
  • 扩展类加载器:将存放在\lib\ext中的类库加载到VM内存中
  • 应用程序加载器:加载ClassPath所指定的类库。
    这里写图片描述
    上图就算双亲委派模型,除了顶层的启动类加载器,其他类加载器都该有自己的父类加载器。这里的父子关系不同该继承实现,而是用组合关系来复用父加载器的代码。
    JDK1.2引入,但不强制。
    如果收到请求,先委派给父类加载器完成。因此所有的加载请求都传送到顶层的启动类加载器中。只有父加载器反馈自己无法完成这个请求,子加载器才会尝试自己加载。

好处是:例如java.lang.Object类,无论哪个加载器请求加载,最后都给顶层的加载,因此Object类在各种类加载器环境中都是同一个类。

7.4.3 破坏双亲委派模型

双亲委派模型很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载)。当基础类又要调用回用户的代码,该怎么办?有了线程上下文加载器,JNDI服务(对资源进行集中查找和管理)使用它去加载所需要的SPI(服务提供接口)代码,就是说父加载器请求子类加载器去完成类加载的动作。

OSGI(Open Services Gateway Initiative,基于Java的动态模型系统)是Java模块化标准,实现模块化热部署的关键是它自定义的类加载器机制的实现。每一个程序模块(bundle)都有一个自己的类加载器。当需要更换一个bundle时,就把bundle连同类加载器一起换掉以实现代码的热替换。

OSGI环境,类加载器不是双亲委派模型的树状结构,而是网站结构。

  1. 将以java.*开头的类,委派给父类加载器加载。
  2. 否则,将委派列表名单内的类,委派给父类加载器加载。
  3. 否则,将Import列表中的类,委派给Export这个类的bundle的类加载器加载。
  4. 否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
  5. 否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。
  6. 否则,查找Dynamic Import列表的Bundle,委派给对应的Bundle的类加载器加载。
  7. 否则,类查找失败。


    我觉得这篇写得算是,重新恢复了翔实·——·

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值