类加载过程(时机)略解

目录:
java虚拟机汇总

  1. class文件结构分析
    1).class文件常量池中的常量项结构
    2). 常用的属性表的集合
  2. 类加载过程<<== 现在位置
    1).类加载器的原理以及实现
  3. 虚拟机结构分析
    1).jdk1.7和1.8版本的方法区构造变化
    2).常量池简单区分
  4. 对象结构分析
    1).压缩指针详解
  5. gc垃圾回收
  6. 对象的定位方式

本文题外链接:类加载器及其实现
类加载,名副其实,就是将你class文件的内容,加载进jvm,过程分为三部,加载链接初始化,先了解什么时候发生类加载,

1.类加载(类初始化)的时机(什么时候发生类加载)

—当你的类被实例化的时候,即创建了这个对象的实例 new Test();
—当你访问了或修改了该类里的静态变量的时候Test.a=4;
—当你调用此类的静态方法的时候Test.getA();
—当你用反射的时候Class.forName()
—此类的子类被初始化的时候
—JVM启动时标明的启动类,即文件名和类名相同的那个类
此外还需要注意几个点
访问final修饰的常量并不会进行初始化
通过子类访问父类的静态变量,父类初始化,子类不会初始化

2.类加载过程

在这里插入图片描述

  1. 加载
    此加载是类加载的第一步,原来不是只有一个class文件吗,现在加载的过程就是将此class文件以二进制流的方式用类加载器加载进jvm,并生成一个Class对象(就是我们调用的Test.class即java.lang.Class),存放在堆中,详细的类加载器及其实现

  2. 链接(连接)
    为什么叫链接呢?其实就是将符号引用转化为直接引用,大白话来讲,你的calss文件中不是用**_index符号来指向一个常量项吗,这就是符号引用,java并不知道所引用的实际地址,引用的可能只是一个utf8字符串,他不是一个内存地址,直接引用就是分配好以后你的栈,或者方法区,会有一个实际的地址引向存放的地址,这就是直接引用
    将符号引用转化为直接引用 链接也就做了这些事
    连接在这里又分为三个步骤

    1. )验证

      官方的话:

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

      它分为

      1. 文件格式验证
        魔数是不是咖啡宝贝啊,我们虚拟机可不可以处理你这个版本号啊,常量池有没有不能被处理的类型啊等等…详细百度.

      2. 元数据验证
        这个类是否继承了不允许被继承的类(final)啊,他如果是可以实例的,那是否覆盖了父类所有的抽象方法啊。。等等

      3. 字节码验证
        确定程序语义是合法的、符合逻辑的

      4. 符号引用验证
        符号引用中找到的的全限定名是否有对应的类啊,等等

      总之一句话:检验此class是否有问题,不详细解释,有需求的话百度

    2. )准备

      准备阶段是正式为类变量分配内存并设置类变量初始值的,比如你定义了一个
      static int a = 3;
      那么在此阶段,会审请一个a的存储空间并赋默认值 0,a此时值为0,
      当然也就是说此时静态变量已经被创建,但是没有被赋值(只有初始值),后面在初始化的时候再统一赋值,
      注意: 如果该变量是静态的,那么此值在这里就会被赋值,后会作为常量存放在运行时常量池

    3. )解析
      解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也是我们连接的主要过程
      就是原来的class以及类信息已经存入内存了(加载时做的),但是此时你的Test.b访问b变量不能成功,此时你的a还是一个符号引用不能指向正确的Class里面的对应地址,所以这一步就是将你的所有引用指向实际地址,即符号引用替换为直接引用
      详细过程很复杂分为

      1. 类或接口的解析
      2. 字段解析
      3. 类方法解析
      4. 接口方法解析

    想要了解其中的具体步骤可以百度

  3. 初始化
    初始化也是类加载的最后一步,还记得,大白话来说就是给你申请好的变量赋初始值,比如你在连接过程中的准备过程申请过了变量空间,但是此时没有赋值,所以在这里会统一赋值,他会默认将你写的所有赋值语句根据写的静态变量赋值和静态代码块的赋值,按顺序摘取下来依次执行,比如

