Java 类加载:字节码加载到Jvm中

前言

生成字节码后,这些数据如何加载到jsm中,并怎么存储成为了问题,本文主要研究一下这个内容。

加载到jvm 内存中

通过javac 转换成.class 字节码文件,这个时候计算机还是不能直接识别的,由jvm加载class文件,JVM的类加载是通过ClassLoader及其子类来完成的,再翻译成二进制指令,Java字节码的执行是由JVM解释器引擎来完成,类的层次关系和加载顺序可以由下图来描述:

我们拆分这几个区域大家一起看下:

  • 加载
    加载主要是将.class文件(也可以是zip包)通过二进制字节流读入到JVM中。 在加载阶段,JVM需要完成3件事:
    1)通过classloader在classpath中获取XXX.class文件,将其以二进制流的形式读入内存。
    2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
    3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

  • 连接:验证
    这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害到自身的安全,验证大致会分为4个阶段的校验动作:
    (1)文件格式验证:主要验证字节流是否符合Class文件的规范
    (2)元数据验证:对字节码描述的信息进行语义分析,以保证描述的信息符合java语言规范的要求。
    (3)字节码验证:主要的目的是通过对数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
    (4)符号引用验证:该验证可以看作对类自身以外的信息进行匹配校验。此验证发生在下文的解析阶段

  • 连接:准备
    正式为类变量(其实是静态变量)分配初始内存并设置初始值(并不是代码的初始赋值)的阶段,这些变量所使用的内存都将在方法区中进行分配

  • 连接:解析
    将虚拟机中常量池内的符号引用替换为直接引用的过程,
    (1)符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义定位到目标即可。
    (2)直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
    解析过程是也可以在运行过程中发生,这个跟动态语言调用相关。

  • 初始化
    初始化是类加载的最后一步,前面的类加载的过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的java程序代码。它主要是负责:初始化阶段是执行类构造器< clinit >()方法的过程, < clinit >()是编译器自动收集类中所有的类变量的赋值动作、静态代码块产生的。

  • 使用
    当有继承关系时,先初始化父类再初始化子类,所以创建一个子类时其实内存中存在两个对象实例。

  • 卸载
    即销毁一个对象,一般情况下中有JVM垃圾回收器完成。代码层面的销毁只是将引用置为null。

类加载器

类加载器是用来加载字节码文件的,主要分为:

  • 启动类加载器(Bootstrap ClassLoader):这个加载器使用C++语言实现,负责加载虚拟机启动所需要的类库。它只能加载自己能够识别的类。

  • 扩展类加载器(Extendtion ClassLoader):它负责加载<JAVA_HOME>\lib\ext目录中的或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以使用扩展类加载器。继承自抽象类java.lang.ClassLoader。

  • 应用程序类加载器(Application ClassLoader):负责加载CLassPath上所指定的类库,如果应用程序没有自定义过自己的类加载器,一般情况下这就是程序的默认类加载器。继承自抽象类java.lang.ClassLoader。

  • 自定义类加载器(User ClassLoader):自己定义的类加载器,继承自抽象类java.lang.ClassLoader。

双亲委派模型的工作过程:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,因此所有的加载请求最终都会委派到启动类加载器中。只有父类加载器反馈自己无法加载这个类时,子类加载器才会尝试自己去加载。

new一个对象

  • 检查这个指令的参数是否能在常量池中能否定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。去方法区 找这个对象的

  • 为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来,方法有指针碰撞、空闲列表

  • 虚拟机需要将分配到的内存空间中的数据类型都初始化为零值(不包括对象头);接下来虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息都存放在对象的对象头中。

一个class 有那些占用内存呢?

  • 对象头:16字节,markword 8字节,kclass:8字节
  • 对象类字段、父类字段:具体的字段按照具体的值来弄
  • 对齐填充:整体对象是8的倍数

问题:静态代码块、普通代码块、构造函数执行顺序

  • 静态代码块:这个先执行,因为它是在类加载的过程中就执行。
  • 普通代码块:再就是普通代码块执行,对象一建立就运行构造代码块了,而且优先于构造函数执行。
  • 构造函数:最后就是构造函数,对象一建立,就会调用与之相应的构造函数。

问题:静态类、静态内部类加载

如果一个类要被声明为static的,只有一种情况,就是静态内部类,就像静态方法一样被声明。静态有一些特点:
1.全局唯一,任何一次的修改都是全局性的影响

2.只加载一次,优先于非静态

3.使用方式上不依赖于实例对象。

4.生命周期属于类级别,从JVM 加载开始到JVM卸载结束。

  • 什么时候加载呢?
    静态内部类在使用的时候才会加载,可以参考博客测试一下,但是我觉得为啥为啥不跟静态变量一样直接使用呢?

  • 适用场景
    静态内部类使用场景一般是当外部类需要使用内部类,而内部类无需外部类资源,并且内部类可以单独创建的时候会考虑采用静态内部类的设计

代码执行

可以参考这个博客:Java程序编译和运行的过程

参考博客

Java类的加载机制(类加载和初始化顺序)

Java提高篇——静态代码块、构造代码块、构造函数以及Java类初始化顺序
Java静态类

静态内部类何时初始化

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章