07-Java语言核心-JVM原理-JVM对象模型详解

JVM对象模型详解

一、知识概述

在Java虚拟机中,对象的创建、存储和访问是Java程序运行的基础。理解JVM对象模型有助于我们编写更高效的代码,排查内存相关问题,以及进行性能优化。

对象的内存布局

在HotSpot虚拟机中,对象在内存中的存储布局可以分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

复制代码
┌─────────────────────────────────────────────────────────────┐
│                     Java 对象内存布局                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    对象头 (Header)                    │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │  ┌─────────────────────────────────────────────┐    │   │
│  │  │            Mark Word (标记字)                 │    │   │
│  │  │            32位: 4字节 / 64位: 8字节          │    │   │
│  │  │  存储: hashCode、锁状态、GC年龄等             │    │   │
│  │  └─────────────────────────────────────────────┘    │   │
│  │  ┌─────────────────────────────────────────────┐    │   │
│  │  │         Class Pointer (类型指针)             │    │   │
│  │  │            指向方法区的类元数据               │    │   │
│  │  │         开启指针压缩: 4字节,否则: 8字节      │    │   │
│  │  └─────────────────────────────────────────────┘    │   │
│  │  ┌─────────────────────────────────────────────┐    │   │
│  │  │         Array Length (数组长度)              │    │   │
│  │  │            仅数组对象有此字段                 │    │   │
│  │  └─────────────────────────────────────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              实例数据 (Instance Data)                │   │
│  │                                                      │   │
│  │  字段内容(继承的 + 自身的)                         │   │
│  │  - 基本类型: 按大小存储                              │   │
│  │  - 引用类型: 存储 reference                          │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │             对齐填充 (Padding)                       │   │
│  │                                                      │   │
│  │  保证对象大小是8字节的整数倍                         │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

对象大小计算

类型 大小 说明
对象头(普通对象) 12字节(64位开启压缩) Mark Word(8) + Class Pointer(4)
对象头(数组对象) 16字节(64位开启压缩) 额外4字节数组长度
引用 4字节(开启压缩)/ 8字节 对象引用
int 4字节 整型
long 8字节 长整型
对齐填充 0~7字节 凑整到8的倍数

二、知识点详细讲解

2.1 对象头详解

对象头包含两部分信息:Mark Word(标记字)和类型指针。如果是数组,还有一个记录数组长度的部分。

Mark Word结构

Mark Word在32位和64位虚拟机中的长度不同,但存储的信息相似。它在不同的锁状态下存储的内容不同:

复制代码
32位 JVM Mark Word 结构:

┌───────────────────────────────────────────────────────────────┐
│                        Mark Word (32 bits)                     │
├───────────────────────────────────────────────────────────────┤
│                                                                 │
│  无锁状态 (Normal):                                             │
│  ┌─────────────────────────────┬─────┬─────┬─────┐            │
│  │      hashcode:25            │ age:4│ 0:1 │ 01  │            │
│  └─────────────────────────────┴─────┴─────┴─────┘            │
│                                                                 │
│  偏向锁 (Biased):                                               │
│  ┌─────────────────────────────┬─────┬─────┬─────┐            │
│  │    thread:23 | epoch:2      │ age:4│ 1:1 │ 01  │            │
│  └─────────────────────────────┴─────┴─────┴─────┘            │
│                                                                 │
│  轻量级锁 (Lightweight Locked):                                 │
│  ┌─────────────────────────────────────────┬─────┐            │
│  │          ptr_to_lock_record:30           │ 00  │            │
│  └─────────────────────────────────────────┴─────┘            │
│                                                                 │
│  重量级锁 (Heavyweight Locked):                                 │
│  ┌─────────────────────────────────────────┬─────┐            │
│  │          ptr_to_heavyweight_monitor:30  │ 10  │            │
│  └─────────────────────────────────────────┴─────┘            │
│                                                                 │
│  GC标记 (Marked for GC):                                        │
│  ┌─────────────────────────────────────────┬─────┐            │
│  │              (空)                        │ 11  │            │
│  └─────────────────────────────────────────┴─────┘            │
│                                                                 │
└───────────────────────────────────────────────────────────────┘

字段说明:
  hashcode: 对象的哈希码
  age: 对象的GC年龄(4位,最大15)
  thread: 持有偏向锁的线程ID
  epoch: 偏向锁的时间戳
  ptr_to_lock_record: 栈中锁记录的指针
  ptr_to_heavyweight_monitor: 对象监视器的指针
  最后2位: 锁状态标记
使用JOL工具分析对象
java 复制代码
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

/**
 * 使用JOL(Java Object Layout)分析对象布局
 * 依赖: org.openjdk.jol:jol-core:0.16
 */
public class ObjectLayoutDemo {
    
    public static void main(String[] args) {
        // 打印JVM信息
        System.out.println("=== JVM信息 ===");
        System.out.println(VM.current().details());
        System.out.println();
        
        // === 1. 空对象 ===
        System.out.println("=== 空对象布局 ===");
        EmptyObject empty = new EmptyObject();
        System.out.println(ClassLayout.parseInstance(empty).toPrintable());
        
        // === 2. 基本类型字段 ===
        System.out.println("\n=== 基本类型字段 ===");
        PrimitiveFields primitives = new PrimitiveFields();
        System.out.println(ClassLayout.parseInstance(primitives).toPrintable());
        
        // === 3. 引用类型字段 ===
        System.out.println("\n=== 引用类型字段 ===");
        ReferenceFields references = new ReferenceFields();
        System.out.println(ClassLayout.parseInstance(references).toPrintable());
        
        // === 4. 数组对象 ===
        System.out.println("\n=== 数组对象布局 ===");
        int[] intArray = new int[5];
        System.out.println(ClassLayout.parseInstance(intArray).toPrintable());
        
        Object[] objArray = new Object[5];
        System.out.println(ClassLayout.parseInstance(objArray).toPrintable());
        
        // === 5. 继承关系 ===
        System.out.println("\n=== 继承关系 ===");
        ChildClass child = new ChildClass();
        System.out.println(ClassLayout.parseInstance(child).toPrintable());
        
        // === 6. 对象大小计算 ===
        System.out.println("\n=== 对象大小 ===");
        System.out.println("EmptyObject 大小: " + 
            VM.current().sizeOf(empty) + " 字节");
        System.out.println("PrimitiveFields 大小: " + 
            VM.current().sizeOf(primitives) + " 字节");
        System.out.println("int[5] 大小: " + 
            VM.current().sizeOf(intArray) + " 字节");
    }
    
