java类加载

之前看《深入理解Java虚拟机》看了虚拟机类加载机制那一章,那时候就想到了Java类成员的加载顺序。看完那本书好像也没有多久,才几个月而已,很多东西就全部都忘了,之前看的书,还做了一些笔记,这本书没做笔记,结果就想从来没看过一样。所以还是记下一些学到的,领悟到的东西。

我们在最开始学Java的时候,应该就有接触javac,很多人估计在之后很长一段时间都没有再继续使用Javac了。我现在开始真正体会到那时候老师最开始讲Java的时候,要让我们用命令行javac来编译了。我们用javac编译出来的东西是class文件,然后虚拟机再加载class文件,将其转换成虚拟机可以直接使用的Java类型。class文件是有一定格式的,其实你也可以去修改class文件。在这里我主要分享一下虚拟机类加载过程。

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification),准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)这几个阶段。

加载

加载应该是有一个时间点,什么时候加载?虚拟机规范中没有强制约束。但是类的初始化是有规定的,而加载、验证、准备、解析是要在类初始化之前的。在介绍类的初始化的时候,详细描述。

加载需要完成以下三件事情: 1. 通过类的全限定名获取类的二进制字节流 2. 将这个字节流所代表的静态存储结果转化为方法去的运行时数据结构 3. 在java堆中生成代表这个类的java.lang.Class对象,作为方法去的这些数据的访问入口

第一件事,从类的全限定名获取类的二进制字节流。这里描述并没有说是是从一个.class文件,在这里可以发挥想象,这个字节流完全可以从网络上获取,甚至可以自己直接在代码内部用一个String来表示。然后用ClassLoader去加载,在动态加载的时候,一般使用自己重定义ClassLoader(继承ClassLoader)的方式去加载。这个地方可以发挥很多的想象力,其实现有的一些东西,比如jar包,Applet,JSP等等,都有利用到这一点。加载其实是很开放的。

另外虚拟机将class文件的数据加载,放到虚拟机中的存储格式也是虚拟机实现自定义的,所以不同的虚拟机存储的数据格式是可以不同的。它是开发的,你可以发挥想象力,去实现一些看似很难实现的东西。

验证

虚拟机将类加载完了后,类正不正确,还不知道。所以需要验证。其实我们编译出的class文件是可以修改的,这里有一个应用—插桩。我之前做过一个统计测试覆盖率的,就是通过这种插桩的方式的。

验证的类别总共有一下几种: 1. 文件格式验证 2. 元数据验证 3. 字节码验证 4. 符号引用验证:如private、protected是否可以被当前类访问

其实验证是有跟加载交杂在一起的部分的,因为有部分验证是跟加载一起的进行的。

准备

这是正事为类变量分配内存并设置变量初始值的阶段。这个时候分配的仅是类变量,不包括实例变量。另外,这些值分配的是类型的零值。比如下面这个public static int value = 321

这里在准备阶段分配的是0。而不是321。之后会在<cinit()>函数中,初始化为321。

但是对于一些常量如:public static int final value = 321

在准备阶段会被直接分配为321.

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行。

初始化

这是初始化的最后一步了。在之前,除了加载可以在程序中定义ClassLoader ,其他的基本都是由虚拟机控制的。初始化就跟我们平时使用类的时候有很多关联了。初始化阶段是执行类构造器()方法的过程。

  • ()方法是由编译器自动收集类中的所有类变量的复制动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,因此静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值,但是不能访问。
  • ()方法与类的构造函数不同,它不需要要显示地调用父类构造,虚拟机会保证在子类的方法执行之前,父类的()方法已经执行完毕。so,虚拟机中第一个被执行的()方法的类一定是java.lang.Object。ps:其实我一直以为Object是抽象类。其实不是的。看看下面这段代码:
  • ()方法对于类或接口来说并不是必须的。如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成()方法。
  • 接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成()方法,但是预制不同的是,执行接口的()方法不需要先执行父接口的()方法。只有当父接口中定义的变量被使用的时候,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的()方法。
  • 虚拟机会保证一个类的()方法在多个线程中被正确地加锁和同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他的线程都需要阻塞等待,知道活动线程执行()方法完毕,如果一个类的()方法中有耗时很长的操作,那就可能造成多个进程阻塞。在实际中这种阻塞往往是很隐藏的。

上面就是类加载过程中主要的过程。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值