JVM的类加载机制

首先,我们先说说什么是jvm的类加载机制?

类加载机制就是jvm把类的.class文件调入内存,对它进行加载,验证,准备,解析,初始化等过程,转换为jvm能够直接使用的java对象,这就是jvm的类加载机制。

在class文件加载到内存,再到被移出内存,整个的过程会经历加载,验证,准备,解析,初始化,适用,卸载等阶段,也称类的生命周期。

其中准备,解析,初始化合称为连接阶段,加载,验证,准备,初始化,卸载阶段的顺序是确定的,但是解析阶段可以放在初始化前也可以初始化后,这是为了支持java语言的运行时绑定。

至于在什么时机对类进行加载,这个取决于程序的设计。
通常情况下,在这几个时机会对类进行初始化,而加载,验证,准备等阶段自然已经发生了。
(1)当虚拟机遇到new,putstatic,getstatic,invokestatic等指令时,即new 一个对象,设置或获取static类型变量,调用类中的静态方法。
(2)当一个子类进行初始化时,如果父类没有初始化,则先初始化父类,即先执行父类代码块,再执行子类代码块。
(3)当利用java.lang.reflect反射调用时,如果没有初始化该类,会先初始化
(4)当虚拟机启动时,执行main方法时,会初始化main方法所在的类。
(5)当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

虽然类加载过程就这几个阶段,但是要分析每个阶段的工作内容却不是一件容易的事,现在我们来看看每个阶段具体是做些什么事。

  • 加载:可以用系统提供的加载器也可以用自定义的加载器

(1)通过类的全限定名来获得类的二进制字节流
(2)讲这个字节流所代表的静态存储结构转换为方法区运行时数据结构
(3)在内存中利用java.lang.class生成一个class对象,作为方法区访问类的各种属性的访问入口

  • 验证:验证是链接的第一步,判断class文件的字节流中的信息是不是符合虚拟机的需要,并且这些信息会不会危害到虚拟机的安全
    主要包含四个检验过程:文件格式检验,元数据检验,字节码验证,符号引用验证。
    (1)文件格式验证:验证文件格式是否符合规范
    (2)元数据检验:对字节码中的信息进行语义分析,判断是否符合java规范。主要有是否有父类,是否继承了不该继承的类,接口,抽象类中的抽象方法是否全部实现。
    (3)字节码验证:验证类的方法在运行时不会做出危害虚拟机安全的行为。
    (4)符号引用验证。

  • 准备:为类变量分配内存并且设置初始值,主要是在方法区进行分配。
    准备阶段有两个注意点:
    首先,该阶段只是对类变量进行分配,实例变量不会进行分配,因为实例变量是跟随实例对象被分配到堆中的;
    其次,为每个类设置的值一般是类型的零值,比如public static int a = 5;
    在这个阶段a的值初始化为0,当初始化阶段才会赋值为5.但是有一种情况例外,就是当这个静态变量为constantvalue,及public static final int a = 5;这个时候为a设置的初始值就是5.

  • 解析:将虚拟机常量池的符号引用解析为直接引用的过程。
    要具体的了解解析过程,我们得先要知道什么是符号引用和什么是直接引用。
    (1)符号引用:一组能描述所引用的对象的符号,能无歧义的定位到对象。符号引用与虚拟机的内存布局无关,引用的目标对象不一定加载到内存中了。
    (2)直接引用:是指向目标对象的指针,偏移地址,或间接定位对象的句柄。直接引用与虚拟机内存布局相关,并且引用的对象一定都在内存中了。注意的一点:同一个符号引用经不同虚拟机解析出来的直接引用不同。

