JVM学习Day02

本文详细介绍了Java字节码文件的处理工具如javap和jclasslib,以及阿里arthas的监控功能。重点讲解了类的加载、连接、初始化过程,包括验证、准备、解析阶段,以及静态代码块和类初始化的执行顺序。
摘要由CSDN通过智能技术生成
字节码文件(书接上回)
常用工具:

javap -v 字节码文件名(jar包需要使用jar -xvf解压)命令,jdk自带的反编译工具,控制台呈现字节码文件(有的时候还是非常方便的)。直接输入javap会给你很多用法的提示。

jclasslib的IDEA插件(这个非常的方便),代码修改之后要编译一下字节码才会给你更新哦。

阿里arthas,是一款线上监控诊断产品,通过全局视角实时查看应用  load 、 内存gc 、线程的状态信息,并能在不修 改应用代码的情况下,对业务问题进行诊断大提升线上排查效率。

dashboard面板-i设置时间间隔-n设置刷新次数(cls可以清楚黑窗的所有行)结果见下

dump可以把字节码报错到指定目录

jad可以反编译类的源代码

类的生命周期(加星)

一个类加载、使用、卸载的整个过程(加载、连接、初始化、使用、卸载),一张好看的图

加载阶段

1.加载(Loading)阶段第一步是类加载器根据类的全限定名通过不同的渠道(本地文件、动态代理生成、通过网络传输)以二进制流的方式获取字节码信息。

2.类加载器在加载完类之后,Java虚拟机会将字节码中的信息保存到方法区中。

3.类加载器在加载完类之后,Java虚拟机会将字节码中的信息保存到内存的方法区中。生成一个InstanceKlass 对象,保存类的所有信息,里边还包含实现特定功能比如多态的信息。

4.同时,Java虚拟机还会在堆中生成一份与方法区中数据类似的java.lang.Class对象。作用是在Java代码中去获取类的信息以及存储静态字段(存在堆区)的数据(JDK8及之后)。

对于开发者,只需要访问堆区的Class对象,不需要访问方法区。

连接阶段

三个部分,验证(验证内容是否满足虚拟机规范)、准备(给静态变量赋初值)、解析(将常量池中的符号引用替换成指向内存的直接引用)

验证(了解一下即可)四个部分1.文件格式验证(比如之前学过的魔数,主次版本号)2.元信息验证,比如类必须有父类 3.验证程序执行指令的语义,比如指令执行到一半强制跳转到其他方法中 4.符号引用验证,比如是否访问了其他类中的private方法等。

主版本号不能高于运行环境主版本号,如果主版本号相等,副版本号也不能超过。

准备,只会给静态变量赋值,每一种基本数据类型和引用数据类型都有其初始值

final修饰的基本数据类型的静态变量,直接给值,反正它后面又不会变

解析,将常量池中的符号引用替换为直接引用,符号引用是在字节码文件中使用编号来访问常量池中的内容,直接引用使用的是内存中的地址,效率当然更高。

初始化阶段

执行静态代码块中的代码,并为静态变量赋值,会执行clinit部分的字节码指令,clint中字节码指令的执行顺序和java中的编写顺序是一致的

导致类的初始化的方式:

1.访问一个类的静态变量或者静态方法(final修饰的不会出发初始化)

2.调用Class.forName

3.new一个该类的对象时

4.执行Main方法的当前类

添加-XX:TraceClassLoading参数可以打印出加载并初始化的类

来两道题把,看这个输出啥

public class Test1 {
    public static void main(String[] args) {
        System.out.println("A");
        new Test1();
        new Test1();
    }
    public Test1(){
        System.out.println("B");
    }
    {
        System.out.println("C");
    }
    static {
        System.out.println("D");
    }
}

答案是DACBCB,DA我想到了,CB这个顺序我一开始没太理解

执行顺序是这样的:

  • 静态块:在类首次加载到 JVM 时执行,只执行一次。
  • 实例初始化块:在每次实例化对象之前执行,每创建一个实例就执行一次。
  • 构造函数:在实例初始化块之后执行,用于初始化新创建的对象。

clinit指令在特定情况下不会出现如下:

1.无静态代码块且无静态变量赋值语句

2.有静态变量的声明,但是没有赋值语句

3.静态变量的定义使用final关键字,这类变量会在准备阶段进行初始化

涉及到继承时,直接访问父类的静态变量,不会触发子类的初始化;子类的初始化clinit调用之前,会先调用父类的clinit初始化方法

这就引入了下一道题

public class Test2 {
    public static void main(String[] args) {
        new B02();
        System.out.println(B02.a);
    }
}
class A02{
    static int a=0;
    static {
        a=1;
    }
}
class B02 extends A02{
    static {
        a=2;
    }
}

这里a=2应该没啥说的,如果去掉newB02()这句话就只有a=1了

再来两道小题

第一道会输出什么呢

public class Test3 {
    public static void main(String[] args) {
        Test3_A[] arr =new Test3_A[10];
    }
}
class Test3_A{
    static {
        System.out.println("Test3_A的静态代码块运行");
    }
}

这里什么都不会输出,数组的创建不会导致数组中的元素的类进行初始化

下一道,这会输出什么呢

public class Test4 {
    public static void main(String[] args) {
        System.out.println(Test4_A.a);
    }
}
class Test4_A{

    public static final int a=Integer.valueOf(1);
    static {
        System.out.println("Test4_A的静态代码块运行");
    }
}

Test4_A的静态代码块运行,1。   final修饰的变量如果赋值内容需要执行指令才能得出结果,会执行clinit方法进行初始化,这个例子很好地展示了即使字段是 finalstatic 的,如果其初始化不是编译时常量表达式,类初始化仍然会按照预期进行。

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值