jvm八股

1.jvm组成

1.1程序计数器

线程私有的,每个线程一份。用来记录正在执行的字节码指令的行号。

程序计数器 是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。

1.2 虚拟机栈

线程私有,生命周期和线程一样(随着线程的创建而创建,随着线程的结束而死亡)。

除本地方法外,java方法调用都通过虚拟机栈(后文简称栈)实现。

栈由栈帧组成,方法调用时栈帧压入,结束后栈帧弹出

每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法返回地址

局部变量表

存放方法的所有 参数和在方法内部定义的局部变量

1.3 堆

介绍一下堆

堆是线程共享的区域,用来保存所有通过 new 关键字创建的 对象实例数组

*什么东西存放在堆里

Java堆(Heap)中存放的是所有通过 new 关键字创建的 对象实例数组**。


为了让这个概念更具体,我们来详细分解一下"对象实例"和"数组"里面到底是什么:

  1. 对象实例 (Object Instances)

这是堆最主要的存储内容。当你写下 new MyClass() 时,这个 MyClass 的一个实例就会在堆上被创建。这个实例对象内部包含了:

  • 实例变量 (Instance Variables)

    • 基本数据类型 :如果类成员变量是 int, double, boolean 等基本类型,那么这些变量的本身就直接存储在堆上的对象内部。

    • 引用数据类型 :如果类成员变量是另一个对象(比如 String 或另一个自定义类),那么存储在当前对象内部的是一个引用(或称指针),这个引用指向另一个在堆上存储的实际对象。

  • 对象头信息 (Object Header)

    • Mark Word:存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁等。这部分数据在32位和64位虚拟机中大小不同。

    • 类型指针 (Klass Pointer):一个指向方法区中该对象对应类信息的指针。通过这个指针,JVM可以知道这个对象是哪个类的实例。

    • 数组长度 (Array Length)这部分只有在对象是数组的情况下才会有。

  1. 数组 (Arrays)

数组在Java中也被当作对象来处理,所以它们同样存储在堆上。无论是基本数据类型数组(如 new int[10])还是引用类型数组(如 new String[10]),数组对象本身都在堆里。

  • new int[10] : 会在堆里创建一个数组对象。这个对象除了对象头信息,还包含了10个整数的连续空间,存放着具体的整数

  • new String[10] : 同样会在堆里创建一个数组对象。这个对象包含了10个空位,用来存放指向 String 对象的引用 。这些 String 对象本身也都在堆里(可能在也可能不在连续的空间)。

总结一下,堆里到底有什么?

您可以把Java堆想象成一个巨大的仓库,里面放着:

  1. 所有new出来的东西本身(无论是普通对象还是数组对象)。

  2. 对象的所有成员变量(基本类型的值和引用类型的地址)。

  3. 每个对象和数组的"管理标签"(也就是对象头信息)。

与之相对,不会存放在堆里的主要是:

  • 局部变量:存放在Java虚拟机栈(Stack)中。如果局部变量是基本类型,栈里存的就是值;如果是引用类型,栈里存的是指向堆中对象的地址。

  • 类信息、静态变量、常量:存放在方法区(Method Area,在JDK 1.8后由元空间Metaspace实现)。

补充1:基本数据类型

  • 整数类型

    • byte: 8位,用于存储小整数。

    • short: 16位。

    • int: 32位,最常用的整数类型。

    • long: 64位,用于存储大整数。

  • 浮点类型 (用于小数)

    • float: 32位,单精度。

    • double: 64位,双精度,比 float 更精确,是小数的默认类型。

  • 字符类型

    • char: 16位,用于存储单个Unicode字符 (例如 'A', '中')。
  • 布尔类型

    • boolean: 只有两个值:truefalse

补充2:引用类型

除了8种基本类型之外,Java中所有其他的类型都是引用类型。变量中存储的不是对象本身,而是一个指向对象在堆内存 中位置的引用(或称为地址)

主要包括:

  • 类 (Class): 这是最常见的引用类型。

    • 所有你自己创建的类,比如我们例子中的 Car

    • Java自带的库类,比如 String, Object, System, ArrayList 等。

  • 接口 (Interface) : 如 List, Map, Runnable 等。变量可以声明为接口类型,但它必须引用一个实现了该接口的类的对象。

  • 数组 (Array) : 比如 int[], String[], Car[] 等。在Java中,数组也是对象,所以数组变量也是引用类型。

关键特性

  • 变量在内存中(通常是栈)只存放一个地址

  • 真正的对象数据存放在上。

  • 可以通过 new 关键字来创建实例(对象)。

  • 默认值为 null,表示不引用任何对象。

  • 按照惯例,名称都是大写字母开头 (String, Car)。

**补充内容

静态变量、实例变量和局部变量的存放位置

java 复制代码
public class Car {
    // 1. 静态变量 (Static)
    // 存放在:【堆 (Heap)】(伴随 Class 对象)
    // 意义:工厂墙上的记分牌,全厂只有一块
    public static int totalCount = 0;