解析对象:
(1)接口,类的解析
(2)类方法的解析
(3)字段的解析
(4)接口方法的解析

  • 初始化;在准备阶段,类变量已经被赋值过一次系统要求的初始值了,而在初始化阶段,会按照程序顺序去初始化类变量和其他资源。即初始化阶段是类构造器 方法的执行过程。在以下四种情况下会被触发执行。
    (1)遇到new,getstatic,setstatic,ivokestatic四条字节码指令时会触发,即用new关键字创建实例对象,设置或获取静态变量,调用静态方法。
    (2)当一个子类被初始化时,它的父类没有并初始化就会先进行父类的初始化。
    (3)当用java.lang.reflect对某个类进行反射时,会进行初始化
    (4)当程序启动时,运行main方法的类会被初始化。

上面的public static int a = 5;在准备阶段过后初始值为0,在初始化后为5;

*类构造器()方法是由编译器自动收集类中所有变量的赋值动作和静态代码块,编译器收集的顺序是由语句在源文件中的顺序所决定的,静态代码块只能访问定义在它之前的静态变量,之后的静态变量只能赋值,不能访问
*由于父类初始化在子类之前,所以父类的()方法比子类先执行,即父类的静态代码块比子类的静态代码块先执行
*()对于类和接口不是必须的,如果一个类中没有赋值语句也没有静态代码块,就不会创建构造器。
*虚拟机会保证同一个类的()方法进行能被正确的加锁和同步,当有多个线程到来时,只会允许一个线程执行构造器方法,其他线程会被阻塞。

以上就是基本的类加载过程了,其中发挥重大作用的就是类加载器了
类加载器:通过类的全限定名来获取描述此类的二进制字节流,这个动作放在虚拟机的外部来执行,执行这个动作的模块称为类加载器。

除了这些,我们还应该知道类与类加载器的关系,以及什么是双亲委派模型。

类与类加载器
对于任何一个类,通过该类和类加载器确立在jvm中的唯一性,两个类由同一个class文件产生并且由同一个类加载器加载,这两个类才相等。

双亲委派模型
从虚拟机的角度看,只有两种,一种是虚拟机内部加载器,由c++实现;另一种是虚拟机外部加载器,由java实现,所有类都集成java.lang.classloader类。
从java开发人员的角度看,有三种类加载器,
(1)启动类加载器:负责加载java_home\lib中的虚拟机识别的类库到jvm中,如果虚拟机无法识别,即使lib中存在也不会加载到jvm中
(2)扩展加载器:负责加载java_home\lib\,该加载器可以被开发者直接使用。
(3)应用程序类加载器,该加载器主要负责加载用户路径上的类库,开发者可以直接使用。如果程序中没有自定义的加载器,默认使用这个。
(4)自定义加载器,继承classloader类。

在这里插入图片描述
像上面这种类加载器之间的层次模型,称为类加载器的双亲委派模型,除了最上层的启动加载器,其它加载器都有自己的父类。子类与父类之间的关系不是利用继承实现,而是通过组合方式来复用父类代码。

双亲委派模型的工作过程:
当一个加载器收到类加载的请求,他不会立即加载,他会传递给父类加载器,最后一级一级的往上传递,如果父类在搜索范围内搜索不到这个类的话,子类才会尝试加载。

源码:

·

	protected synchronized Class<?>loadClass(String name,boolean resolve)throws ClassNotFoundException
{
//首先,检查请求的类是否已经被加载过了
Class c=findLoadedClass(name);
if(c==null){
try{
if(parent!=null){
c=parent.loadClass(name,false);
}else{
c=findBootstrapClassOrNull(name);
}
}catch(ClassNotFoundException e){
//如果父类加载器抛出ClassNotFoundException
//说明父类加载器无法完成加载请求
}
if(c==null){
//在父类加载器无法加载的时候
//再调用本身的findClass方法来进行类加载
c=findClass(name);
}
}
if(resolve){
resolveClass(c);
}
return c;
}

双亲委派模型主要通过loadclass来实现,运行逻辑是:如果该类没有被加载,就传递给父加载器加载,如果父类加载不了则抛出异常,然后调用子类的findclass方法加载。

好了,以上就是JVM类加载机制的全部内容了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值