Java多线程相关知识【25】--类加载器(ClassLoader)--类加载的过程

文章目录


菜鸟的一个学习笔记,欢迎大神批评指正

Java多线程相关知识【25】–类加载器(ClassLoader)–类加载的过程

简介

请参考官方介绍文档。

JVM内存简介

方法区

​ 程序的一个共享区域,他在创建时即可被所有的程序共享,他是堆内的一个逻辑单元,也经常被称为“非堆”,他存放了程序的一些数据结构,还保存了程序的运行时常量池,当内存不够时,会抛出OutOfMemory异常。

运行时常量池

​ 为方法区的一部分,可能在编译时,相关数据即被放入此区域,可提高运行时的速度。

​ 内存很大的一个区域(GC区),可存储类的对象和数组,他非线程私有的,当被标记为可回收时,会被线程进行自动回收。

虚拟机栈

​ 线程私有,保存虚拟机的私有数据,相关的管理有程序自身进行管理,jvm不加以管理。(后进先出)

本地方法栈

​ 存放c和c++方法的区域。

程序计数器

​ 标记程序下一条运行的语句的编号。

结束JVM的运行周期的方式

1. 调用System.exit()进行退出

2. 正常的结束

3. 编写的程序发生异常(未处理Exception等)

4. 虚拟机运行内部出现异常

5. 虚拟机依附的操作系统异常

类加载的阶段

1.加载

进行类的查找并根据给定的名称加载类的二进制数据,只有此阶段用户可操作。

类加载的方式

1. 从本地磁盘中直接加载
2. 从内存中直接加载
3. 通过网络加载.class

​ 可使用URLClassLoader。

4. 从zip、jar等归档文件中加载.class
5. 从数据库中提取.class
6. 动态编译

2.链接

创建对象的过程

1.第一种创建方式(通过句柄池):

栈->堆中的句柄池–|->堆中的实例池

​ |-> 方法区中的对象数据

2.第二种创建方式(通过指针的方式)(速度较快):

栈->堆中对象类的指针和对象实例的数据->对象类型的数据

引用和对象的关系

​ 堆区(对象) 方法区(数据结构)

引用1->对象1->|

引用2->对象2->|--------->对象的数据结构

引用3->对象3->|

2.1. 验证

​ 加载部分和链接部分的部分内容是可以交叉进行的,它可以提高运行效率。例如加载完成后,即可对加载成功的代码进行相关的验证。

​ 验证的目的是为了确保类加载是正确的,若验证失败,则会出现一个VerifyError的异常。
在类的验证阶段主要进行检查以下的内容:

1.检查文件的文件格式是否为.class
2.检查魔术因子是否为0xCAFEBABE
2.检查文件的主从版本号是否支持本虚拟机
4.检查常量池中的常量类型是否受到支持
5.其他的一些认证
6.元数据的验证
  1. 是否有父类
  2. 父类是否允许继承
  3. 是否实现了抽象方法
  4. 是否覆盖了父类的final字段
  5. 其他语义检查
7.字节码的验证

​ 数据流和控制流的分析,即声明为int,但赋值为long。

8.符号的验证

​ 调用了一个不存在的方法和字段等。

2.2. 准备

​ 为类的静态变量分配内存,并初始化为默认值。

2.3. 解析

​ 把类中的符号引用转化为直接引用,主要有以下的几个方面。

类或接口的解析
字段解析
类方法的解析
接口方法的解析

3.初始化

​ 为类的静态变量赋予正确的值。

1. 在静态语句块中,只能访问静态语句块前的变量,而静态语句块后的变量,只能进行赋值操作,而不能进行读操作。

2.虚拟机会保证父类的静态方法优先于子类的静态方法执行。

3.静态代码块在程序运行中不是必须的。

4.接口中也存在静态代码块方法。

5.虚拟机保证静态代码块是运行安全的。

类加载的最终产物是位于堆区中的class对象。

程序对类的使用方式

主动使用的分类

1.new直接使用

2.访问某个类或接口的静态变量,或对静态变量进行赋值操作

3.调用静态方法

4. 反射某个类

5.初始化一个子类

​ 先初始化了父类,再初始化子类。

6.调用启动类

被动使用分类

除了以上的主动使用情况外,剩下的都为被动分类。

Java虚拟机规范中规定:所有虚拟机必须在每个类或接口被程序首次主动使用时才能初始化他们。

类加载的诡异事件

1.通过子类调用父类的静态变量,子类不会被初始化(被动引用)

2.通过数组引用时,对象不会被初始化

Obj o=new Obj[10];//对象不会被初始化

3. 访问简单常量时,不会引起类的初始化

简单常量在编译时会放入常量池中,故可以直接找到,即无需初始化。

4.访问复杂常量时,会引起类的初始化

复杂常量无法在编译时得出相应的值,故不可以直接找到,即需初始化。

5.未进行初始化赋值,可能会产生错误的加载结果

参考以下示例:

1.静态变量在前,静态对象初始化在后
public class Obj {
    private static int x = 0;
    private static int y;
    private static Obj o= new Obj();
    private Obj(){
        x++;
        y++;
    }

    public static Obj getObj(){
        return o;
    }

    public static void main(String[] args) {
        Obj obj=Obj.getObj();
        System.out.println(x);
        System.out.println(y);
    }

}

运行结果为:
1
1

2.静态对象初始化在前,静态变量在后
public class Obj {
    private static Obj o= new Obj();
    private static int x = 0;
    private static int y;
    private Obj(){
        x++;
        y++;
    }

    public static Obj getObj(){
        return o;
    }

    public static void main(String[] args) {
        Obj obj=Obj.getObj();
        System.out.println(x);
        System.out.println(y);
    }

}

运行结果为:
0
1

发生错误的原因
1.静态在前时的变量执行过程
  1. 虚拟机会先为静态对象赋予默认值,即x=0,y=0,obj=null;
  2. 虚拟机为x和y进行初始化,即x=1,y=0;
  3. 虚拟机为obj进行初始化,即x+1,y+1;
  4. 执行的结果为 x=1,y=1。
2.静态在后时的变量执行过程
  1. 对值进行默认值:obj=null,x=0,y=0;
  2. 虚拟机为obj进行正确赋值,即x+1,y+1,运行后结果为x=1,y=1;
  3. 虚拟机为x进行正确的赋值,即x=0;
  4. 经过以上操作后,x=0,y=1。

版权声明:本文为XiaoDiaoDiao521原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。