JAVA类加载机制

类加载过程

类加载过程:即JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程

比如:JVM在执行某段代码时,遇到了class A, 然而此时内存中并没有class A的相关信息,于是JVM就会到相应的class文件中去寻找class A的类信息,并加载进内存中,这就是我们所说的类加载过程。

由此可见,JVM并不是一开始就把所有的类都加载进内存中,而是只有第一次遇到某个需要运行的类 / 预料某个类将要被使用时才会加载,且只加载一次。(在同一个类加载器下,一个类型只会被初始化一次)
如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

类加载器。一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。

类是什么时候加载的?

在java代码中,类型的加载、连接、与初始化过程都是在程序运行期间完成的(类从磁盘加载到内存中经历的三个阶段)

在这里插入图片描述

1. 加载

加载阶段是类加载的第一个阶段

步骤:
	1.	将类的.class文件(字节码文件)中的二进制数据读入到内存中
	2.	再将其(二进制数据)放在运行时数据区的方法区内
	3.	然后在堆区创建一个 java.lang.Class 对象
	4.Java程序员提供了访问方法区内的数据结构的接口
	
加载.calss文件的方式:1)从本地系统中直接加载
(2)通过网络下载.class文件
(3)从zip,jar等归档文件中加载.class文件
(4)从专用数据库中提取.class文件
(5)将java源文件动态编译为.class文件

2. 验证(了解)

验证就是为了确保Class文件中字节流包含的信息符合当前虚拟机的要求

文件格式验证: 是否以 0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内…
元数据验证: 字节码描述的信息进行语义分析
字节码验证
符号引用验证
验证阶段是非常重要的,但不是必须的。可以考虑采用 -Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

3. 准备

类变量:指的是被 static 修饰的变量
类成员变量:除过被static修饰的变量

3.1 内存分配

在准备阶段,JVM 只会为类变量(static)分配内存,而不会为类成员变量分配内存。类成员变量的内存分配需要等到初始化阶段才开始。

public static int OLAF= 666;
public String ICON = "jvm";
例如:在准备阶段,只会为 OLAF 属性分配内存,而不会为 ICON 属性分配内存。

3.2 初始化类型

在JVM为类变量(static)分配内存之后,会对其进行初始化
但是这里的初始化指的是为变量赋予 Java 语言中该数据类型的默认值而不是用户代码里初始化的值

public static int OLAF = 666;
例如:在准备阶段之后,OLAF的值将是 0,而不是 666

但是:对于常量来说(被 static final 修饰),在准备阶段,属性便会被赋予用户希望的值。

public static final int OLAF = 666;
例如:在准备阶段之后,OLAF 的值将是 666,而不再会是 0。
原因:被 final 修饰的变量final 一旦赋值就不会再改变了。
	  而没有被 final 修饰的类变量,其值可能在初始化阶段/运行阶段发生变化,所以没有必要在准备阶段对它赋予用户想要的值。

换句话说,只对static修饰的变量或语句进行初始化。

4. 解析(了解)

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程

举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。

5. 初始化

到了初始化阶段,用户定义的 Java 程序代码才真正开始执行

java程序对类的使用方式可分为两种:
1.主动使用
2.被动使用
一般来说只有当对类的首次主动使用的时候才会导致类的初始化

类的主动使用包含以下几种情况:

1、 创建类的实例 (即 new 的方式)// 注意:通过数组定义来引用类,不会触发此类的初始化
 
2、 访问某个类或接口的静态变量,或者对该静态变量赋值(凡是被final修饰/其实更准确的说应该是:编译器把结果放入常量池的静态字段除外)
 
3、 调用类的静态方法
 
