JVM学习笔记(1)之ClassLoader

JVM学习笔记(1)之ClassLoader

1.JVM简介

JVM是伴随Java这门语言的诞生而存在的,Java的最大特点就是跨平台性,即我们常常说的一次编译,到处运行,这个特性其实就是JVM的功劳,JVM不仅仅是一个虚拟机,更是一种规范,所以任何符合JVM虚拟机规范的语言都可以跑在JVM中,包括Scala、Grooy、Kotlin等

2.JVM、JRE、JDK之间的关系

JDK

JDK(Java Development Kit) 是Java语言的软件开发工具包(SDK)。在JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib合起来就称为jre。

JRE

JRE(Java Runtime Environment,Java运行环境),包含JVM标准实现及Java核心类库。JRE是Java运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器)

JVM

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

下面一张图可以形象的反应他们之间的关系

image

3.类加载器 CLassLoader

类加载过程:

image

  • 装载class文件

将内存中的二进制字节码加载到JVM中的过程

  • 连接

1、验证,确保类文件遵从Java类文件的固定格式

2、准备,为类的静态变量分配内存,并将其初始化为默认值

3、解析,把类中的符号引用转换为直接引用

  • 初始化

为类的静态变量赋予正确的初始值

  • 使用

  • 卸载

4.类的加载、连接、初始化

java程序对类的使用分为主动使用和被动使用

主动使用:
所有的java虚拟机实现必须在每个类或接口被java程序首次主动使用时才初始化他们,主动使用大致可以分为以下6种情况:

  • 1 创建类的实例
  • 2 访问某个类或接口的静态变量,或者对该静态变量赋值或者调用类的静态方法
  • 3 反射 Class.forName(“com.zh.Dog”)
  • 4 初始化一个类的子类
  • 5 Java虚拟机启动时被标明为启动类的类,如Test
  • 6 JDK1.7提供的动态语言支持:java.lang.invoke.methodHandle的实例的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化
5 测试
public class MyTest1 {

    public static void main(String[] args) {

        /*
         * 结果为:
         * Father static block
         * hello world
         * 疑问?如果str1加了final 则不打印Father static block
         */
        System.out.println(Child1.str2);

    }
}

class Father1{
    static final String str1 = "hello world";

    static {
        System.out.println("Father static block");
    }
}


class Child1 extends Father1 {

    static String str2 = "welcome";
    static {
        System.out.println("Child1 static block");
    }
}

执行结果如下图所示:

在这里插入图片描述

由于加载信息太多,上图中截取的只是部分重要的ClassLoader加载信息,其中截图中可以看到加载了MyTest1的二进制码文件以及Father1及Child1的文件信息,如下图显示

在这里插入图片描述

可以看到不仅打印了子类静态块中的信息,而且还打印了父类静态块中的信息,而且父类打印是在子类之前,说明了子类调用自己的静态变量时(子类初始化),必须先初始化父类,当子类调用父类的静态变量时即Child1.str1时,此时JVM加载了子类文件,但是只打印了父类的静态块却并没有打印子类的静态块,说明:只有静态变量所属的类 调用时才会被初始化,如图三所示

在这里插入图片描述

要追踪加载信息需要在VM option➕上

-XX:+TraceClassLoading

JVM参数配置格式如下

-XX:+<option> 设置jvm参数
-XX:-<option> 取消jvm参数
-XX:<option>=<value> 设置jvm参数的值为value

接下来看另外一个代码:

/*
助记符:
       ldc:将int float String等类型的常量从常量池中推送到栈顶
       bipush表示将单字节(-128-127)的常量值推送至栈顶
       sipush表示将一个短整型常量值(-32768-32767)推送至栈顶
       iconst_1表示将int类型1推送至栈顶 (iconst_1 - iconst_5)有待验证
 */
public class MyTest2 {

    public static void main(String[] args) {
        //结果是哈喽 并没有打印Demo1 static block
//        System.out.println(Demo1.s);
        System.out.println(Demo1.j);
    }
}

class Demo1{

    static final String s = "哈喽";

    static final int j = 0;

    static final int k = 128;

    static{
        System.out.println("Demo1 static block");
    }
}

跟上面的代码有所不同的地方在于,这里的静态变量加了final,也就是静态常量,此时执行Demo1.j得出的结果是哈喽,如下图

在这里插入图片描述

可以看到并没打印Demo1的静态代码块,结论:常量(s)会被调用常量的方法(main)所在的类(MyTest)加载到类的常量池中,本质上:调用类并没有直接引用到定义常量的类,因此并不会触发定义常量的类的初始化,注意:MyTest2将常量存到了常量池中,此时MyTest2与Demo1本质上已经没有任何关系了
实验:将生成的Demo1的class文件删除 仍然能打印出哈喽

  • 反编译

反编译Demo1.class文件,进入到classpath文件目录下,输入 javap -c MyTest2.class,如图所示

在这里插入图片描述

main方法下涉及到的助记符:

ldc:将int float String等类型的常量从常量池中推送到栈顶
bipush:表示将单字节(-128-127)的常量值推送至栈顶
sipush:表示将一个短整型常量值(-32768-32767)推送至栈顶
iconst_m:表示将int类型-1推送至栈顶 (iconst_0 - iconst_5)+ iconst_m

接下来我们再看一个类型的代码:

public class MyTest3 {

    public static void main(String[] args) {
        //结果
        // Demo2 static block
        // a5d9eb5a-5057-4ce4-9e0d-431198105439
        System.out.println(Demo2.s);
    }


}

class Demo2 {
    static final String s = UUID.randomUUID().toString();

    static {
        System.out.println("Demo2 static block");
    }
}

代码跟上面的版本不同之处获取了随机UUID,但是打印结果打印了Demo2的静态代码块,如下图所示,什么原因?

在这里插入图片描述

结论:常量非编译期间能确定的,则常量不会存放在调用该常量的方法所在的类的常量池中
,这时在常量程序运行时会导致主动的使用这个常量所在的类 显然会导致这个类被初始化

最后在看一版代码

public class MyTest4 {


    public static void main(String[] args) {
        //结果 Demo4 static block
        Demo4 demo4 = new Demo4();

        //结果 没有任何打印
        Demo4[] demo4s = new Demo4[2];
        System.out.println(demo4s.getClass());
        System.out.println(demo4s.getClass().getSuperclass());


        int[] ints = new int[1];
        System.out.println(ints.getClass());
        System.out.println(ints.getClass().getSuperclass());

        char[] chars = new char[2];
        System.out.println(chars.getClass());
    }
}

class Demo4 {

    static {
        System.out.println("Demo4 static block");
    }
}

区别是类数组代替了普通类,发现new了数组并没有初始化类的静态代码块,如下图所示.

在这里插入图片描述

相信经过上面多个例子,这个代码的结果的原因大家都应该猜到了吧,结论:对于数组来说 其类型是由JVM在运行期动态生成的,表示为[Lcom.zh.classloader.Demo4;这种形式 动态生成的类型,其父类型是Object
对于数组来说,JavaDoc经常将构成素组的元素为Component,实际上就是将数组降低一个维度后的类型

助记符:

anewarray 表示创建一个引用类型的(如类、接口、数组)数组并将其引用值压入栈顶

newarray 表示创建一个指定的原始类型 如int float char的数组并将其引用值压入栈顶

在这里插入图片描述

##### 备注:笔记是我自己整理,内容有些是来源于圣思园张龙老师的JVM课程视频,有些是自己的理解,感谢张龙老师的奉献,讲的真的很精彩,知识点讲的特别的细
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值