深入理解JVM学习(b站 张龙视频 P1-P14)

7 篇文章 0 订阅

JVM上不只能运行java程序,scala等其他语言也可以在jvm上运行,只要能生成jvm上可以理解的字节码文件就行。
在cmd中输入jconsole可以出现控制台
jvisualvm也是一种监控工具
jmap命令行工具

类加载
在java代码中,类型的加载、连接与初始化过程都是在程序运行期间完成的
什么是类型 ?定义的class\interface\枚举,并不是对象
类型的加载就是将类型所在的class文件/字节码文件从磁盘上加载到内存里面
连接:将类与类之间的关系处理好,对于字节码的处理验证校验都是在加载连接上完成的。
字节码文件可以被人为操纵,存在错误的可能,需要校验
初始化:对于静态变量进行赋值
提供了更大的灵活性,增加了更多的可能性

类加载器:每个类型都是由类加载器加载到内存当中的
在下面几种情况,java虚拟机将结束生命周期
1.执行System.exit();
2.程序正常执行结束
3.程序在执行过程中遇到了异常或错误而异常终止
4.由于操作系统出现错误而导致java虚拟机进程终止

类的加载:查找并加载类的二进制数据,class文件,就是把类的class文件加载到内存里
连接:

  • 验证:确保被加载类的正确性(class文件是符合JVM规范的)
  • 准备:为类的静态变量分配内存,并将其初始化为默认值(false,0)
  • 解析:把类中的符号引用转换为直接引用

初始化:为类的静态变量分配正确的初始值
类的使用与卸载
类的卸载是与类的加载相对应的

符号引用和直接引用:符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
直接引用可以是
(1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)
(2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
(3)一个能间接定位到目标的句柄
直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。

java程序对类的使用方式可分为两种:
1.主动使用
2.被动使用
所有的java虚拟机实现必须在每个类或接口被java程序首次主动使用时才初始化他们

主动使用(7种):

  • 创建类的实例
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射(如Class.forName(“com.test.Test”))
  • 初始化一个类的子类。当我初始化child类时,也会对parent类进行初始化
  • java虚拟机启动时被表明为启动类的类,即包含main方法的类
  • JDK1.7开始提供的动态语言支持。
    在这里插入图片描述
    除了上面七种情况,其他使用java类的方式都被看作是对类的被动使用,都不会导致类的初始化

在这里插入图片描述
在这里插入图片描述
静态代码块在程序初始化的时候会被执行的
在这里插入图片描述
在这里插入图片描述
mychild1没有被初始化,但是有没有被加载呢,使用-XX:+TraceClassLoading,用于追踪类的加载信息并打印出来。打印出来发现,虽然没有被初始化,但是被加载的

对于静态字段来说,只有直接定义了该字段的类才会被初始化。
初始化一个类的子类时,父类也会被初始化。
在一个类初始化时,要求其父类全部都已经初始化完毕了

-XX:+,表示开启option选项
-XX:+,表示关闭OPTION选项
-XX:=,表示将option的值赋为Value
下面一个例子,加有final关键字 的常量
在这里插入图片描述
//常量在编译阶段会存入到调用这个常量的方法所在的类的常量池当中,
//本质上,调用类MyTest1并没有直接引用到定义常量的类MyParent,因此不会触发定义常量的类Myparent的初始化
//这里指的是将常量存放到MyTest1的常量池中,之后MyTest1与MyParent就没有关系了
//甚至可以将out文件夹下的MyParent.class文件删除

反编译MyTest1.class

E:\java_web_dev\kmoon\out\production\classes>javap -c com.ipoc345.kmoon.MyTest1
Compiled from "MyTest1.java"
public class com.ipoc345.kmoon.MyTest1 {
  public com.ipoc345.kmoon.MyTest1();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #4                  // String hello world
       5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

助记符:
ldc表示将int 、float或是String类型的常量值从常量池中推送至栈顶
bipush表示将单字节的(-128~127)的常量值推送到栈顶
public static final short s = 7;
sipush表示将一个短整型常量值-32768~32767推送至栈顶
public static final int s = 128;
iconst_1表示将int类型1推送至栈顶(iconst_m1~iconst_5,(-1——5))

在这里插入图片描述
所以在编译器是无法确定值的
在这里插入图片描述

Part8.
在这里插入图片描述
当一个常量的值并非编译期间可以确定的时候,那么其值就不会被放到调用类的常量池中,这时候在程序运行中,会主动使用这个常量所在的类,显然会导致这个类被初始化

实例化一个类
在这里插入图片描述
创建数组,并不会初始化类
在这里插入图片描述
在这里插入图片描述
对于数组实例,其类型是JVM在运行期动态生成的,表示为class[Lcom.kmoonwang.mywenda.Myparent3
这种形式动态生成的类型,其父类型是Object
助记符:
anewarray:创建一个引用类型的(如类、接口、数组)的数组,并将其引用值压入栈顶
newarray:创建一个原始类型(如int、char、float、double等)的数组,并将其引用值压入栈顶
在这里插入图片描述
接口中的域默认是static final的,那么当调用这个域的时候就和类一样不用初始化

在这里插入图片描述
调整一下位置结果就变了,这是为什么呢?
下面进行详细分析
在这里插入图片描述
调用静态方法是主动使用的一种情况

  1. 首先进行准备阶段
    给变量赋予默认值,counter1为0,singleton为null,counter2为0
  2. 按照顺序进行变量初始化
    counter1由于没有赋值,所以为默认值0
    singleton进行初始化,counter1 = 1;counter2 = 1;
    counter2进行初始化,counter2 = 0;
    初始化是自上而下的

类加载的顺序
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
Class对象是整个反射的入口

类是由类加载器来加载的
在这里插入图片描述

在这里插入图片描述
前面学到过所有的java虚拟机实现必须在每个类或接口被java程序首次主动使用时才初始化他们
但是一个类没有被初始化,并不代表它没有被加载

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
感觉这个说的有点问题,其实接口和类的本质是一样的,因为接口所有的变量都是static final的,所以相当于类中的一种特殊情况。就把接口考虑成变量定义为static final的类就行了
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
根据双亲委托机制,根加载器加载不了的使用扩展类加载器,扩展类加载器加载不了的使用系统类加载器,系统类加载不了就抛异常了。看起来很像继承关系,其实是包含关系。系统包含了扩展,扩展包含了根

在这里插入图片描述
在这里插入图片描述
当把class换成interface,把myparent5.class和child.class删掉再运行都不会报错
在这里插入图片描述
当interface的常量是编译期间不能确定的时候,那么就会加载类,如果能够确定就放到JVMlearning类的常量池当中
在这里插入图片描述‘下面模拟,当一个类初始化的时候,它的接口不会初始化
在这里插入图片描述
子接口的初始化并不会导致父接口进行初始化

在这里插入图片描述
在这里插入图片描述
想要用loader1加载类,loader1先向上查看自己的父亲加载器-系统类加载器,系统类加载器还有父亲扩展类加载器,扩展类加载器还有根类加载器,然后从根类加载器对类进行加载,加载不成功就用扩展类加载器,不然就用系统类加载器。系统类加载器可以加载成功了。在上述过程中,有一个加载器加载成功就类加载完成了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
String类是由根加载器进行加载的,返回的Null代表根加载器
在这里插入图片描述
C类是由系统(应用)类加载器来加载的
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

MyTest10 static block
----------
Parent2 static block
----------
3
----------
Child 2 static block
4

在这里插入图片描述

parent3 static block
3
do something

在这里插入图片描述
在这里插入图片描述

分别把类加载器打印出来
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值