小言_互联网的博客

深入理解java虚拟机类加载机制,通俗易懂

302人阅读  评论(0)


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);就会报错:超前引用,即可以赋值,但不可访问

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包,要依靠虚拟机创建的启动类加载器而创建的类加载器

3.双亲委派机制

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

java 虚拟机执行过程

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

  • 1.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文件,被加载的内存的方法区。
  • 接下来就是验证,准备,解析,初始化等一系列操作,直到这个类生命周期结束被卸载

附录

文章还会持续更新和改正
参考文章:
java虚拟机执行过程
classloader详解


转载:https://blog.csdn.net/Alphr/article/details/105644215
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场