深入理解java虚拟机2 - 字节码指令与类加载机制

什么是字节码指令

  • 字节码指令是包含在字节码中的指令,是jvm可执行的指令,可以说是jvm层面的汇编语言,或者说是java代码的最小单元。

字节码指令格式

java虚拟机的指令由一个操作码和零至多个操作数构成。

  • 操作码:一个字节长度,代表某种特定操作含义的数字。(总数不超过256个)
  • 操作数:代表此操作的参数。

字节码指令分类

  1. 加载与存储指令:用于将数据在栈帧中的局部变量和操作数栈中来回传输。
  2. 运算指令:用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。大体上算术指令可分为两种:对整形数据进行运算的指令和对浮点型数据进行运算的指令。
  3. 类型转换指令:可以将两种不同的数值类型进行相互转换。
  4. 对象创建与访问指令:用于创建和访问对象。
  5. 操作数栈管理指令:用于直接操作数栈。
  6. 控制转移指令:用于让java虚拟机有条件或无条件地从指定位置继续执行程序。
  7. 方法调用和返回指令:
    方法调用指令:用于调用各种方法(实例方法,接口方法,实例初始化方法,私有方法,父类方法)。
    返回指令:用于返回各种类型的返回值。
  8. 异常处理指令:java虚拟机处理异常采用异常表来完成,不采用字节码指令完成。
  9. 同步指令:java虚拟机支持方法级的同步和方法内部一段指令。

虚拟机类加载机制

java虚拟机把类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这个过程称为虚拟机的类加载机制。
因为java里类型的加载,连接和初始化都是在程序运行期间完成的(动态加载动态连接),这为java提供了极高的拓展性和灵活性。

类加载特性

  • 类加载子系统负责从文件或网络加载class字节流
  • 类加载子系统会读取字节码中的信息,运行时存储到JVM内存
  • 任何Class要被加载,都要符合JVM字节码规范

类加载过程

一个类从被加载到虚拟机内存中开始,到卸载出内存为止,会经历7个阶段。
在这里插入图片描述
其中,验证,准备,解析统称为连接

加载阶段

  • 读取字节码二进制流
  • 解析字节码二进制流的静态数据转换为运行时JVM方法区数据
  • 在内存中生成一个代表这个类的java.lang.Class对象,放入堆中,作为方法区的访问入口
  • 在加载类过程中,必然触发父类加载
Class实例何时被创建
  • new实例化 A a = new A();
  • 反射 Class clzA = Class.forName(“com.uestc.A”);
  • 子类加载时父类同时加载
  • JVM启动时,包含main方法的主类
  • 1.7动态类型语言支持

连接阶段

验证

连接的第一步,目的是确保class文件的字节流中包含的信息符合规范。
在这里插入图片描述

准备

准备阶段正式为类中定义的变量(即静态变量,被static修饰的变量),分配内存并设置初始值,这些变量都在方法区中进行分配。

注意:这时候进行内存分配的只有类变量,不包括实例变量,实例变量会在对象实例化时随着对象一起分配在java堆中。
在这里插入图片描述
例如:public class A{public static int a = 100;}
准备阶段:a = 0
初始化阶段:a = 100

解析

解析是连接的核心阶段,是java虚拟机将常量池内的符号引用替换为直接引用的过程,也就是将字节码中的静态字面关联转换JVM内存中的动态指针关联。

  • 类解析
  • 字段解析
  • 方法解析
  • 接口解析

初始化

加载、验证、准备、解析都是由虚拟机主导的,与代码无关。
初始化则是通过代码生成clint,完成类初始化操作,也就是说初始化阶段是执行类构造器方法< clint >()的过程。

  • < clint >()不是在java代码中直接编写的方法,它是javac编译器的自动生成物。
  • < clint >()方法用于完成类的初始化操作,对类(静态)变量赋值,执行static代码块。所以,如果没有类变量和静态代码块,就不会产生< clint > ()方法。
  • 子类的< clint > 方法会优先执行父类的< clint >()方法。因此,java虚拟机中第一个被执行的< clint >()方法的类型是java.lang.Object。
  • 接口和类一样,都会生成< clint >()方法(接口不能使用静态代码块,但是仍有变量初始化操作),但执行接口的< clint >()方法不需要先执行父接口的< clint >()方法,因为只有当父接口中定义的变量被使用时,父接口才会初始化。接口的实现类在初始化时也不会执行接口的< clint >()方法。
  • 在多线程环境中,< clint >()方法默认会增加同步锁,只有第一个线程会执行< clint >(),其他线程处于阻塞等待状态,当第一个线程执行< clint >()后,代表该类加载完毕,后续线程不会再执行< clint >()。确保< clint >()方法只执行一次。

类加载器

三层类加载器
在这里插入图片描述

面试题:Class实例在JVM是全局唯一的吗?

不是,同一个Class被不同的类加载器加载后在JVM中产生的类对象是不同的。
推导:在同一个类加载器作用范围内Class实例加载时才会保持唯一性。

双亲委派模型

在这里插入图片描述
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。
因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

双亲委派的优点

  • 保护了类不会被重复加载
  • 禁止用户污染java开头的核心包,保证java程序的稳定

面试题:自定义类java.lang.Cusom运行时会有什么问题?

会报错,java.lang开头的包都是由启动类加载器加载,启动类加载器不会加载自定义类,所以自定义类包名不能是系统的核心包名。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值