JVM(一)初步

1. jvm生命周期

image
image

2.内存布局:

image

3. 类的加载过程(生命周期):

加载,链接(验证、准备、解析),初始化,使用,卸载。链接结束后,类已经加载到内存中了,初始化表示类装载到系统中了。

3.1 加载:

image
image
image
image
image
image
image
方法区和堆区的对象,相互关联(引用)
image
方法区的是有cpp编写的,java程序员一般不能直接操作,而堆区的可以
image
java.lang.Class对象可供反射使用。
image

  • PS:基本数据类型由虚拟机预先定义,不需要加载

数组的加载

  • 数组类由JVM 在运行时根据需要动态生成的。JVM使用指定的元素类型和数组维度来创建新的数组类。
  • 当 JVM 需要创建一个数组类时,它会检查数组元素的类型:
    • 如果数组元素是基本数据类型(如 int、char 等),则 JVM 会将该数组类标记为与引导类加载器(Bootstrap ClassLoader)关联,不需要通过常规的类加载器加载;
    • 如果数组元素是引用类型(如 String、Object 等),JVM 会递归地使用类加载器去加载该元素类型。例如,对于 String[] 数组,JVM 会首先确保 java.lang.String 类已经被加载。

3.2 验证

3.3 准备

3.3.1

image

  • 静态变量位于堆区的Class对象中

3.3.2

image

  • boolean实际是int,false是0,true是1

3.3.3

image

  • final修饰的类变量,右边是常量,在编译阶段已确定好值,在准备阶段直接赋值。
  • static final int a = 1

3.4 解析

符号引号:常量池里的编号,就是一组符号用来描述所引用的目标,解析,即将常量池内的符号引用转换为直接引用,使用内存中的地址。

public class Main {
    public static void main(String[] args) {
        // 触发String类的解析
        String str = new String("Hello");
        System.out.println(str);
    }
}
public static void main(java.lang.String[]);
  Code:
     0: new           #2  // class java/lang/String
     3: dup
     4: ldc           #3  // String Hello
     6: invokespecial #4  // Method java/lang/String."<init>":(Ljava/lang/String;)V
     9: astore_1
    10: getstatic     #5  // Field java/lang/System.out:Ljava/io/PrintStream;
    13: aload_1
    14: invokevirtual #6  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    17: return
#2 = Class              #23           // java/lang/String
#23 = Utf8              java/lang/String
  • 符号引用‌:#2 是常量池中对 java/lang/String 类的符号引用
  • 类加载检查‌:JVM 检查 String 类是否已加载
  • 类加载触发‌:如果未加载,触发完整的类加载过程(加载、验证、准备、解析、初始化)
  • 符号引用转换‌:将 #2 转换为指向 String 类元数据的直接指针
  • 内存分配‌:new 指令在堆中为 String 对象分配内存

3.5 初始化

即执行clinit方法,由类变量赋值语句和静态代码块中的语句合并而成,方法中的语句按代码顺序执行。子类会先执行父类的clinit方法。多线程下,一个类的clinit方法会加锁执行,这保证了,虚拟机加载类,只会执行一次clinit方法。

3.5.1 示例

代码:

public class Demo01 {
    public static final int num ;
    public static  String name = "Tom";
    static {
        num=1;
        System.out.println(num);
        System.out.println(name);
    }
}

字节码:
image

3.5.2 以下的几种方式会导致类的初始化:

image

3.5.2.1 初始化示例:

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

输出如下:构造代码块先于构造方法

D
A
C
B
C
B

3.5.3 不会有clinit指令的情况

image

3.5.4 可能会出现非法的前向引用
public class Demo01 {
    static {
        System.out.println(num);// 报错:非法的前向引用
    }
    public static final int num =1;
}
public class Demo01 {
    static {
        num=1;
        System.out.println(num);// 报错:非法的前向引用
    }
    public static final int num ;
}

3.5.5初始化的面试题

3.5.5.1访问父类的变量,不会初始化子类

public class Demo3 {
    public static void main(String[] args) {
        //输出3
        System.out.println(B.a);
    }
}

class A{
    static int a=3;
}

class B extends A{
    static {
     a=4;
    }
}

3.5.5.2下面的代码表示子类变量a覆盖了父类,访问a即表示访问子类,会初始化子类

public class Demo3 {
    public static void main(String[] args) {
        //输出4
        System.out.println(B.a);
    }
}

class A{
    static int a=3;
}

class B extends A{
    static int a=6;
    static {
     a=4;
    }
}

3.5.5.3类的使用

类的主动使用和被动使用,即会不会执行类的初始化

3.5.5.3.1以下是主动使用:

image
image
image

3.5.5.3.1以下是被动使用:

image

3.6 准备阶段对比初始化阶段

