虚拟机类加载机制

第一步:加载

  1. 获取二进制字节流
  2. 静态存储结构转化为方法区的运行时数据结构
  3. 在Java堆里生成一个类对象,作为方法区的访问入口

第二步:链接

验证

  1. 验证class文件的标识:魔数 Magic Number
  2. 验证class文件的版本号
  3. 验证常量池(常量类型、常量类型数据结构是否正确、UTF-8是否符合标准等)
  4. class文件的每个部分(字段表、方发表等)是否正确
  5. 元数据验证(父类验证、继承验证、final验证)
  6. 字节码验证(指令验证)
  7. 符号引用验证()

准备

为类变量分配内存并且设置类变量的初始化阶段。
只对static类变量进行内存分配。

static int n = 2;

初始化值是0,而不是2。因为此时还没有执行任何java方法()。

static final int n = 2;

对应到常量池ConstantValue,在准备阶段n必须被赋值成2。
类变量:一般称为静态变量。
实例变量:当对象被实例化的时候,实例变量就跟着确定。随着对象销毁而销毁。

解析

对符号引用进行解析。
直接引用:指向目标的指针或者偏移量。
解析就是将符号引用转换为直接引用。
主要涉及:类、接口、字段、方法(接口、类)等。
CONSTANT_Class_info
CONSTANT_Fieldref_info
CONSTANT_Methodref_info
CONSTANT_InterfaceMethodref_info
CONSTANT_MethodType_info
CONSTANT_MethodHandler_info(方法=>vtable 接口=>itable)
CONSTANT_invokeDynamic_info
匹配规则:简单名字+描述符 同时满足

  1. 字段的解析
class A extends B implements C,D{
	private String str;
}

在本类里面去找有没有匹配的字段===>如果类有实现接口,往上层接口找匹配的字段===>搜索父类
解析字段的顺序A(本类)=>C、D(上层接口)=>B(父类)===>Object(根类)
如果找到了,但是没有权限:java.lang.IllegalAccessError
如果失败:java.lang.NoSuchFieldError

  1. 类方法的解析
class A extends B implements C, D{
	private void inc(); //方法的解析
}

在本类里面去找有没有匹配的方法===>去父类找匹配的方法===>接口列表里面去找匹配的方法(代表本类是一个抽象类,查找结束,抛出java.lang.AbstractMethodError异常)
如果找到了,但是没有权限:java.lang.IllegalAccessError
如果失败(没找到):java.lang.NoSuchMethodError

  1. 接口方法的解析
    在本类里面去找有没有匹配的方法===>父类接口中递归查找
    如果失败(没找到):java.lang.NoSuchMethodError

第三步:初始化

初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:

  1. 声明类变量是指定初始值
  2. 使用静态代码块为类变量指定初始值

初始化就是执行()方法的过程。
JVM初始化步骤

1、假如这个类还没有被加载和连接,则程序先加载并连接该类

2、假如该类的直接父类还没有被初始化,则先初始化其直接父类

3、假如类中有初始化语句,则系统依次执行这些初始化语句

类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

  • 创建类的实例,也就是new的方式

  • 访问某个类或接口的静态变量,或者对该静态变量赋值

  • 调用类的静态方法

  • 反射(如Class.forName(“com.shengsiyuan.Test”))

  • 初始化某个类的子类,则其父类也会被初始化

  • Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类

结束生命周期

在如下几种情况下,Java虚拟机将结束生命周期

  • 执行了System.exit()方法

  • 程序正常执行结束

  • 程序在执行过程中遇到了异常或错误而异常终止

  • 由于操作系统出现错误而导致Java虚拟机进程终止

类加载器

打印类加载器及其父加载器:

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

运行结果

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@4554617c
null

类加载器用来把类加载到虚拟机中。Java虚拟机自带了以三种加载器:

  • 启动类加载器(Bootstrap ClassLoader):负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
  • 扩展类加载器(Extension ClassLoader):该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
  • 应用程序类加载器(Application ClassLoader):该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

双亲委派

双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

public Class<?> loadClass(String name) throws ClassNotFoundException {
	return loadClass(name, false);
}

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) {
                    	//如果存在父类加载器,就委派给父类加载器加载
                        c = parent.loadClass(name, false);
                    } else {
                    	//如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                   
                }

                if (c == null) {
                    // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    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程序安全稳定运行
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值