虚拟机类加载机制你真得了解吗?

  在.java代码被编译成.class文件即字节码文件以后,在Class文件中描述的各类信息,最终都需要被加载到虚拟机当中才能运行和使用。Java虚拟机把描述类的数据从Class文件加载到内存中,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的数据的过程,称为虚拟机类加载机制。在Java语言里面,类型的加载和、链接和初始化过程都是在程序运行期间完成的。

类加载机制分为如下的三步:

  1. 加载

  2. 链接(验证,准备,解析)

  3. 初始化

那么接下来我就说说每一步都干了些啥?

加载:

在加载这里虚拟机需要完成以下的三件事情:

  1. 通过类的全限制名称获取定义此类的字节流数据

  2. 将字节流中的静态结构转换为方法区运行时数据结构

  3. 在Java堆内存中实例化一个java.lang.Class的对象

      注意这里获取字节流可以有很多方法,从zip,jar,war等包中读取,从网络中获取,有其他文件生成,比如jsp文件生成对应的.class文件。

    类加载使用类加载器来完成这个动作。虚拟机中的类加载器目前有启动类加载器(bootStrap ClassLoader,java.lang.ClassLoader),这个类加载器使用C++语言实现,是虚拟机的一部分,他主要加载一些lib目录下面或者被-Xbootclasspath参数所指定的路径中存放的能够识别的类库加载到内存中。除了启动类加载器外,其他的加载器都是java.lang.ClassLoader的子类,因此有对象的Java对象,这些类需要先由另外一个类加载器,比如说启动类加载器加载到虚拟机中,方能执行类加载加载器。比如扩展类加载器(sun.misc.Launcher.ExtClassLoader)和应用类加载器(sun.misc.Launcher.AppClassLoader),扩展类加载器一般加载lib\ext目录下面的类,一般是一些扩展类。应用类加载器用来加载用户路径上面的类。这里需要注意的是类加载器还有命名空间的作用,即同一个类被不同的类加载器加载,得到的类都是不一样的类。可以用如下的代码验证。

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
public class ClassLoadTest {    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader classLoader = new ClassLoader() {            @Override            public Class<?> loadClass(String name) throws ClassNotFoundException {                String fileName = name.substring(name.lastIndexOf("." )+1) + ".class";                InputStream resourceAsStream = getClass().getResourceAsStream(fileName);                if (resourceAsStream == null) {                    return super.loadClass(name);                }                try {                    byte[] b = new byte[resourceAsStream.available()];                    resourceAsStream.read(b);                    return defineClass(name, b, 0, b.length);                } catch (IOException e) {                    throw new ClassNotFoundException();                }            }        };
        Object resultObject = classLoader.loadClass("com.lishuai.demo2.ClassLoadTest").newInstance();        System.out.println(resultObject.getClass());        System.out.println(resultObject instanceof  ClassLoadTest);        System.out.println(new ClassLoadTest() instanceof ClassLoadTest);    }}

 

    类加载的时候一般使用的是双亲委派模型,意思是说一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求交给父类加载器去完成,每一层的类加载器都是如此,所以所有的加载请求最后都会传送到最顶层的启动类加载器当中去,只有当父类加载器反馈自己无法加载这个类的时候(它的管辖范围没有这个类),子类加载器才会去尝试自己加载完成加载。如下就是双亲委派模型,不过这里类加载器之间的父子关系一般不是继承而是组合关系。


    使用双亲委派模型的好处就是:Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

      加载阶段完成后,Java虚拟机外部的二进制字节流文件就按照虚拟机所设定的格式存储在方法区了,这里方法区的数据结构由具体的虚拟机实现把,Java虚拟机规范中没有规范其数据结构,之后就会在Java堆中实例化一个java.lang.Class类对象,这个对象作为程序访问方法区中的数据类型的外部接口。


链接:

接下来进入链接的范围:链接的主要作用是将加载的二进制类数据合并到虚拟机的运行时环境中去。

 

a)验证

    对class文件的数据进行验证,进而保证不会加载错误的class文件导致虚拟机宕机。主要有

  • 文件格式验证,即验证字节流是否符合Class文件的规范

  • 元数据验证,保证其否和Java语法

  • 字节码验证,通过数据流分析和控制流分析程序语义。

  • 符号引用的验证,这个验证发生在解析的时候,保证解析能正确执行

因为这块不是必须要执行的一个过程,如果说我们自己写的代码反复验证并且使用没什么问题的话,则可以通过-Xverify:none关闭大部分类的验证措施,以缩短虚拟机的加载时间。

b)准备

    准备是为类中定义的类变量分配内存并且设置类初始化值的阶段。这里需要注意之前为类变量分配的内存在方法区上,JDK7之前方法区是由永久代实现的,JDK8之后,类变量则会随着Class对象一起放在堆内存当中。还有一点是这里的内存分配仅仅是类变量不包括实例变量,实例变量在对象实例化的时候随对象一起在堆中分配。这里初始化值通常情况下说的也是类型的默认值。

比如下面的case

  •  
public static int value = 123;

这里的value在准备阶段会被初始化为0,因为int默认值是0,但是某些特殊情况除外,比如

  •  
    public static final int value = 123;

上述例子中用final修饰了类变量,那么就会在准备阶段将其初始化为123.

c)解析:

    这里主要是把符号引用替换为直接引用,Java 虚拟机规范并没有要求在链接过程中完成解析。它仅规定了:如果某些字节码使用了符号引用,那么在执行这些字节码之前,需要完成对这些符号引用的解析。


3.初始化:

     在前面介绍的那些步骤里,除了类加载可以自定义类加载器干预外,其余的都是在虚拟机内部执行完成,到了这里虚拟机才开始执行类中编写的代码,将主导权交给了应用程序。

    简单来说初始化阶段就是执行类初始化函数<clinit>(),这个方法不是程序员自己定义的而是编译生成物。它是由编译器自动收集类中的的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是源码中的顺序。注意静态语句块只能访问定义在其前面的变量,定义在其后面的,可以赋值但是不能访问 。但是clinit对于类或者接口不是必须的,如果说其 没有静态语句块,也没有类变量赋值操作。则不会生成<clinit>()方法。                       

 

以上就是虚拟机类加载的整体过程了!欢迎讨论交流,有问必答!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值