    // 空对象
    static class EmptyObject {
        // 只有对象头
    }
    
    // 基本类型字段
    static class PrimitiveFields {
        byte b;      // 1字节
        short s;     // 2字节
        int i;       // 4字节
        long l;      // 8字节
        float f;     // 4字节
        double d;    // 8字节
        char c;      // 2字节
        boolean z;   // 1字节
    }
    
    // 引用类型字段
    static class ReferenceFields {
        Object obj;      // 引用,4字节(开启压缩)
        String str;      // 引用,4字节
        int[] array;     // 引用,4字节
    }
    
    // 父类
    static class ParentClass {
        int parentField;
    }
    
    // 子类
    static class ChildClass extends ParentClass {
        int childField;
    }
}

/*
运行结果示例(64位JVM,开启指针压缩):

=== JVM信息 ===
# Running 64-bit HotSpot VM.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

=== 空对象布局 ===
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   4        (object header)           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
  4   4        (object header)           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
  8   4        (object header)           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
 12   4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

=== 基本类型字段 ===
PrimitiveFields object internals:
OFF  SZ     TYPE DESCRIPTION               VALUE
  0   8          (object header)           01 00 00 00 00 00 00 00 (1)
  8   4          (object header)           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
 12   1   byte   PrimitiveFields.b         0
 13   1   boolean PrimitiveFields.z        false
 14   2   char   PrimitiveFields.c         
 16   2   short  PrimitiveFields.s         0
 20   4   float  PrimitiveFields.f         0.0
 24   8   double PrimitiveFields.d         0.0
 32   8   long   PrimitiveFields.l         0
 40   4   int    PrimitiveFields.i         0
 44   4        (loss due to the next object alignment)
Instance size: 48 bytes
*/

2.2 指针压缩

在64位JVM中,为了减少内存占用,可以使用指针压缩(Compressed Oops)。默认开启。

指针压缩原理
java 复制代码
/**
 * 指针压缩演示
 */
public class CompressedOopsDemo {
    
    public static void main(String[] args) {
        // 检查指针压缩是否开启
        System.out.println("=== 指针压缩状态 ===");
        
        String compressOops = getPropertyValue("UseCompressedOops");
        String compressClassPointers = getPropertyValue("UseCompressedClassPointers");
        
        System.out.println("UseCompressedOops: " + compressOops);
        System.out.println("UseCompressedClassPointers: " + compressClassPointers);
        
        // 计算内存差异
        System.out.println("\n=== 内存占用对比 ===");
        
        // 创建大量对象测试
        int count = 1_000_000;
        
        // 每个对象的内存差异
        long referenceSizeWith = 4;   // 开启压缩,引用4字节
        long referenceSizeWithout = 8; // 关闭压缩,引用8字节
        
        // 每个对象头的差异
        long headerSizeWith = 12;      // 开启压缩,对象头12字节
        long headerSizeWithout = 16;   // 关闭压缩,对象头16字节
        
        System.out.println("创建 " + count + " 个对象:");
        System.out.println();
        
        // 空对象
        long emptyWith = 16;    // 12头 + 4填充
        long emptyWithout = 16; // 16头,无需填充
        System.out.println("空对象:");
        System.out.println("  开启压缩: " + (emptyWith * count / 1024 / 1024) + " MB");
        System.out.println("  关闭压缩: " + (emptyWithout * count / 1024 / 1024) + " MB");
        
        // 包含引用的对象
        ReferenceObject[] refs = new ReferenceObject[count];
        long refObjWith = 16;   // 12头 + 4引用
        long refObjWithout = 24; // 16头 + 8引用
        System.out.println("\n包含1个引用的对象:");
        System.out.println("  开启压缩: " + (refObjWith * count / 1024 / 1024) + " MB");
        System.out.println("  关闭压缩: " + (refObjWithout * count / 1024 / 1024) + " MB");
        System.out.println("  节省: " + ((refObjWithout - refObjWith) * count / 1024 / 1024) + " MB");
        
        // 数组
        Object[] array = new Object[count];
        // 数组头 + 引用数组
        long arrayWith = 16 + 4L * count;   // 16头(含长度) + 4*count引用
        long arrayWithout = 24 + 8L * count; // 24头 + 8*count引用
        System.out.println("\n长度" + count + "的对象数组:");
        System.out.println("  开启压缩: " + (arrayWith / 1024 / 1024) + " MB");
        System.out.println("  关闭压缩: " + (arrayWithout / 1024 / 1024) + " MB");
        System.out.println("  节省: " + ((arrayWithout - arrayWith) / 1024 / 1024) + " MB");
        
        // 指针压缩的地址范围
        System.out.println("\n=== 指针压缩限制 ===");
        System.out.println("压缩指针最大寻址: 4GB (2^32)");
        System.out.println("配合对象对齐8字节: 最大32GB堆内存");
        System.out.println("超过32GB需要关闭压缩或使用其他对齐");
        System.out.println();
        System.out.println("相关参数:");
        System.out.println("  -XX:+UseCompressedOops     开启普通对象指针压缩");
        System.out.println("  -XX:+UseCompressedClassPointers  开启类指针压缩");
        System.out.println("  -XX:ObjectAlignmentInBytes=8  对象对齐字节数");
    }
    
    private static String getPropertyValue(String property) {
        try {
            return sun.misc.VM.getSavedProperty(property);
        } catch (Exception e) {
            return "unknown";
        }
    }
    
    static class ReferenceObject {
        Object ref;
    }
}

/*
指针压缩工作原理:

地址编码:
  压缩前:64位真实地址
  压缩后:32位偏移量 × 8(对象对齐)

示例:
  真实地址: 0x0000000123456780
  压缩后:   0x02468ACF (偏移量/8)

限制:
  32位 × 8字节对齐 = 32GB最大堆内存
  超过此限制需要关闭压缩
*/

