JVM(7):虚拟机类加载机制

一、类加载时机

....待更新

二、类(Class)加载过程

非数组类型加载阶段:

Class文件需要加载到虚拟机中之后才能运行和使用,虚拟机如何加载这些Class?

加载Class类型文件主要三步:加载——>连接(连接过程三步:验证——>准备——>解析)——>初始化

所以类加载过程具体有五个步骤:加载、验证、准备、解析、初始化

数组类型类加载:

         不通过类加载器创建,它直接由JVM创建 

2.1、加载

加载主要完成如下三件事:

  1. 通过全类名获取定义此类的 二进制字节流
  2. 将 字节流 所代表的静态存储结构转换为方法区的 运行时数据结构
  3. 在内存中生成一个代表该类的Class对象,作为方法区这些数据的访问入口。

注意:数组类型不通过类加载器创建,而是通过JVM直接创建

注意:加载阶段和连接阶段交叉进行,加载未结束可能连接已开始

2.2、验证

作为连接阶段第一步。目的是为了确保Class文件字节流中包含的信息符合当前虚拟机要求,并且不会危害虚拟机安全。

验证主要完成如下四件事:

(1)文件格式验证

验证字节流是否符合Class文件格式规范,并且能被当前虚拟机理解

(2)元数据验证

对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求

(3)字节码验证

通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。对Java类的方法体进行详细校验

(4)符号引用校验

发生在虚拟机将符号引用转化为直接引用的时候。是对类自身以外(常量池中各种符号引用)的校验

2.3、准备

作为连接阶段第二步。目的是类变量 分配内存 并为类变量 设置初始值,这些内存都在方法区中分配

注意

(1)这时内存分配的仅包括类变量(static变量),而不包括实例变量。实例变量是在对象实例化时和对象一起才Java堆分配内存;

(2)这里设置的初始值,一般指数据类型默认0值(如0、0L、null、false等)。

2.4、解析

作为连接阶段第二步。目的是虚拟机将常量池中 符号引用替换为直接引用 的过程(也就是得到类、方法、字段在内存中的指针或者偏移量)

JVM为每个类都准备了一张方法表,当需要调用这个类的方法时,只要知道这个方法在方法表中的偏移量(地址)就可以直接调用该方法了。通过解析操作符号引用就可以转换为直接引用从而定位到目标方法在类中的位置,从而使得该方法可以被调用。

(7类操作符号:类或接口、字段、类方法、接口方法、方法类型、方法句柄、调用限定符)

2.5、初始化

类加载最后一步。目的是真正执行Java中代码、执行类构造器方法<clinit>()的过程

JVM规定只有5种情况必须对类初始化:

(1)当遇到 new 、 getstatic、putstatic、invokestatic 这4条直接码指令时;比如new一个类、读取一个静态字段(未被final修饰)、调用一个类的静态方法;

(2)使用使用 java.lang.reflect 包的方法对类进行反射调用时;

(3)初始化一个类如果其父类还没初始化,先触发该父类的初始化

(4)虚拟机启动时,用户需要定义一个包含main方法的主类,虚拟机先初始化这个main类;

(5)略

三、类加载器

3.1、三种类加载器介绍

JVM内置了三个ClassLoader,除了BootstrapClassLoader 其他2个都由Java实现并且继承java.lang.ClassLoader

(1)BootstrapClassLoader(启动类加载器):

最顶层加载器由C++实现,负责加载 %JAVA_HOME%/lib目录下的jar包和类或者或被 -Xbootclasspath参数指定的路径中的所有类;

(2)ExtensionClassLoader(扩展类加载器) :

主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包;

(3)AppClassLoader(应用程序类加载器):

面向用户的加载器,负责加载当前应用classpath下的所有jar包和类。

3.2、双亲委派模型

双亲委派模型介绍

每个类都有一个与之对应的类加载器,系统中的类加载器默认都会使用双亲委派模型:即在加载时会先判断该类是否被加载过,已加载类会直接返回,否则才会尝试加载;加载的时候会把该请求委派给该类父类加载器的loadClass() 处理(所以,所有请求都会被传递到顶层启动类加载器BootstrapClassLoader处理(所以叫双亲)),当父类加载器无法处理时才会自己处理。

注意

(1)每一个类都有一个父类加载器;

(2)当父类加载器为null时,并不代表该类没有父类加载器,而说明该父类加载器为BootstrapClassLoader;

双亲委派模型好处

(1)保证Java程序运行稳定,可避免类重复加载;

不想用双亲委派模型咋办?

可以自定义加载器,继承自java.lang.ClassLoader,重写loadClass()方法

双亲委派模型实现源码分析:

在 java.lang.ClassLoader 的 loadClass() 中,相关代码如下所示:

private final ClassLoader parent; 
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查请求的类是否已经被加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {//父加载器不为空,调用父加载器loadClass()方法处理
                        c = parent.loadClass(name, false);
                    } else {//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                   //抛出异常说明父类加载器无法完成加载请求
                }
                
                if (c == null) {
                    long t1 = System.nanoTime();
                    //自己尝试加载
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

### 若对你有帮助的话,欢迎点赞!评论!转发!谢谢!

上一篇:类文件结构

  参考资料:深入理解Java虚拟机(第2版) : JVM高级特性与最佳实

  参考资料:《实战Java虚拟机》

发布了52 篇原创文章 · 获赞 116 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/RuiKe1400360107/article/details/103534630