public class Demo5 {
    //准备阶段赋值,并且进入常量池
    public static final int a =1;
    public static final String PATTERN="DUAL";
    //初始化阶段赋值,不会进入常量池
    public static final int b =new Random().nextInt();
    public static final String CAR_COLOR=new String("RED");
}
  • 凡是以字面量形式赋值的常量,准备阶段赋值;需要调用方法指令(new、invoke)的都是初始化阶段赋值。

4. 运行时数据区

4.1 总览:

image

image

RunTime说明,一个JVM对应一个RunTime实例
image
image

4.2 PC寄存器

在加载阶段,虚拟机将字节码文件中的指令读取到内存后,会将偏移量转换为内存地址,每条字节码指令都拥有一个内存地址,程序计数器存放下一条指令的地址,执行引擎读取之,执行相应的操作,线程私有。
image
image
image

4.3 虚拟机栈

image
image
虚拟机栈可能出现的异常。方法递归,可能会导致StackOverFlowError
image
设置栈大小:示例,-Xss256K

4.3.1 栈帧:一个一个的方法

image

栈运行的原理:

image

栈运行的原理示意图:

image

  • 方法正常退出或者发生异常,会导致栈帧弹出。

4.3.2 栈帧内部的结构:

image

4.3.2.1 局部变量表

栈帧大小主要由局部变量表影响,表的前面存有形参。实例方法的局部变量表首位存了this。
image
image
image

  • 访问变量q,那么访问的索引为4(不是5)。
局部变量表里的变量是要声明的,比如下图的info变量,删除后,局部变量表就没有了。

删除前:
image
删除后:
image

slot槽位可重复利用

image
注意下图中局部变量表的长度为3,b的index是2,c的index也是2,变量c复用了变量b的位置
image

局部变量需要显示赋值,没有默认值

image

局部变量表涉及到GC

image

局部变量表里的“length”,指作用域范围,pc加length等于code 长度,code长度就是指令长度。

image
image
image

4.3.2.2 操作数栈

image
image

4.3.2.3 动态链接

动态链接,指向运行时常量池的方法引用。当前类的字节码指令引用了其他类的属性或方法时,需要将符号引用转换为对应的内存地址,动态链接保存了符号引用到运行时常量池的内存地址的映射关系。

比如下图,运行时常量池指的是System类的,第9行获取System类的静态变量out,在11行接着调用out的方法println,JVM虚拟机在执行该指令前,需要将符号引用#10、#16的内存地址保存在动态链接中。
image

4.3.2.4 方法返回地址:

在方法正常返回时,返回地址是‌调用该方法的指令的下一条指令的地址‌。

public class Example {
    public static void main(String[] args) {
        int result = add(1, 2);  // 调用 add() 方法,记录下一条指令地址(即 println)
        System.out.println(result);  // 这行指令的地址就是返回地址
    }

    public static int add(int a, int b) {
        int sum = a + b;
        return sum;  // 执行 ireturn 指令,返回地址用于跳转回 main 方法的 println
    }
}

4.4 异常表

4.5 本地方法栈

image

本地方法栈抛异常示例
public class Demo6 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fileOutputStream =new FileOutputStream("E:\\123.txt");
        fileOutputStream.write(2);
    }
}
Exception in thread "main" java.io.FileNotFoundException: E:\123.txt (系统找不到指定的路径。)
	at java.io.FileOutputStream.open0(Native Method)
	at java.io.FileOutputStream.open(FileOutputStream.java:270)
	at java.io.FileOutputStream.<init>(FileOutputStream.java:213)
	at java.io.FileOutputStream.<init>(FileOutputStream.java:101)
	at org.jvm.study.java6.Demo6.main(Demo6.java:9)
  • 电脑里没有E盘

4.6变量的位置

局部变量

  • 在虚拟机栈的栈帧内‌
  • 基本类型(如 int)的变量名和值均存于栈帧的‌局部变量表‌。
  • 引用类型(如 Object)的变量名存于栈,其指向的对象实例存于堆。
  • 生命周期‌:随方法调用开始,方法结束栈帧弹出时销毁。

实例变量

  • 存储位置‌:‌堆内存(Heap)‌
  • 对象实例化时,实例变量随对象分配在堆中,生命周期与对象一致。
  • 若变量为基本类型(如 int),值直接存于堆内;若为引用类型(如 String),变量存储对象在堆中的地址。

类变量

image
image

5 方法调用

原理:
image
image
image

示例:

image
image
image

5.2 方法调用指令

image

5.3 动态绑定

image

image
image

类加载补充:

自己写个String类

包的名不同就可以重写

package com.alibaba.dme.bcp.ump.biz.activities.constants;

public class String {

    public static void main(java.lang.String[] args) {
        System.out.println(String.class.getClassLoader());
        java.lang.String str = "123";
        System.out.println(str.getClass().getClassLoader());
    }
}

类初始化和对象实例化

https://www.cnblogs.com/chanshuyi/p/jvm_serial_07_jvm_class_loader_mechanism.html

posted @ 2025-11-24 20:12  dvdhellohaha  阅读(82)  评论(0)    收藏  举报