2.3 对象创建过程

java 复制代码
/**
 * 对象创建过程详解
 */
public class ObjectCreationDemo {
    
    public static void main(String[] args) {
        /*
        对象创建步骤:
        
        1. 类加载检查
           - 检查类是否已加载
           - 未加载则先加载类
        
        2. 分配内存
           - 指针碰撞(Bump the Pointer):堆规整时使用
           - 空闲列表(Free List):堆不规整时使用
           - 并发安全:CAS或TLAB
        
        3. 初始化零值
           - 将分配的内存初始化为零值
           - 保证对象字段有默认值
        
        4. 设置对象头
           - 设置类元数据指针
           - 设置对象哈希码、GC年龄等
        
        5. 执行<init>方法
           - 调用构造方法
           - 初始化成员变量
           - 执行构造代码块
        */
        
        // 示例:跟踪对象创建
        System.out.println("=== 对象创建过程 ===\n");
        
        // 步骤1:类加载检查
        System.out.println("1. 类加载检查");
        System.out.println("   检查Person类是否已加载");
        
        // 步骤2:分配内存
        System.out.println("\n2. 分配内存");
        System.out.println("   在堆中分配对象所需空间");
        
        // 步骤3:初始化零值
        System.out.println("\n3. 初始化零值");
        Person p = new Person();  // 此时所有字段为零值
        System.out.println("   name = null, age = 0");
        
        // 步骤4:设置对象头
        System.out.println("\n4. 设置对象头");
        System.out.println("   设置类指针、哈希码种子等");
        
        // 步骤5:执行构造方法
        System.out.println("\n5. 执行<init>方法");
        p = new Person("张三", 25);
        System.out.println("   调用构造方法初始化");
        System.out.println("   结果: " + p);
        
        // 对象创建的内存分配方式
        System.out.println("\n=== 内存分配方式 ===\n");
        memoryAllocationDemo();
        
        // TLAB演示
        System.out.println("\n=== TLAB (Thread Local Allocation Buffer) ===\n");
        tlabDemo();
    }
    
    /**
     * 内存分配方式演示
     */
    private static void memoryAllocationDemo() {
        /*
        指针碰撞(Bump the Pointer):
        ┌─────────────────────────────────────────┐
        │ 已分配区域 │        空闲区域          │
        │           │                           │
        │  obj1 obj2│←─ ptr ─→                 │
        └─────────────────────────────────────────┘
        
        - 适用:堆内存规整(使用标记-整理、复制算法)
        - 方式:指针向后移动对象大小距离
        - 优点:效率高
        - 使用:Serial、ParNew等收集器
        
        空闲列表(Free List):
        ┌─────────────────────────────────────────┐
        │ obj1 │空闲│ obj2 │  空闲  │ obj3 │空闲 │
        └─────────────────────────────────────────┘
                ↑           ↑              ↑
              记录在空闲列表中
        
        - 适用:堆内存不规整(使用标记-清除算法)
        - 方式:从空闲列表找合适大小的空闲块
        - 缺点:效率较低,可能产生碎片
        - 使用:CMS收集器
        */
        
        System.out.println("指针碰撞: 适用于规整堆(Serial, ParNew)");
        System.out.println("空闲列表: 适用于不规整堆(CMS)");
    }
    
    /**
     * TLAB演示
     */
    private static void tlabDemo() {
        /*
        TLAB (Thread Local Allocation Buffer):
        每个线程在堆中预先分配一小块私有区域
        
        ┌─────────────────────────────────────────────────────────┐
│                       Java Heap                          │
        │                                                          │
        │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐       │
│  │Thread 1 │ │Thread 2 │ │Thread 3 │ │Thread 4 │       │
        │  │  TLAB   │ │  TLAB   │ │  TLAB   │ │  TLAB   │       │
        │  │         │ │         │ │         │ │         │       │
        │  └─────────┘ └─────────┘ └─────────┘ └─────────┘       │
        │                                                          │
        │                   共享区域(需要同步)                    │
        │                                                          │
        └─────────────────────────────────────────────────────────┘
        
        优点:
        - 避免多线程竞争
        - 在TLAB中分配无需同步
        - 使用指针碰撞方式,高效
        
        参数:
        -XX:+UseTLAB           开启TLAB(默认开启)
        -XX:TLABSize=256k      设置TLAB大小
        -XX:TLABRefillWasteFraction=64  TLAB refill阈值
        */
        
        System.out.println("TLAB: 每个线程的私有分配缓冲区");
        System.out.println("优点: 避免并发竞争,提高分配效率");
        System.out.println("参数: -XX:+UseTLAB (默认开启)");
    }
    
    static class Person {
        private String name;
        private int age;
        
        // 构造代码块
        {
            System.out.println("   构造代码块执行");
        }
        
        public Person() {
            System.out.println("   无参构造执行");
        }
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
            System.out.println("   有参构造执行: name=" + name + ", age=" + age);
        }
        
        @Override
        public String toString() {
            return "Person{name='" + name + "', age=" + age + "}";
        }
    }
}

2.4 对象的访问定位

java 复制代码
/**
 * 对象访问定位方式
 */
public class ObjectAccessDemo {
    
    public static void main(String[] args) {
        /*
        创建对象后,Java程序需要通过栈上的reference数据来操作堆上的具体对象。
        
        访问方式取决于JVM实现,主要有两种:
        
        1. 句柄访问
        2. 直接指针访问
        */
        
        // === 句柄访问 ===
        System.out.println("=== 句柄访问 ===\n");
        handleAccessDemo();
        
        // === 直接指针访问 ===
        System.out.println("\n=== 直接指针访问 ===\n");
        directPointerDemo();
        
        // === HotSpot的实现 ===
        System.out.println("\n=== HotSpot实现 ===\n");
        System.out.println("HotSpot使用直接指针访问");
        System.out.println("优点: 访问速度快,减少一次间接访问");
        System.out.println("缺点: 对象移动时需要修改reference");
    }
    