class Test{
    
    
	static int a = 4;
	static{
    
    
		a=9;
	}
}

在这里准备阶段已经将a变量空间申请好了,但是是默认值0,在这里将变量赋值操作 a=4,静态代码块里的操作a=9摘下依次执行,即a被赋值为4,再赋值为9,最后值为9,其实这里静态代码块的作用不就是刚给静态变量初始化的吗,就跟我们构造函数是给变量初始化一样,
再看一个例子

class Test{
    
    
	static int a = 1;
	static{
    
    
		int a = 3;
	}
	public static void main(String []ages){
    
    
		System.out.println(a);
	}
	输出1
}

…别被迷惑了,这个静态代码块里面的int a只是局部变量而已出来代码块就没了,再看一个

lass Test{
    
    
	
	static{
    
    
		a = 3;
		System.out.println(a);
	}
	static int a = 1;
	public static void main(String []ages){
    
    
		
	}
	非法向前引用

此时a可以被赋值,即a=3不会报错,因为我们的a已经被申请了存储空间,而输出语句会报非法向前引用异常,注意下即可,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问!!!

为了加深变量的初始化,再来看一个举例。看下输出什么

class Test{
    
    
	private static Test test = new Test();
    public static int a;
    public static int b=0;
     
    private Test(){
    
    
        a++;
        b++;
    }
    public static Test getTest(){
    
    
        return test;
    }
    public static void main(String []ages){
    
    
   		System.out.println(Test.getTest().a);
		System.out.println(Test.getTest().b);
	}
}

在这里插入图片描述
当然,这是一个单例模式,来用分析一下,
连接步骤中的准备过程:将test,a,b,赋初值null,0,0
初始化过程:摘取赋值语句,按顺序执行,注意此时发现了new Test()语句,此时的a是0,b是0!!,然后new ,执行构造代码块(这里没有)和构造方法,将a和b增加,此时a,b都为1,然后继续摘取,int a这条语句没有赋值语句跳过,int b=0这条语句的b=0摘取下来,然后没有静态代码块,结束,这些全部会被装到clinit方法中(具体的有兴趣了解可百度),统一执行,好了 ,也就是说 此时 clinit里的语句为 test =new Test();b=0;
结果b被重新赋值了0,注意此时b是静态的,全class仅有一个,所以会影响之前new 出来的对象,这样输出b就是0了,那么我们换个顺序试试?

class Test{
    
    
   
   public static int a;
   public static int b=0;
   private static Test test = new Test();
   private Test(){
    
    
       a++;
       b++;
   }
   public static Test getTest(){
    
    
       return test;
   }
   public static void main(String []ages){
    
    
  		System.out.println(Test.getTest().a);
   	System.out.println(Test.getTest().b);
   }
}

这个语句输出是正常的
在这里插入图片描述
请读者在分析一边吧,这里就不做分析了,

总结:类加载过程:
加载过程:一句话来说就是,将class文件传换成字节,再转换为class对象,放入内存
连接过程:一句话来说就是,将符号引用转化为直接引用
初始化过程:一句话来说就是顺序执行静态初始化代码
!!记住类加载指的是加载,连接,初始化这三部分,触发条件在开头指出了,我们也可以单独的进行加载(自己实现一个类加载器加载),连接过程我们无法模拟,涉及内存,初始化过程也是,但是要知道:连接过程依赖加载,初始化过程依赖连接和加载,经常搞不清类加载和类初始化的这里应该明白了,也就是说类加载某种程度上也可以写成类初始化,因为触发了初始化后,发现没有执行前两步会立即去执行

如果此文章有任何错误请立即指出,博主会立即改正

猜你喜欢

转载自blog.csdn.net/lioncatch/article/details/106032324