4、 反射(如 Class.forName(“com.gx.yichun”)5、 初始化某个类的子类,则其父类也会被初始化
 
6Java虚拟机启动时被标明为启动类的类( JavaTest ),还有就是Main方法的类会首先被初始化

类的实例化与类的初始化区别

类的实例化是指创建一个类的实例(对象)的过程;
类的初始化是指为类中各个类成员(被static修饰的成员变量)赋初始值的过程,是类生命周期中的一个阶段。

6. 使用(了解)

当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码。

7. 卸载(了解)

当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存。

什么时候 JVM 结束生命周期呢?

1、程序正常执行结束
2、 程序在执行过程中遇到了异常或错误而异常终止
3、 执行了 System.exit()方法
4、 由于操作系统出现错误而导致Java虚拟机进程终止

接口与类加载过程区别

当一个类在初始化时,要求其父类全部都已经初始化过了。
而一个接口在初始化时,并不要求其父接口全部都完成了初始化,当真正用到父接口的时候才会初始化。

例题学习

class Father2{
    public static String strFather="HelloJVM_Father";
    static{
        System.out.println("Father静态代码块");
    }
}
class Son2 extends Father2{
    public static String strSon="HelloJVM_Son";
    static{
        System.out.println("Son静态代码块");
    }
}
public class InitativeUseTest2 {
    public static void main(String[] args) {
       System.out.println(Son2.strSon);
    }
}
运行结果:
        Father静态代码块
        Son静态代码块
        HelloJVM_Son
原因:Son2.strSon是调用了Son类自己的静态方法属于主动使用,所以会初始化Son类。
	  又由于继承关系,类继承原则是初始化一个子类,所以会先去初始化其父类。
class YeYe{
    static {
        System.out.println("YeYe静态代码块");
    }
}
class Father extends YeYe{
    public static String strFather="HelloJVM_Father";
    static{
        System.out.println("Father静态代码块");
    }
}
class Son extends Father{
    public static String strSon="HelloJVM_Son";
    static{
        System.out.println("Son静态代码块");
    }
}
public class InitiativeUse {
    public static void main(String[] args) {
        System.out.println(Son.strFather); 
    }
}
运行结果:
	YeYe静态代码块
    Father静态代码块
    HelloJVM_Father
原因:对于静态字段,只有直接定义这个字段的类才会被初始化(执行静态代码块),这句话在继承、多态中最为明显!
	  Son.strFather中的静态字段是属于父类Father的,也就是说直接定义这个字段的类是父类Father。
	  所以在执行 System.out.println(Son.strFather); 这句代码的时候会去初始化Father类而不是子类Son
class YeYe{
    static {
        System.out.println("YeYe静态代码块");
    }
}
class Father extends YeYe{
    public final static String strFather="HelloJVM_Father";
    static{
        System.out.println("Father静态代码块");
    }
}
class Son extends Father{
    public static String strSon="HelloJVM_Son";
    static{
        System.out.println("Son静态代码块");
    }
}
public class InitiativeUse {
    public static void main(String[] args) {
        System.out.println(Son.strFather);
    }
}
运行结果:HelloJVM_Father
原因:final static
	 所以并不会初始化任何类(Main方法所在类除外)
class Test{
    static {
        System.out.println("static 静态代码块");
    }
    public static final double str=Math.random();  //编译期不确定
}
public class FinalUUidTest {
    public static void main(String[] args) {
        System.out.println(Test.str);
    }
}
运行结果:static 静态代码块
		 0.7338688977344875
原因:这里final已经不是重点了,重点是编译器把结果放入常量池这一步。
	  当一个常量的值并非编译期可以确定的,那么这个值就不会被放到调用类的常量池中。
public class ClassAndObjectLnitialize {
        public static void main(String[] args) {
            System.out.println("输出的打印语句");
        }
      public ClassAndObjectLnitialize(){
            System.out.println("构造方法");
            System.out.println("我是熊孩子我的智商=" + ZhiShang +",情商=" + QingShang);
        }
        {
            System.out.println("普通代码块");
        }
        int ZhiShang = 250;
        static int QingShang = 666;
        static
        {
            System.out.println("静态代码块");
        }     
}
运行结果
		静态代码块
		输出的打印语句
此例中类初始化时会执行以下内容:
		static int QingShang = 666;  //类变量(static变量)的赋值语句
		static   
		 {
		     System.out.println("静态代码块");
		 }
public class ClassAndObjectLnitialize {
        public static void main(String[] args) {
            new ClassAndObjectLnitialize();
            System.out.println("输出的打印语句");
        }
      public ClassAndObjectLnitialize(){
            System.out.println("构造方法");
            System.out.println("我是熊孩子我的智商=" + ZhiShang +",情商=" + QingShang);
        }
        {
            System.out.println("普通代码块");
        }
        int ZhiShang = 250;
        static int QingShang = 666;       
        static
        {
            System.out.println("静态代码块");
        }      
}
运行结果:
		静态代码块
		普通代码块
		构造方法
		我是熊孩子我的智商=250,情商=666
		输出的打印语句		
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值