    /**
     * 句柄访问演示
     */
    private static void handleAccessDemo() {
        /*
        句柄访问:
        
        ┌──────────────┐
        │ Java栈       │
        │ reference ───┼────┐
        └──────────────┘    │
                            ↓
        ┌─────────────────────────────────────────┐
        │              句柄池                      │
        │  ┌─────────────────────────────────┐    │
        │  │        句柄                      │    │
        │  │  ┌───────────┬───────────┐      │    │
        │  │  │对象实例数据│对象类型数据│      │    │
        │  │  │  指针      │   指针    │      │    │
        │  │  └─────┬─────┴─────┬─────┘      │    │
        │  └────────│───────────│────────────┘    │
        └───────────│───────────│─────────────────┘
                    ↓           ↓
        ┌───────────────┐ ┌─────────────────┐
        │   堆(实例)   │ │  方法区(类型)  │
        │   Object      │ │   Class数据     │
        └───────────────┘ └─────────────────┘
        
        优点:
        - reference存储稳定句柄地址
        - 对象移动时只需修改句柄,不动reference
        
        缺点:
        - 需要两次指针访问
        - 需要维护句柄池空间
        */
        
        System.out.println("句柄访问流程:");
        System.out.println("  reference -> 句柄 -> 对象实例");
        System.out.println("  reference -> 句柄 -> 对象类型");
        System.out.println();
        System.out.println("优点: 对象移动只需改句柄");
        System.out.println("缺点: 两次指针访问,效率稍低");
    }
    
    /**
     * 直接指针访问演示
     */
    private static void directPointerDemo() {
        /*
        直接指针访问(HotSpot使用):
        
        ┌──────────────┐
        │ Java栈       │
        │ reference ───┼──────────────────────┐
        └──────────────┘                      │
                                              ↓
        ┌─────────────────────────────────────────────┐
        │               堆(对象实例)                 │
        │  ┌─────────────────────────────────────┐    │
        │  │           对象头                     │    │
        │  │  其中包含指向方法区的类型指针         │────┼──→ 方法区(类型数据)
        │  ├─────────────────────────────────────┤    │
        │  │           实例数据                   │    │
        │  │          ...                        │    │
        │  └─────────────────────────────────────┘    │
        └─────────────────────────────────────────────┘
        
        优点:
        - 只需一次指针访问
        - 访问速度快
        
        缺点:
        - 对象移动需要修改reference
        - GC时需要更新所有引用
        */
        
        System.out.println("直接指针访问流程:");
        System.out.println("  reference -> 对象实例 -> 类型数据(通过对象头)");
        System.out.println();
        System.out.println("优点: 一次访问,效率高");
        System.out.println("缺点: 对象移动需要更新reference");
    }
    
    /**
     * 实际代码访问对象
     */
    public void accessObject() {
        // 创建对象
        User user = new User("张三", 25);  // user是栈上的reference
        
        // 访问对象字段
        String name = user.getName();  // 通过reference访问堆中的对象
        int age = user.getAge();
        
        // 调用对象方法
        user.sayHello();
        
        // 对象作为参数传递
        processUser(user);  // 传递的是reference的副本
    }
    
    private void processUser(User user) {
        // 仍然指向堆中同一个对象
        user.setAge(26);
    }
    
    static class User {
        private String name;
        private int age;
        
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        public String getName() { return name; }
        public int getAge() { return age; }
        public void setAge(int age) { this.age = age; }
        
        public void sayHello() {
            System.out.println("Hello, I'm " + name);
        }
    }
}

2.5 对象的分配策略

java 复制代码
/**
 * 对象分配策略详解
 */
public class ObjectAllocationDemo {
    
    public static void main(String[] args) {
        System.out.println("=== 对象分配策略 ===\n");
        
        // === 1. 对象优先在Eden分配 ===
        edenAllocation();
        
        // === 2. 大对象直接进入老年代 ===
        largeObjectAllocation();
        
        // === 3. 长期存活对象进入老年代 ===
        tenuringDemo();
        
        // === 4. 动态对象年龄判定 ===
        dynamicAgeDemo();
        
        // === 5. 空间分配担保 ===
        spaceGuaranteeDemo();
    }
    
    /**
     * Eden区分配
     */
    private static void edenAllocation() {
        System.out.println("【1. Eden区分配】");
        System.out.println("大多数对象首先在Eden区分配");
        System.out.println();
        
        /*
        新生代结构:
        ┌─────────────────────────────────────────────┐
        │                 新生代                       │
        │  ┌────────────┬─────────┬─────────┐        │
        │  │    Eden    │    S0   │    S1   │        │
        │  │   (80%)    │  (10%)  │  (10%)  │        │
        │  └────────────┴─────────┴─────────┘        │
        └─────────────────────────────────────────────┘
        
        Eden满了触发Minor GC
        */
        
        // 模拟Eden分配
        byte[] allocation1 = new byte[1024 * 1024];  // 1MB
        byte[] allocation2 = new byte[1024 * 1024];  // 1MB
        
        System.out.println("allocation1, allocation2 在Eden分配");
        System.out.println();
    }
    
    /**
     * 大对象直接进老年代
     */
    private static void largeObjectAllocation() {
        System.out.println("【2. 大对象直接进老年代】");
        System.out.println("超过阈值的大对象直接在老年代分配");
        System.out.println();
        
        /*
        参数:
        -XX:PretenureSizeThreshold=3145728  (3MB)
        
        注意:
        - 只对Serial和ParNew收集器有效
        - G1有Humongous区域处理大对象
        */
        
        // 假设阈值是3MB
        byte[] largeObject = new byte[4 * 1024 * 1024];  // 4MB
        // 大于阈值,直接在老年代分配
        
        System.out.println("大对象: 4MB > 阈值(3MB) -> 老年代分配");
        System.out.println("参数: -XX:PretenureSizeThreshold");
        System.out.println("注意: 避免大对象,增加Eden区GC压力");
        System.out.println();
    }
    
