深入理解java虚拟机(3)-----字节码文件格式和类加载

字节码文件格式和类加载

1字节码结构

*.java文件经过javac编译后得到*.class文件,称为字节码文件,字节码文件时构成各种平台虚拟机的关键基石,字节码文件包含了java虚拟机指令集和若干其他辅助信息,好多语言比如jRuby Groovy经过编译都会生成字节码文件在虚拟机上运行,所以字节码文件是虚拟机的重要基石

《深入理解java虚拟机(3)-----字节码文件格式和类加载》
Class文件是一组以8字节为基础单位的二进制流,各个数据项目按照严格的顺序排列在class文件中,中间没有任何的分隔符,这使得class文件在存储的内容全部是虚拟机运行程序所必须的,当存储的数据大于8位就采用大端模式。class文件不想xml等语言有分隔符,所以各种数据类型的排列顺序还是数量都是严格的。

<深入理解java虚拟机>插图
《深入理解java虚拟机(3)-----字节码文件格式和类加载》
在class文件中,class文件采用了一种类似于c语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型,无符号数和表,无符号数是基本的数据类型,比如u1,u2,u4,u8分别代表这个数据有几个字节存储(大端模式);表就是有无符号数和其他表组成的数据类型,所有表都已_info结尾。下面大致分析一下这张插图

u4 magic  #4个字节的文件标识,称为魔数
u2 minor_version #2个字节次版本号
u2 major_version #2个字节主版本号
u2 constant_pool_count #2个字节代表常量池的数目大小,就是接下来多少个数据类型是常量池数据
cp_info constant_pool #常量池数据,常量池是空间最大的数据项目之一,它的文件结构与其他项目关联很多,常量池基本类型有14种,如何分析就和分析class文件一样,根据常量池项目类型的各种结构信息一层层分析
u2 access_flags #访问标志,代表类或者接口层次的访问标志
u2 this_class #代表类索引,根据u2代表的数据得到存储在常量池中的资源,确定类的全限定名
u2 super_class #父类索引,同上
u2 interfaces_count #用于确定下面的interfaces的大小
u2 interfaces #interfaces_count 个连续的u2数据,每个代表一个常量池的资源,一个接口
u2 fields_count #字段的数目
field_info  fields #fields_count个连续的字段表,具体分析查看field_info结果

#后面的方法表和属性表分析和字段相同

总结,class文件中存储的具体信息格式不需要很清楚,但是我们要明白,class文件数据的存储顺序比如按照虚拟机规范的严格格式(前4个字节时魔数,接下来4个字节存放版本号,等等顺序,大小必须正确),否则在运行时无法正确加载。还有一个class文件对应唯一的一个类或接口的定义信息,我们可以在一个java文件中写2个类或者接口(内部类不算)但是编译出来的class文件等于所以class加上interface

2虚拟机类加载机制

在上面我们了解到,在class文件中的各种信息,最终都要加载到虚拟机中才能运行和使用,但是虚拟机如何加载这些class文件? (虚拟机的类加载机制,虚拟机把描述类的数据从class文件加载到内存,并对数据进行效验,转换解析和初始化,最终形成可以被虚拟机直接使用的java类型)

java语言的特点?c语言静态编译完成后直接把原文件编译的二进制文件和库文件进行连接后产生可执行文件,在java语言类的加载,连接,初始化都是运行期间完成的,这位java语言提供了高度的灵活性,因为类加载器加载的类可以是磁盘上的class文件,也可以是网络或者解压得到的二级制文件(符合class格式),这里的class文件是一串二进制的字节流

2.1类的加载时机

虚拟机规范了下面情况必须对类进行初始化?
1. 遇到new,getstatic,putstaic,invokestatic四个字节码指令时,如果类没有进行初始化,则先进行类的初始化过程(new ,读取或者设置类的static字段,final的放在常量池除外,调用类的静态方法)
2. 使用java.lang.reflect包进行反射调用
3. 当初始化一个类的方法,如果父类没有初始化,先初始化父类
4. main方法所在的类在虚拟机启动时加载

2.2类的加载过程

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中准备、验证、解析3个部分统称为连接(Linking)。如图所示。
《深入理解java虚拟机(3)-----字节码文件格式和类加载》
加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)

加载

在装载阶段,虚拟机需要完成以下3件事情
        (1) 通过一个类的全限定名来获取定义此类的二进制字节流
        (2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
        (3) 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
    虚拟机规范中并没有准确说明二进制字节流应该从哪里获取以及怎样获取,这里可以通过定义自己的类加载器去控制字节流的获取方式

验证

    如果我们是从自己本地的class文件加载类信息肯定不会出错,但是我们上面讲到了类的加载只是加载了一系列的二级制字节码,无法保证字节码的正确性,所以需要验证

准备

准备阶段是正式为类变量分配并设置类变量初始值的阶段,这些内存都将在方法区中进行分配(因为这里的变量都是类变量,实例变量在堆,类变量在方法区)
如:
public static int value = 123;
value在准备阶段过后的初始值为0而不是123,而把value赋值的putstatic指令将在初始化阶段才会被执行

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行

初始化

类初始化阶段是类加载过程的最后一步,到了初始化阶段,才真正开始执行类中定义的java程序代码。在准备阶段变量已经付过一次系统要求的初始值,而在初始化阶段,则根据程序猿通过程序制定的主管计划去初始化类变量和其他资源,或者说:初始化阶段是执行类构造器<clinit>()方法的过程.
<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的
<clinit>()方法与实例构造器<init>()方法不同,它不需要显示地调用父类构造器,虚拟机会保证在子类<init>()方法执行之前,父类的<clinit>()方法方法已经执行完毕 ----------
就是由于父类的<clinit>()方法先与子类执行,所以在父类的static语句先于子类执行,然后是父类的非static语句块和构造方法,接下来是子类的非static语句块和构造方法 ----------

2.3类加载器

参考地址:http://blog.csdn.net/gjanyanlig/article/details/6818655/
《深入理解java虚拟机(3)-----字节码文件格式和类加载》

    原文作者:java虚拟机
    原文地址: https://blog.csdn.net/chenqianleo/article/details/77621086
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