    // 2. 非静态成员变量 / 实例变量 (Instance Variable)
    // 存放在:【堆 (Heap)】(在 Car 对象内部)
    // 意义:每辆车自己的颜色。车在堆里,颜色就在堆里。
    private String color; 
    private int price;

    // 方法
    public void drive() {
        // 3. 局部变量 (Local Variable)
        // 存放在:【栈 (Stack)】
        // 意义:工人开车时踩油门的力度。方法结束,力度也就没了。
        int speed = 80; 
        
        // 特殊情况:引用类型的局部变量
        // 'myDog' 这个引用(遥控器)在【栈】上
        // 但它指向的 'Dog' 对象在【堆】上
        Dog myDog = new Dog(); 
    }
}

口诀:成员变量(静态+非静态)都在堆,局部变量都在栈

静态变量和非静态变量的区别

java 复制代码
class Car {
    // static 修饰:静态变量(属于类,大家共用一份)
    // 就像那块"计分板"
    static int count = 0; 

    // 没有 static 修饰:非静态变量(属于对象,每辆车各有一份)
    // 就像车的"颜色"
    String color; 

    public Car(String c) {
        this.color = c;   // 设置这辆特定车的颜色
        count++;          // 生产了一辆车,计分板总数 +1
    }
}

public class Main {
    public static void main(String[] args) {
        // 1. 造第一辆车:红色
        Car car1 = new Car("红色");
        
        // 2. 造第二辆车:蓝色
        Car car2 = new Car("蓝色");

        // --- 见证区别 ---

        // 区别 A:非静态变量是独立的
        System.out.println(car1.color); // 输出:红色
        System.out.println(car2.color); // 输出:蓝色
        // (修改 car1 的颜色,不会影响 car2)

        // 区别 B:静态变量是共享的
        // 虽然我是通过 car1 去看 count,但看到的和 car2 是一样的
        System.out.println(car1.count); // 输出:2
        System.out.println(car2.count); // 输出:2
        System.out.println(Car.count);  // 输出:2 (推荐这种写法,直接问类)
    }
}

详细对比表

维度 静态变量 (Static) 非静态变量 (Instance)
归属 属于 类 (Class) 属于 对象 (Object)
内存副本 只有 1 份 (无论创建多少对象) 有 N 份 (创建多少对象就有多少份)
存放位置 堆 (Heap) [JDK7+],跟随 Class 对象 堆 (Heap),在每个实例对象内部
生命周期 很长。类加载时诞生,程序结束才销毁。 较短。对象创建时诞生,对象被回收时销毁。
调用方式 推荐用 类名.变量名 (如 Car.count) 必须用 对象名.变量名 (如 myCar.color)
典型用途 计数器、全局配置、常量 (Math.PI) 用户的名字、车的颜色、银行卡余额

String s = new String("abc"); 到底创建了几个对象?

答案是:通常是 2 个,最少是 1 个。

这行代码其实包含了两步操作:

  1. "abc":处理字面量。

  2. new String(...):处理 new 关键字。

情况 A:通常情况(创建了 2 个对象)

假设字符串常量池里还没有 "abc" 这个字符串。

  1. 第 1 个对象 :JVM 看到字面量 "abc",它会先在 字符串常量池(String Pool) 里创建一个 "abc" 对象。

  2. 第 2 个对象 :JVM 看到 new 关键字,会在 堆(Heap) 中开辟一块新内存,创建一个新的 String 对象。这个对象会拷贝刚才那个 "abc" 的值。

  3. 引用 :最后,把堆里那个新对象的地址,赋值给栈上的变量 s

结论:一个在池里,一个在堆里。共 2 个。

情况 B:特殊情况(创建了 1 个对象)

假设你的代码前面已经有一行 String t = "abc";,这意味着常量池里已经有 "abc" 了。

  1. 第 0 个对象 :JVM 看到字面量 "abc",去池里找,发现有了,就不创建了。

  2. 第 1 个对象 :JVM 看到 new 关键字,依然必须在 堆(Heap) 里创建一个新对象(因为 new 意味着强制新建)。

  3. 引用 :把堆里新对象的地址赋给 s

结论:池里的省了,只创建了堆里的那一个。共 1 个。

相关推荐
小兔薯了2 小时前
11. Linux firewall 防火墙管理
linux·运维·服务器
ink@re3 小时前
Docker环境搭建与容器管理实战:从部署到编排的完整指南
运维·docker·容器
RPA机器人就选八爪鱼4 小时前
RPA财务机器人:驱动财务数字化转型的核心引擎
大数据·运维·人工智能·机器人·rpa
-大头.5 小时前
Redis内存碎片深度解析:从动态整理到核心运维实践
运维·数据库·redis
倦王5 小时前
Linux看ip,改用户名字加权限,在单独用户下设置miniconda
linux·服务器·tcp/ip
qq_2153978975 小时前
docker 管理工具 Portainer安装
运维·服务器·docker
凤凰战士芭比Q6 小时前
Docker安装与常用命令
linux·运维·docker·容器
华哥啊.6 小时前
服务器安装node_exporter监测cpu以及内存相关情况
运维·服务器
yuejich6 小时前
命名规范snake_case
服务器·前端·数据库