    /**
     * 长期存活对象进入老年代
     */
    private static void tenuringDemo() {
        System.out.println("【3. 长期存活对象进入老年代】");
        System.out.println("对象经过多次GC仍存活,年龄达到阈值后晋升");
        System.out.println();
        
        /*
        年龄计数:
        - 对象在Eden出生,经历一次Minor GC存活,年龄+1
        - 年龄达到阈值,晋升老年代
        
        参数:
        -XX:MaxTenuringThreshold=15  (默认15)
        -XX:InitialTenuringThreshold=7  (初始阈值,并行GC)
        
        对象头中年龄占4位,最大值15
        */
        
        // 模拟年龄增长
        Object longLived = new Object();
        
        System.out.println("对象经历GC次数 -> 年龄");
        System.out.println("年龄达到 MaxTenuringThreshold(默认15) -> 晋升老年代");
        System.out.println();
        System.out.println("年龄记录在对象头Mark Word中:");
        System.out.println("┌─────────────────────────────────┐");
        System.out.println("│ ... │ age:4位 │ ... │");
        System.out.println("└─────────────────────────────────┘");
        System.out.println("  4位最大值15,所以年龄阈值最大15");
        System.out.println();
    }
    
    /**
     * 动态对象年龄判定
     */
    private static void dynamicAgeDemo() {
        System.out.println("【4. 动态对象年龄判定】");
        System.out.println("Survivor区相同年龄对象大小超过一半,直接晋升");
        System.out.println();
        
        /*
        动态晋升规则:
        如果Survivor空间中相同年龄所有对象大小的总和大于
        Survivor空间的一半,年龄大于或等于该年龄的对象
        直接进入老年代。
        
        示例:
        Survivor大小: 10MB
        
        年龄1: 2MB
        年龄2: 3MB
        年龄3: 4MB  (累计 2+3+4=9MB > 10MB/2)
        
        -> 年龄3及以上的对象晋升老年代
        */
        
        System.out.println("示例:");
        System.out.println("Survivor大小: 10MB");
        System.out.println("年龄1对象: 2MB");
        System.out.println("年龄2对象: 3MB");
        System.out.println("年龄3对象: 4MB (累计9MB > 5MB)");
        System.out.println();
        System.out.println("结果: 年龄≥3的对象晋升老年代");
        System.out.println();
    }
    
    /**
     * 空间分配担保
     */
    private static void spaceGuaranteeDemo() {
        System.out.println("【5. 空间分配担保】");
        System.out.println("Minor GC前检查老年代是否有足够空间");
        System.out.println();
        
        /*
        分配担保流程:
        
        1. Minor GC前检查
           老年代最大可用的连续空间 > 新生代所有对象总大小?
           
           YES -> Minor GC安全
           NO  -> 检查 HandlePromotionFailure 设置
           
        2. 允许担保失败 (HandlePromotionFailure=true)
           老年代最大可用连续空间 > 历次晋升到老年代对象的平均大小?
           
           YES -> 尝试Minor GC(有风险)
           NO  -> Full GC
           
        3. 不允许担保失败
           -> Full GC
        
        参数:
        -XX:-HandlePromotionFailure  (JDK 6之后默认允许)
        */
        
        System.out.println("检查流程:");
        System.out.println("  1. 老年代可用空间 > 新生代总大小?");
        System.out.println("     YES -> Minor GC");
        System.out.println("     NO  -> 继续");
        System.out.println();
        System.out.println("  2. 老年代可用空间 > 历史晋升平均值?");
        System.out.println("     YES -> 尝试Minor GC");
        System.out.println("     NO  -> Full GC");
        System.out.println();
    }
}

三、可运行Java代码示例

完整示例:对象大小计算工具

java 复制代码
import java.lang.reflect.*;
import java.util.*;

/**
 * 对象大小计算工具
 * 不使用JOL的情况下估算对象大小
 */
public class ObjectSizeCalculator {
    
    // 对象头大小(64位JVM,开启指针压缩)
    private static final int OBJECT_HEADER_SIZE = 12;
    
    // 数组头额外大小
    private static final int ARRAY_HEADER_EXTRA = 4;
    
    // 引用大小(开启压缩)
    private static final int REFERENCE_SIZE = 4;
    
    // 对齐
    private static final int ALIGNMENT = 8;
    
    // 基本类型大小
    private static final Map<Class<?>, Integer> PRIMITIVE_SIZES = new HashMap<>();
    
    static {
        PRIMITIVE_SIZES.put(byte.class, 1);
        PRIMITIVE_SIZES.put(boolean.class, 1);
        PRIMITIVE_SIZES.put(char.class, 2);
        PRIMITIVE_SIZES.put(short.class, 2);
        PRIMITIVE_SIZES.put(int.class, 4);
        PRIMITIVE_SIZES.put(float.class, 4);
        PRIMITIVE_SIZES.put(long.class, 8);
        PRIMITIVE_SIZES.put(double.class, 8);
    }
    
    /**
     * 计算对象大小
     */
    public static long sizeOf(Object obj) {
        if (obj == null) {
            return 0;
        }
        
        Class<?> clazz = obj.getClass();
        
        // 数组
        if (clazz.isArray()) {
            return sizeOfArray(obj);
        }
        
        // 普通对象
        return sizeOfObject(clazz);
    }
    
    /**
     * 计算普通对象大小
     */
    private static long sizeOfObject(Class<?> clazz) {
        long size = OBJECT_HEADER_SIZE;
        
        // 收集所有字段(包括父类)
        List<Field> fields = getAllFields(clazz);
        
        for (Field field : fields) {
            Class<?> fieldType = field.getType();
            
            if (fieldType.isPrimitive()) {
                size += PRIMITIVE_SIZES.get(fieldType);
            } else {
                size += REFERENCE_SIZE;
            }
        }
        
        // 对齐
        return align(size);
    }
    
    /**
     * 计算数组大小
     */
    private static long sizeOfArray(Object array) {
        Class<?> componentType = array.getClass().getComponentType();
        int length = java.lang.reflect.Array.getLength(array);
        
        long size = OBJECT_HEADER_SIZE + ARRAY_HEADER_EXTRA;
        
        if (componentType.isPrimitive()) {
            size += (long) PRIMITIVE_SIZES.get(componentType) * length;
        } else {
            size += (long) REFERENCE_SIZE * length;
        }
        
        return align(size);
    }
    
    /**
     * 获取所有字段
     */
    private static List<Field> getAllFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();
        
        while (clazz != null && clazz != Object.class) {
            for (Field field : clazz.getDeclaredFields()) {
                if (!Modifier.isStatic(field.getModifiers())) {
                    fields.add(field);
                }
            }
            clazz = clazz.getSuperclass();
        }
        
