java虚拟机类加载机制

java源文件经编译后以 .class的文件形式存在本地磁盘上,在 Class文件中描述的各类信息最终都需要加载到虚拟机中之后才能被运行和使用

类加载机制在jvm中的位置

在这里插入图片描述 

1.类加载过程

虚拟机把类的数据从.class文件加载到内存,并对class文件中的数据进行校验、转换、解析、初始化等操作后,最终形成可以被虚拟机识别并使用的Class对象的过程就叫做“虚拟机的类加载”,主要包括为3大阶段。

在这里插入图片描述 

加载->链接->初始化三大阶段,这三个阶段也被称成为类加载子系统

其中链接包含了验证,准备,解析三个子阶段,下面根据这三大阶段一一解释

1.1加载

需要注意的是,这里的加载是整个类加载子系统的一个小阶段,在这个阶段,java虚拟机需要完成以下三件事:

  • 通过一个类的全限定类名获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
     

1.2验证

确保class文件符合java虚拟机要求

1.3准备

简单的来说为被static修饰的变量赋初始值,分配内存,这些变量所使用的内存在方法区进行分配,JKD7之前虚拟机将其存放于永久代,JKD8及以后,类变量会随着Class对象一起放在堆中,实例变量会在对象实例化时随对象一起分配到堆中

1.4解析

这里不赘述

1.5初始化

这是类加载过程的最后一步,在之前的准备阶段,类变量已经赋初始值了,这里的初始化就是虚拟机根据程序员书写静态变量,静态代码块的顺序执行赋值等动作,但大家需要注意以下注意点

public class Demo {
    public static void main(String[] args) {
        System.out.println(p.i);
    }
}
class p{
    {
        i = 2;
        
    }
    static int i = 1;
}

这段代码在编译器是不会报错的,但是如果在代码块中执行System.out.println(i);就会报错:超前引用,即可以赋值,但不可访问
另外,在Java虚拟机规范中明确指出了,有且只有6种情况必须立即对类进行初始化 

1.遇到new,getstatic,putstatic,invokestatic这四条字节码指令时,如果类没有进行初始化,就触发其初始化,能生成这些字节码指令的场景:

  • 使用new关键字实例化对象
  • 读取或设置一个类型的静态字段(被final修饰,已在编译器就把结果放入常量池的静态字段除外)
  • 调用静态方法

2.使用反射
3.初始化类时,其父类如果没有初始化,则需要对其父类先初始化
4.执行main方法的类
5.使用jdk7的动态语言支持
6.使用了JDK8的默认方法 

2.类加载器

上面的架构图已经揭示了类加载器的作用,类加载器类加载器(class loader)将Java类从本地磁盘加载到Java虚拟机中,并同时创建了该类的Class对象,实现了“通过一个类的全限定类名来获取此类的二进制字节流”功能。通过一个例子将类加载器和类加载过程做一个说明

在这里插入图片描述 

一个car类在被编译后形成了class文件,被类加载器加载到jvm中并分配了内存和唯一一个class对象,通过这个class对象就可以在堆中实例化多个car实例对象

2.1类加载器的分类

java虚拟机中,只存在两种不同的类加载器: 

  • 启动类加载器Bootstrap Classloader,由C++编写,属于虚拟机一部分
  • 继承自抽象类java.lang.Classloader的类加载器,由Java语言编写,独立于虚拟机之外

现在可能有人会问独立于虚拟机之外是什么意思了,请往下看:

2.1.1 启动类加载器

它只负责加载你在环境变量中配置的<JAVA_HOME>\lib下的内容,由于是C++编写,不能被java程序所引用,用以下代码演示 

public class Demo {
    public static void main(String[] args) {
        System.out.println(Demo.class.getClassLoader().getParent().getParent());
    }
}

当用户使用以上方式想要获取Bootstrap Classloader是会失败的,返回的是null;

2.1.2 继承自抽象类java.lang.Classloader的类加载器

  • Extension Class Loader,扩展类加载器
  • Application Class Loader(System Class Loader),应用程序类加载器

这两个类都在sun.misc.Launcher类中,属于内部类,不同的是 Extension Class Loader只会加载<JAVA_HOME>\lib\ext目录中的内容,而 Application Class Loader则会加载我们自定义的类
展示一下他们的继承关系 

在这里插入图片描述 

需要提一下sun.misc.Launcher,这是java虚拟机的入口程序,再次之前我们先要了解下虚拟机启动过程:
如何启动?java 命令

java命令是一个入口,执行的时候 会找到对应的执行文件,它会调用java api 接口(java api 接口和jvm 一起构成了jre),接口内部会调用其他接口创建虚拟机(虚拟内存,硬件,CPU等),

然后虚拟机会自动创建bootstrap 类加载器,我们再来看一下Launcher类

在这里插入图片描述 

我们发现这个类位于rt.jar包下,而rt.jar这个包由Bootstrap Classloader负责加载,那么就可以说明引导类和扩展类加载器都是由Bootstrap Classloader负责创建,到现在我们基本可以回答出刚才的问题了:
独立于虚拟机之外是什么意思:扩展类加载器和程序加载器这两个类加载器存在于jar包,要依靠虚拟机创建的启动类加载器而创建的类加载器,这里要说明一下:

  • 一个类由不同的类加载器加载进内存,会在方法区生成两个不同的类,彼此不可见,并且在堆中产生不同Class对象,比较两个对象的Class实例对象是否相等,同一个classloader加载两个类使用“==”或equals做比较是正确的;两个不同的classloader分别加载两个类做比较就会有问题,两个实例不会相等;

3.双亲委派机制

bootstrap类加载器,会创建 扩展类加载器和应用程序类加载器,他们执行原理为:

* 

如果去加载不存在的类会抛出异常,不会返回null 

java 虚拟机执行过程

文章写到这里基本上将类加载机制介绍完了,从头梳理一下jvm的执行过程

  • java命令是一个入口,一般都是通过java -jar的命令启动, 会找到对应的执行文件,它会调用java api 接口(java api 接口和jvm 一起构成了jre),接口内部会调用其他接口创建虚拟机(虚拟内存,硬件,CPU等)
  • 然后虚拟机会创建 bootstrap 类加载器,bootstrap类加载器,会创建 扩展类加载器和应用程序类加载器
  • 应用程序加载器 会首先会去寻找 方法 public static void main(String ars[]) 作为项目的执行入口
  • 同时加载主函数所在的类,如果发现需要加载其他类,递归交由父类加载器加载,如果父类加载器能够加载就加载,不能就递归向下交给下一级加载器加载(一般来说:bootstrap 类加载器 会加载java API 类,辅助jvm运行,扩展类加载器会加载 jdk 的类库。应用程序加载器 会加载项目下的class文件)
  • 主函数所在的类及相关类加载完毕后,加载的结果就是 class文件,被加载的内存的方法区。
  • 接下来就是验证,准备,解析,初始化等一系列操作,直到这个类生命周期结束被卸载
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值