JVM整理(三)

类加载与字节码技术

1. 类文件结构
2. 字节码指令
3. 编译期处理
4. 类加载阶段
5. 类加载器
6. 运行期优化

1. 类文件结构

类文件样子:

有哪些东西:

魔数(magic):表示class类型的文件

版本(version):表示类的版本,java8

常量池:常量池长度,method信息,field信息,class信息等

访问标识与继承信息

field信息:成员变量

method信息:方法

附加属性

2. 字节码指令

方法执行流程:

1、jvm把main方法所在的类进行类加载操作,把.class里的字节码读取到内存中
2、.class常量池中的数据载入运行时常量池(方法区的组成部分)

      注:比较小的数字不会存储在常量池中,会和字节码指令存在一起。如果超过一定最大值(整数最大值),就会存储在常量池中

3、方法字节码载入方法区

 4、main线程开始运行,分配栈帧内存

 5、执行引擎开始执行字节码

借助局部变量表和操作数栈

2.1构造方法(前面还有一些字节码指令,我觉这个比较重要)

1)<cinit>()V :整个类的构造方法
public class Demo3_8_1 { 
      static int i = 10; 
      static { 
          i = 20; 
      }
      static { 
          i = 30; 
      }
 }
编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码,合并为一个特殊的方法 <cinit>()V 

 <cinit>()V 方法会在类加载的初始化阶段被调用

2) <init>()V :某个实例的构造方法
public class Demo3_8_2 { 

private String a = "s1";
{
b = 20;
}

private int b = 10;
{
a = "s2";
}
  public Demo3_8_2(String a, int b) {
      this.a = a;
      this.b = b;
  }
    public static void main(String[] args) {
      Demo3_8_2 d = new Demo3_8_2("s3", 30);
      System.out.println(d.a);
      System.out.println(d.b);
   }
编译器会按从上至下的顺序,收集所有 {} 代码块和成员变量赋值的代码,形成新的构造方法, 但原始构造方法内的代码总是在最后

2.2方法调用

public class Demo3_9 {
    public Demo3_9() {
    }

    private void test1() {
    }

    private final void test2() {//final修饰的方法无论是私有还是公有对应的字节码指令都一样
    }

    public void test3() {
    }

    public static void test4() {
    }

    public static void main(String[] args) {
        Demo3_9 d = new Demo3_9();
        d.test1();
        d.test2();
        d.test3();
        d.test4();
        Demo3_9.test4();
    }
}

  • new 是创建【对象】,给对象分配堆内存,执行成功会将【对象引用】压入操作数栈
  • dup 是赋值操作数栈栈顶的内容,本例即为【对象引用】,为什么需要两份引用呢,一个是要配合 invokespecial 调用该对象的构造方法 "<init>":()V (会消耗掉栈顶一个引用),另一个要配合 astore_1 赋值给局部变量
  • 最终方法(fifinal),私有方法(private),构造方法都是由 invokespecial 指令来调用,属于静态绑定
  • 普通成员方法(public)是由 invokevirtual 调用,属于动态绑定,即支持多态,在编译期间不能确定调用那个对象的方法(父类还是子类。。),在运行时确定
  • 成员方法与静态方法调用的另一个区别是,执行方法前是否需要【对象引用】
  • 比较有意思的是 d.test4(); 是通过【对象引用】调用一个静态方法,可以看到在调用invokestatic 之前执行了 pop 指令,把【对象引用】从操作数栈弹掉了😂
  • 还有一个执行 invokespecial 的情况是通过 super 调用父类方法 

 2.3finally

 finally 中的代码被复制了 3 份,分别放入 try 流程, catch 流程以及 catch 剩余的异常类型流程 
 public static void main(String[] args) {
        int result = test();
        System.out.println(result);
    }

    public static int test() {
        try {
            int i = 1 / 0;
            return 10;
        } finally {
            return 20;
        }
    }

 如果finally语句中有return,会吞掉异常,不会抛出,如上代码

2.4synchronized
public static void main(String[] args) {
        Object lock = new Object();
        synchronized (lock) {
            System.out.println("ok");
        }
    }

 注:方法级别的 synchronized 不会在字节码指令中有所体现 public synchronized String test(){}

3. 编译期处理

语法糖:
其实就是指 java 编译器把 *.java 源码编译为 *.class 字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是 java 编译器给我们的一个额外福利(给糖吃嘛)

3.1 默认构造器

3.2 自动拆装箱

 jdk5开始加入

 

3.3 泛型集合取值

 java 在编译泛型代码后会执行 泛型擦除 的动作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了 Object 类型来处理

 

3.4 可变参数

可变参数 String... args 其实是一个 String[] args

3.5 foreach 循环

 增强for循环

注:foreach 循环写法,能够配合数组,以及所有实现了 Iterable 接口的集合类一起使用,其中Iterable 用来获取集合的迭代器( Iterator )

3.6 switch 字符串 

 3.7匿名内部类

 

4. 类加载阶段

4.1 加载 

  • 将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 fifield 有:
                  _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴露给 java 使用
                  _super 即父类
                  _fifields 即成员变量
                  _methods 即方法
                  _constants 即常量池
                  _class_loader 即类加载器
                  _vtable 虚方法表
                  _itable 接口方法表
  • 如果这个类还有父类没有加载,先加载父类
  • 加载和链接可能是交替运行的

注:instanceKlass 这样的【元数据】是存储在方法区(1.8 后的元空间内),但 _java_mirror是存储在堆中 

 

4.2 链接

验证 
验证类是否符合 JVM规范,安全性检查
准备 
为 static 变量分配空间,设置默认值 
  • 1.7开始存储在堆中
  • static 变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成 
  • 如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
  • 如果 static 变量是 final 的,但属于引用类型(new对象),那么赋值也会在初始化阶段完成
解析
将常量池中的符号引用解析为直接引用 

 

4.3 初始化 

<cinit>()V 方法,类的初始化方法,初始化即调用 <cinit>()V ,虚拟机会保证这个类的『构造方法』的线程安全
发生的时机 
概括得说,类初始化是【懒惰的】
  • main 方法所在的类,总会被首先初始化
  • 首次访问这个类的静态变量或静态方法时
  • 子类初始化,如果父类还没初始化,会引发
  • 子类访问父类的静态变量,只会触发父类的初始化
  • Class.forName
  • new 会导致初始化

不会导致类初始化的情况

  • 访问类的 static final 静态常量(基本类型和字符串)不会触发初始化
  • 类对象.class 不会触发初始化
  • 创建该类的数组不会触发初始化
  • 类加载器的 loadClass 方法
  • Class.forName 的参数 2 为 false 时

5. 类加载器

 

6. 运行期优化 

6.1 即时编译 

分层编译 

 

方法内联

 

字段优化

6.2 反射优化 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值