        return fields;
    }
    
    /**
     * 对齐到8字节边界
     */
    private static long align(long size) {
        return (size + ALIGNMENT - 1) / ALIGNMENT * ALIGNMENT;
    }
    
    /**
     * 深度计算对象大小(包括引用的对象)
     */
    public static long deepSizeOf(Object obj) {
        return deepSizeOf(obj, new IdentityHashMap<>());
    }
    
    private static long deepSizeOf(Object obj, Map<Object, Object> visited) {
        if (obj == null || visited.containsKey(obj)) {
            return 0;
        }
        
        visited.put(obj, null);
        
        Class<?> clazz = obj.getClass();
        long size = sizeOf(obj);
        
        // 数组
        if (clazz.isArray()) {
            if (!clazz.getComponentType().isPrimitive()) {
                int length = java.lang.reflect.Array.getLength(obj);
                for (int i = 0; i < length; i++) {
                    Object element = java.lang.reflect.Array.get(obj, i);
                    size += deepSizeOf(element, visited);
                }
            }
            return size;
        }
        
        // 对象字段
        for (Field field : getAllFields(clazz)) {
            if (!field.getType().isPrimitive()) {
                try {
                    field.setAccessible(true);
                    Object fieldValue = field.get(obj);
                    size += deepSizeOf(fieldValue, visited);
                } catch (IllegalAccessException e) {
                    // 忽略
                }
            }
        }
        
        return size;
    }
    
    // === 测试 ===
    public static void main(String[] args) {
        System.out.println("=== 对象大小计算工具 ===\n");
        
        // 1. 空对象
        Object empty = new Object();
        System.out.println("空对象: " + sizeOf(empty) + " 字节");
        
        // 2. 包含基本类型的对象
        PrimitiveObject primitive = new PrimitiveObject();
        System.out.println("基本类型对象: " + sizeOf(primitive) + " 字节");
        
        // 3. 包含引用的对象
        ReferenceObject reference = new ReferenceObject();
        System.out.println("引用对象(浅): " + sizeOf(reference) + " 字节");
        System.out.println("引用对象(深): " + deepSizeOf(reference) + " 字节");
        
        // 4. 数组
        int[] intArray = new int[100];
        System.out.println("int[100]: " + sizeOf(intArray) + " 字节");
        
        Object[] objArray = new Object[100];
        System.out.println("Object[100](浅): " + sizeOf(objArray) + " 字节");
        
        // 5. 复杂对象
        Person person = new Person("张三", 25, new Address("北京", "朝阳"));
        System.out.println("复杂对象(浅): " + sizeOf(person) + " 字节");
        System.out.println("复杂对象(深): " + deepSizeOf(person) + " 字节");
        
        // 6. 集合
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            list.add("String" + i);
        }
        System.out.println("ArrayList(100个字符串)(深): " + deepSizeOf(list) + " 字节");
    }
    
    // 测试类
    static class PrimitiveObject {
        byte b;
        short s;
        int i;
        long l;
        float f;
        double d;
        char c;
        boolean z;
    }
    
    static class ReferenceObject {
        String str = "Hello, World!";
        Object obj = new Object();
    }
    
    static class Person {
        String name;
        int age;
        Address address;
        
        Person(String name, int age, Address address) {
            this.name = name;
            this.age = age;
            this.address = address;
        }
    }
    
    static class Address {
        String city;
        String district;
        
        Address(String city, String district) {
            this.city = city;
            this.district = district;
        }
    }
}

完整示例:逃逸分析与标量替换

java 复制代码
/**
 * 逃逸分析与标量替换示例
 */
public class EscapeAnalysisDemo {
    
    /**
     * 逃逸分析:判断对象是否可能逃逸方法
     */
    public static void main(String[] args) {
        System.out.println("=== 逃逸分析示例 ===\n");
        
        // 开启逃逸分析(JDK 7+默认开启)
        // -XX:+DoEscapeAnalysis
        
        // 1. 不逃逸
        noEscape();
        
        // 2. 方法逃逸
        methodEscape();
        
        // 3. 线程逃逸
        threadEscape();
        
        // 4. 标量替换
        scalarReplacement();
        
        // 5. 锁消除
        lockElimination();
    }
    
    /**
     * 不逃逸:对象仅在方法内使用
     * 可以进行标量替换,在栈上分配
     */
    private static void noEscape() {
        // 对象不逃逸,可以被优化
        Point p = new Point(1, 2);
        int result = p.getX() + p.getY();
        System.out.println("不逃逸: " + result);
        
        /*
        JIT编译后可能优化为:
        int x = 1;
        int y = 2;
        int result = x + y;
        // 对象根本不会在堆上创建
        */
    }
    
    /**
     * 方法逃逸:对象被返回或传递给其他方法
     */
    private static Point methodEscape() {
        Point p = new Point(3, 4);
        return p;  // 逃逸:返回给调用者
    }
    
    /**
     * 线程逃逸:对象被其他线程访问
     */
    private static void threadEscape() {
        Point p = new Point(5, 6);
        
        // 逃逸:被其他线程访问
        new Thread(() -> {
            System.out.println(p.getX());
        }).start();
    }
    
    /**
     * 标量替换:将对象拆解为标量(基本类型)
     */
    private static void scalarReplacement() {
        System.out.println("\n=== 标量替换 ===");
        
        long start = System.currentTimeMillis();
        
        for (int i = 0; i < 10000000; i++) {
            // 这个对象不会逃逸
            Point p = new Point(i, i + 1);
            int x = p.getX();
            int y = p.getY();
        }
        
        long end = System.currentTimeMillis();
        System.out.println("耗时: " + (end - start) + "ms");
        System.out.println("开启逃逸分析和标量替换后,对象不会在堆上创建");
        System.out.println();
        System.out.println("相关参数:");
        System.out.println("  -XX:+DoEscapeAnalysis     开启逃逸分析(默认)");
        System.out.println("  -XX:+EliminateAllocations  开启标量替换(默认)");
        System.out.println("  -XX:+EliminateLocks        开启锁消除(默认)");
        
        /*
        验证:
        java -XX:-DoEscapeAnalysis EscapeAnalysisDemo  // 关闭,较慢
        java -XX:+DoEscapeAnalysis EscapeAnalysisDemo  // 开启,较快
        */
    }
    
    /**
     * 锁消除:对象不逃逸时,锁操作可以消除
     */
    private static void lockElimination() {
        System.out.println("\n=== 锁消除 ===");
        
        long start = System.currentTimeMillis();
        
        for (int i = 0; i < 10000000; i++) {
            StringBuffer sb = new StringBuffer();
            sb.append("a").append(i);  // StringBuffer的方法都有synchronized
            // 但sb不逃逸,锁可以消除
        }
        
        long end = System.currentTimeMillis();
        System.out.println("StringBuffer耗时: " + (end - start) + "ms");
        System.out.println("StringBuffer的synchronized锁被消除");
        System.out.println("参数: -XX:+EliminateLocks (默认开启)");
    }
    
    // 测试类
    static class Point {
        private int x;
        private int y;
        
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
        
        public int getX() { return x; }
        public int getY() { return y; }
    }
}

/*
逃逸分析优化效果:

1. 栈上分配
   - 对象不逃逸时,可以在栈上分配
   - 减少GC压力
   
2. 标量替换
   - 对象拆解为标量(基本类型和引用)
   - 完全消除对象创建
   
3. 锁消除
   - 对象不逃逸时,synchronized锁可以消除
   - 提高并发性能

相关JVM参数:
  -XX:+DoEscapeAnalysis        开启逃逸分析(JDK7+默认)
  -XX:+EliminateAllocations    开启标量替换(默认)
  -XX:+EliminateLocks          开启锁消除(默认)
*/

四、实战应用场景

场景1:内存优化

java 复制代码
import java.util.*;

/**
 * 基于对象模型的内存优化
 */
public class MemoryOptimizationDemo {
    
    public static void main(String[] args) {
        System.out.println("=== 内存优化技巧 ===\n");
        
        // === 1. 避免不必要的对象头开销 ===
        System.out.println("【1. 减少小对象数量】");
        
        // 不好的设计:每个属性都是对象
        class BadDesign {
            List<Integer> values = new ArrayList<>();
        }
        
        // 好的设计:使用数组
        class GoodDesign {
            int[] values;
        }
        
        System.out.println("List<Integer> 每个Integer都有对象头(16字节)");
        System.out.println("int[] 只有数组头+数据,节省大量内存");
        System.out.println();
        
        // === 2. 使用基本类型替代包装类 ===
        System.out.println("【2. 基本类型vs包装类】");
        
        // 包装类
        Integer[] integers = new Integer[1000];
        // 每个: 16字节头 + 4字节int = 20字节 -> 对齐24字节
        // 总计: 24 * 1000 = 24000字节 (还不算引用数组)
        
        // 基本类型
        int[] ints = new int[1000];
        // 只有: 16字节头 + 4 * 1000 = 4016字节
        // 节省约85%
        
        System.out.println("Integer[1000]: 约24KB");
        System.out.println("int[1000]: 约4KB");
        System.out.println("节省约85%内存");
        System.out.println();
        
        // === 3. 字段顺序优化 ===
        System.out.println("【3. 字段顺序优化】");
        
        // 不好的顺序
        class BadOrder {
            byte b1;    // 1字节
            long l1;    // 8字节 (需要8字节对齐,前面填充7字节)
            byte b2;    // 1字节
            long l2;    // 8字节 (前面填充7字节)
            byte b3;    // 1字节
            // 总计: 1 + 7(填充) + 8 + 1 + 7(填充) + 8 + 1 = 33字节 -> 40字节
        }
        
        // 好的顺序
        class GoodOrder {
            long l1;    // 8字节
            long l2;    // 8字节
            byte b1;    // 1字节
            byte b2;    // 1字节
            byte b3;    // 1字节
            // 总计: 8 + 8 + 1 + 1 + 1 = 19字节 -> 24字节
        }
        
        System.out.println("BadOrder: 40字节(大量填充)");
        System.out.println("GoodOrder: 24字节(字段按大小排序)");
        System.out.println("建议: 长字段在前,短字段在后");
        System.out.println();
        
        // === 4. 使用更紧凑的数据结构 ===
        System.out.println("【4. 紧凑数据结构】");
        
        // 存储布尔值
        boolean[] boolArray = new boolean[8];  // 8个字节
        byte bitSet = 0;  // 1个字节,用位存储8个布尔值
        
        System.out.println("存储8个布尔值:");
        System.out.println("boolean[8]: 16字节头 + 8字节 = 24字节");
        System.out.println("位运算byte: 1字节");
        System.out.println("节省约96%");
        System.out.println();
        
        // === 5. 对象池 ===
        System.out.println("【5. 对象池复用】");
        
        // 频繁创建销毁的对象使用对象池
        ObjectPool<Point> pointPool = new ObjectPool<>(() -> new Point());
        
        Point p = pointPool.borrow();
        p.set(1, 2);
        // 使用后归还
        pointPool.returnObject(p);
        
        System.out.println("对象池避免频繁创建销毁对象");
        System.out.println("适用: 创建开销大、频繁使用的对象");
        System.out.println();
        
        // === 6. 使用原始类型集合 ===
        System.out.println("【6. 原始类型集合】");
        
        // 标准集合
        // List<Integer> - 每个元素是Integer对象
        
        // 第三方原始类型集合(如Eclipse Collections, Trove)
        // IntArrayList - 内部使用int[]
        
        System.out.println("使用Eclipse Collections等库的原始类型集合");
        System.out.println("避免包装类开销");
    }
    
    static class Point {
        int x, y;
        void set(int x, int y) { this.x = x; this.y = y; }
    }
    
    static class ObjectPool<T> {
        private final Queue<T> pool = new LinkedList<>();
        private final java.util.function.Supplier<T> factory;
        
        public ObjectPool(java.util.function.Supplier<T> factory) {
            this.factory = factory;
        }
        
        public T borrow() {
            T obj = pool.poll();
            return obj != null ? obj : factory.get();
        }
        
        public void returnObject(T obj) {
            pool.offer(obj);
        }
    }
}

场景2:对象布局调优

java 复制代码
import org.openjdk.jol.info.ClassLayout;
import java.util.*;

/**
 * 对象布局调优示例
 */
public class ObjectLayoutOptimization {
    
    public static void main(String[] args) {
        System.out.println("=== 对象布局调优 ===\n");
        
        // === 1. 分析当前布局 ===
        System.out.println("【1. 分析对象布局】");
        
        // 使用JOL分析
        // System.out.println(ClassLayout.parseClass(MyClass.class).toPrintable());
        
        // === 2. 字段重排序 ===
        System.out.println("\n【2. 字段重排序】");
        
        // JVM会自动进行字段重排序优化
        // 但最好还是按规范写
        
        System.out.println("优化前(可能有填充):");
        System.out.println("  byte, long, byte, long, byte");
        
        System.out.println("\n优化后(减少填充):");
        System.out.println("  long, long, byte, byte, byte");
        
        // === 3. 使用@Contended避免伪共享 ===
        System.out.println("\n【3. 避免伪共享】");
        
        // 多线程场景下,不同线程访问同一缓存行的不同变量
        // 会导致缓存行失效(伪共享)
        
        // 使用@Contended注解让JVM添加填充
        // @Contended
        // volatile long value;
        
        System.out.println("伪共享: 不同线程访问同一缓存行的不同变量");
        System.out.println("解决: @Contended注解或手动填充");
        System.out.println("参数: -XX:-RestrictContended");
        
        // === 4. 对齐控制 ===
        System.out.println("\n【4. 对齐控制】");
        
        System.out.println("默认对齐: 8字节");
        System.out.println("参数: -XX:ObjectAlignmentInBytes");
        System.out.println();
        System.out.println("增大对齐可以支持更大堆(指针压缩时):");
        System.out.println("  8字节对齐 -> 最大32GB堆");
        System.out.println("  16字节对齐 -> 最大64GB堆");
    }
    
    /**
     * 手动填充避免伪共享
     */
    static class PaddedLong {
        volatile long value;
        
        // 手动填充(确保value独占一个缓存行)
        long p1, p2, p3, p4, p5, p6, p7;
    }
    
    /**
     * 使用@Contended(需要JVM支持)
     */
    // @jdk.internal.vm.annotation.Contended
    static class ContendedLong {
        volatile long value;
    }
}

五、总结与最佳实践

核心要点回顾

概念 内容
对象头 Mark Word + 类型指针 + 数组长度(可选)
实例数据 字段内容,按大小排序减少填充
对齐填充 保证对象大小是8字节的倍数
指针压缩 64位JVM优化,减少内存占用
逃逸分析 判断对象是否逃逸方法
标量替换 对象拆解为标量,消除对象创建

最佳实践

  1. 内存优化

    • 使用基本类型替代包装类
    • 字段按大小排序(长字段在前)
    • 减少小对象数量
    • 使用对象池复用对象
  2. 避免伪共享

    java 复制代码
    // 多线程共享变量添加填充
    @Contended
    volatile long counter;
  3. 利用逃逸分析

    • 让对象不逃逸方法
    • JIT会自动优化栈上分配
  4. 合理设置指针压缩

    bash 复制代码
    # 堆内存 <= 32GB,开启指针压缩
    -XX:+UseCompressedOops
    
    # 堆内存 > 32GB,关闭或增大对齐
    -XX:ObjectAlignmentInBytes=16

对象大小速查表

对象类型 大小(开启压缩) 大小(关闭压缩)
空对象 16字节 16字节
包含1个int 16字节 16字节
包含1个long 16字节 24字节
包含1个引用 16字节 24字节
Object0 16字节 24字节
int0 16字节 16字节
Integer 16字节 16字节
Long 24字节 24字节

相关JVM参数

bash 复制代码
# 指针压缩
-XX:+UseCompressedOops           # 开启普通指针压缩(默认)
-XX:+UseCompressedClassPointers  # 开启类指针压缩(默认)

# 逃逸分析
-XX:+DoEscapeAnalysis            # 开启逃逸分析(默认)
-XX:+EliminateAllocations        # 开启标量替换(默认)
-XX:+EliminateLocks              # 开启锁消除(默认)

# 对齐
-XX:ObjectAlignmentInBytes=8     # 对象对齐字节数(默认8)

# TLAB
-XX:+UseTLAB                     # 开启TLAB(默认)
-XX:TLABSize=256k                # TLAB大小

# 对象年龄
-XX:MaxTenuringThreshold=15      # 晋升年龄阈值

扩展阅读

  • 《深入理解Java虚拟机》:周志明著,对象内存布局章节
  • JOL工具:OpenJDK项目,分析对象布局
  • 逃逸分析论文:Choi et al., "Escape Analysis for Java"
  • HotSpot源码:oops模块,对象模型实现
相关推荐
未若君雅裁3 分钟前
算法复杂度与数据结构:Java 集合篇的第一块基石
java·数据结构·算法
致Great11 分钟前
Claude Code 上线 Dynamic Workflows:一句话调度 1000 个子智能体并行干活
java·linux·服务器
一个做软件开发的牛马13 分钟前
Java 常用类:String不可变、新时间API与包装类陷阱
java·后端
yurenpai(27届找实习中)25 分钟前
redis_点评(25.附件店铺—把数据库里的店铺按【类型分组】,批量导入Redis 的 GEO 地理位置结构)
java·redis·缓存
云烟成雨TD33 分钟前
Spring AI Alibaba 1.x 系列【66】Graph 长期记忆
java·人工智能·spring
Javatutouhouduan1 小时前
Java面试大厂真题汇总!
java·java面试·java面试题·后端开发·java编程·java架构师·java八股文
maomao大哥闯天下1 小时前
K8s对象deployment、job、service应用详解
java·容器·kubernetes
闪电悠米1 小时前
黑马点评-优惠券秒杀-05_local_lock_cluster_problem
java·spring boot·redis·缓存
IronMurphy1 小时前
SSM拷打第二讲!!!
java·spring·mybatis
小江的记录本1 小时前
【JVM虚拟机】类加载机制:类加载全流程:加载→验证→准备→解析→初始化(附《思维导图》+《面试高频考点清单》)
java·jvm·spring boot·算法·安全·spring·面试