java数据类型详解篇

发布于:2025-06-26 ⋅ 阅读:(20) ⋅ 点赞:(0)

1、8种基本数据类型

数据类型 分类 字节数 内存位数 是否最高位为符号位
(0正数1负数)
取值范围(数值形式) 取值说明
byte 整数类型 1 8 -128 ~ 127 -2^7 ~ 2^7 - 1 (幂形式)
short 整数类型 2 16 -32,768 ~ 32,767 -2^15 ~ 2^15 - 1 (幂形式)
int 整数类型 4 32 -2,147,483,648 ~ 2,147,483,647 -2^31 ~ 2^31 - 1 (幂形式)
long 整数类型 8 64 -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 -2^63 ~ 2^63 -1 (幂形式)
float 浮点类型(单精度) 4 32 正数:1.4e-45 ~ 3.402e38
负数:-3.4028235e38 ~ -1.4e-45
6~7位有效十进制小数位
double 浮点类型(双精度) 8 64 正数:4.9e-324 ~ 1.797e308
负数:-1.797e308 ~ 4.9e-324
15位十进制小数位
char Unicode字符类型 2 16 0 ~ 65,535 0 ~ 2^16 -1
与 Unicode 编码的直接对应,如下
char c = 65;
// 输出的不是65而是A字符 System.out.println©;
boolean 布尔类型 无固定 无固定 无固定 无固定,实际占用取决于 JVM 实现和使用场景

根据JVM的内存模型,基本数据类型存储位置取决于声明位置和使用方式:

  • 方法内部声明,则存储在栈里
  • 在实例类中声明,则存储在堆里

2、引用数据类型

1、自定义类或java api提供的类

存储对象的引用(内存地址),指向实际堆存储的位置。

Date now = new Date();       // 日期类
Person p = new Person();     // 自定义类实例
class Person {
    String name;
    int age;
}

字符串String 特别说明:

String有缓存机制,主要通过 字符串常量池(String Pool) 实现,相同内容的字符串只存储一份,后续重复使用直接引用池中的对象。

字面量赋值(自动入池)

String s1 = "Fly";       // 第一次创建,存入常量池
String s2 = "Fly";       // 直接复用常量池中的对象
System.out.println(s1 == s2); // true(地址相同)

显式调用 intern()(手动入池)

String s3 = new String("Fly"); // 在堆中创建新对象
String s4 = s3.intern();       // 将s3内容加入常量池(若池中已有则返回引用)
System.out.println(s1 == s4);  // true(s4指向常量池对象)

new String() 不触发自动缓存

String s5 = new String("Fly"); // 强制在堆中创建新对象
String s6 = new String("Fly"); // 另一个新对象
System.out.println(s5 == s6);  // false(地址不同)
System.out.println(s1 == s5);  // false(常量池 vs 堆新对象)

注意:运行时拼接(不触发缓存)

// 示例1:编译期优化(字面量拼接)
String a = "Fly" + "Fish";  // 编译后自动合并为 "FlyFish",复用常量池对象

// 示例2:运行时拼接(不触发缓存)// 循环内拼接字符串用 StringBuilder,避免生成大量中间对象
String b = "Fly";
String c = b + "Fish";      // 运行时在堆中生成新对象
String d = "FlyFish";
System.out.println(a == d);  // true(a、d在常量池)
System.out.println(c == d);  // false(c在堆,d在常量池)

使用缓存减少对象创建开销,加速字符串比较(==equals() 快),缓存机制下 == 有时有效,但非字面量字符串比较必须用 equals()

在java8之前内部使用 char[](字符数组)存储数据,java9及之后为节省内存,改为 byte[] + 编码标志(Latin-1 或 UTF-16),但逻辑上仍等价于字符序列。

2、接口类

存储对象的引用(内存地址),指向实际堆存储的位置。

interface Drawable {
    void draw();
}

class Circle implements Drawable {
    public void draw() {
        System.out.println("Drawing circle");
    }
}

Drawable d = new Circle();  // 接口引用指向实现类

3、数组类型

数组如果是引用对象存储对象的引用(内存地址),指向实际堆存储的位置。

数组如果是基本数据类型,则按基本数据类型存储在一致。

int[] numbers = {1, 2, 3};             // 基本类型数组
String[] names = new String[5];         // 引用类型数组
int[][] matrix = {{1,2}, {3,4}};        // 多维数组

4、枚举类型

引用对象存储对象的引用(内存地址),指向实际堆存储的位置。

enum Color {
    RED, GREEN, BLUE
}
Color c = Color.RED;  // 枚举引用

5、注解类型

引用对象存储对象的引用(内存地址),指向实际堆存储的位置。

@Retention(RetentionPolicy.RUNTIME)
@interface Author {
    String name();
    int version() default 1;
}

@Author(name = "John")
class MyClass {...}

6、集合框架

List<String> list = new ArrayList<>();  // 有序列表
Set<Integer> set = new HashSet<>();     // 唯一值集合
Map<String, Integer> map = new HashMap<>(); // 键值对映射

对象存储对象的引用(内存地址),指向实际堆存储的位置。

7、8中基本数据类型对应的包装类

基本类型 包装类 自动装箱/拆箱 值缓存 比较陷阱(建议始终用equals比较包装对象)
byte Byte 类似 -128 ~ 127 Integer x = 100, y = 100;
System.out.println(x == y); // true(缓存内)

Integer m = 200, n = 200;
System.out.println(m == n); // false(缓存外)
short Short 类似 -128 ~ 127 类似Integer
int Integer Integer num = 42; // 自动装箱 (int → Integer)
int value = num; // 自动拆箱 (Integer → int)
-128 ~ 127 类似Integer
long Long 类似 -128 ~ 127 类似Integer
float Float 自动装箱都创建新对象。 无缓存
double Double 自动装箱都创建新对象。 无缓存
char Character 类似 ** 0 ~ 127**(ASCII字符范围) 类似Integer
boolean Boolean 仅有两个静态实例Boolean.TRUEBoolean.FALSE,所有自动装箱均复用它们。

3、四大引用类型

GC回收时机
永不回收
内存不足时回收
下次GC时回收
回收后通知
强引用
对象
软引用
弱引用
虚引用
一、强引用
说明

强引用是 Java 中最常见、最默认的引用类型。只要一个对象被至少一个强引用指向,垃圾收集器 (GC) 就绝对不会回收它。只有当所有指向该对象的强引用都断开(被设置为 null 或超出作用域)后,该对象才会变得可回收

使用场景举例
    public class StrongReferenceExample {
        public static void main(String[] args) {
            // 1. 创建一个新的 Person 对象 "Alice"
            //    alice 是一个强引用,指向堆内存中创建的 Person("Alice") 对象
            Person alice = new Person("Alice"); // 强引用建立!

            System.out.println("Alice created: " + alice.getName()); // 输出: Alice created: Alice

            // 2. 让 alice 引用指向一个 *新* 的 Person 对象 "Bob"
            //    现在,第一个 Person("Alice") 对象失去了它唯一的强引用 (alice 不再指向它)
            alice = new Person("Bob"); // 强引用指向了新对象,"Alice" 的强引用断开!

            System.out.println("Now Alice refers to: " + alice.getName()); // 输出: Now Alice refers to: Bob

            // 3. 显式触发垃圾收集 (注意:这只是建议,GC 不保证立即执行)
            for (int i = 0; i < 3; i++) {
                System.gc();
            }

            // 4. 给 GC 一点时间运行 (实际应用中通常不需要这样),GC回收后日志即可看到:!!! GC is collecting Person: Alice !!!
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("Program finished.");
        }
    }

    class Person {
        private String name;

        public Person(String name) {
            this.name = name;
            System.out.println("Person constructor: " + name + " created in memory.");
        }

        public String getName() {
            return name;
        }

        // 重点:覆写 finalize 方法 (不推荐用于生产,仅用于演示GC行为)
        @Override
        protected void finalize() throws Throwable {
            System.out.println("!!! GC is collecting Person: " + name + " !!!");
            super.finalize();
        }
    }
二、软引用
说明

通过SoftReference类创建,只有当内存不足时,才会被回收,所以比弱引用的生命周期更长点,适合实现内存敏感的缓存,如图片缓存等。这些缓存对象在内存充足时可以提升性能,但在内存紧张时会被自动回收,为更重要的对象腾出空间,避免 OOM

JVM 不会一次性回收所有软引用对象。回收过程是有策略、渐进式的,具体行为取决于 JVM 的实现和垃圾回收算法。

  • 按需回收:只回收足够多的软引用对象来缓解当前内存压力,而非一次性全部回收。
  • 策略性回收:JVM 可能根据内部策略(如最近最少使用 LRU)选择性地回收部分软引用对象。如HotSpot 虚拟机使用类似 LRU(Least Recently Used)的策略,在每个软引用对象关联一个“时间戳”,记录最近一次被访问(通过 get() 方法)的时间。内存不足时,优先回收最久未被使用的软引用对象。回收会持续进行,直到释放的内存满足需求或没有更多可回收的软引用。不同 JVM 版本或供应商(如 OpenJDK、Oracle JDK)的实现策略可能不同。
  • JVM的垃圾回收算法:软引用对象可能分布在新生代(Young Gen)或老年代(Old Gen)。触发 Full GC(回收整个堆)时,才会全面扫描并回收老年代中的软引用。如果 Minor GC(回收新生代)后内存仍然不足,可能直接触发 Full GC 来回收老年代的软引用。
使用场景举例

适合实现内存敏感的缓存,如图片缓存等。这些缓存对象在内存充足时可以提升性能,但在内存紧张时会被自动回收,为更重要的对象腾出空间,避免 OOM

/**
 * 软引用例子
 */
public class SoftReferenceExample {

    // 核心缓存结构:文件路径 -> 文件内容的软引用
    private final Map<String, SoftReference<String>> cache = new HashMap<>();
    // 引用队列,用于跟踪哪些软引用已被GC回收(内容已被清除)
    private final ReferenceQueue<String> refQueue = new ReferenceQueue<>();

    // 从缓存获取文件内容(如果存在且未被回收),否则从磁盘读取并缓存
    public String getFileContent(String filePath) {
        // 1. 清理已被GC回收的缓存条目
        evictCollectedEntries();

        // 2. 尝试从缓存中获取软引用
        SoftReference<String> softRef = cache.get(filePath);

        // 3. 如果软引用存在,尝试获取其引用的实际内容
        String content = null;
        if (softRef != null) {
            content = softRef.get(); // get() 方法获取被引用的对象,如果已被GC则返回null
        }

        // 4. 如果从软引用成功获取到内容(content != null),直接返回缓存内容
        if (content != null) {
            System.out.println("Retrieved from cache: " + filePath);
            return content;
        }

        // 5. 缓存未命中(软引用不存在,或软引用存在但其内容已被GC回收)
        System.out.println("Cache miss (or collected). Reading from disk: " + filePath);
        // 模拟从磁盘读取文件内容 (实际应用中替换为真实IO操作)
        content = readFileFromDisk(filePath);

        // 6. 将新读取的内容用软引用包装,并与文件路径关联放入缓存
        //    同时注册引用队列,以便后续知道该引用何时被回收
        softRef = new SoftReference<>(content, refQueue);
        cache.put(filePath, softRef);

        return content;
    }

    // 清理那些已经被垃圾回收器回收了内容的软引用条目
    private void evictCollectedEntries() {
        SoftReference<? extends String> clearedRef;
        // 从引用队列中取出所有已被GC回收的软引用
        while ((clearedRef = (SoftReference<? extends String>) refQueue.poll()) != null) {
            // 遍历缓存,找到这个被回收的软引用对应的条目并移除
            // (注意:这里简单遍历,实际高效实现可能需要反向映射或其他结构)
            SoftReference<? extends String> finalClearedRef = clearedRef;
            cache.entrySet().removeIf(entry -> entry.getValue() == finalClearedRef);
            System.out.println("Evicted collected reference from cache.");
        }
    }

    // 模拟从磁盘读取文件(简单返回一个模拟的大字符串)
    private String readFileFromDisk(String filePath) {
        // 模拟读取大文件:创建一个较大的字符串
        StringBuilder sb = new StringBuilder();
        sb.append("Content of file: ").append(filePath).append("\n");
        for (int i = 0; i < 10000; i++) { // 增加字符串大小以模拟大文件
            sb.append("This is line ").append(i).append(" in the file.\n");
        }
        return sb.toString();
    }

    // 测试主方法
    public static void main(String[] args) {
        SoftReferenceExample fileCache = new SoftReferenceExample();

        // 第一次读取文件A - 会从磁盘读取并缓存
        String contentA1 = fileCache.getFileContent("fileA.txt");
        System.out.println("Content A length: " + contentA1.length());

        // 第二次读取文件A - 应该从缓存命中
        String contentA2 = fileCache.getFileContent("fileA.txt");
        System.out.println("Content A length (again): " + contentA2.length());

        // 模拟内存压力:尝试缓存多个大文件
        System.out.println("\nSimulating memory pressure by caching many files...");
        for (int i = 0; i < 100; i++) {
            fileCache.getFileContent("large_file_" + i + ".dat");
        }
        System.out.println("Cached many large files.");

        // 尝试再次读取文件A - 可能命中,也可能被回收了需要重新读取
        System.out.println("\nTrying to access fileA.txt again...");
        String contentA3 = fileCache.getFileContent("fileA.txt");
        System.out.println("Content A length (after pressure): " + (contentA3 != null ? contentA3.length() : "null"));
    }
}
三、弱引用
说明

通过WeakReference类创建的,下次GC时无论内存是否充足,只要对象仅被弱引用指向(没有强引用或软引用),无论内存是否充足,该对象都会被回收。

使用场景举例

ThreadLocal内部实现的key即ThreadLocal本身就是用的弱引用,具体查看文章 java线程变量ThreadLocal用法篇

通常用在临时缓存的场景使用,WeakHashMap举例如下

static class Config {
        private final String value;
        private final byte[] payload; // 增加内存占用

        public Config(String value) {
            this.value = value;
            this.payload = new byte[12048]; // 每个配置对象占用2KB
        }
    }
    /**
     * 实例本身不可回收,生命周期与类加载器绑定,当键失去所有外部强引用时,GC时即可回收
     */
    private static final WeakHashMap<Object, Config> cache = new WeakHashMap<>();
    private static final WeakHashMap<Object, Config> cache2 = new WeakHashMap<>();

    // 内存监控工具
    private static final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
    // 打印内存使用情况
    private static void printMemoryStats(String label) {
        // 获取堆内存使用情况
        MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();
        long usedHeap = heapUsage.getUsed() / 1024; // KB

        // 获取非堆内存使用情况
        MemoryUsage nonHeapUsage = memoryMXBean.getNonHeapMemoryUsage();
        long usedNonHeap = nonHeapUsage.getUsed() / 1024; // KB

        System.out.printf("[%s] 堆内存: %d KB | 非堆内存: %d KB | cache大小: %d | cache2大小: %d%n",
                label, usedHeap, usedNonHeap, cache.size(), cache2.size());
    }

    /**
     * 这种方式字符串在常量池中,所以key存在强引用,所以key不会被回收
     */
    static void testStringLiteral() {
        System.out.println("\n===== 开始测试字符串字面量 =====");
        printMemoryStats("测试前");
        String key1 = "key1";
        String key2 = "key2";
        String key3 = "key3";
        String key4 = "key4";
        String key5 = "key5";
        String key6 = "key6";
        String key7 = "key7";
        String key8 = "key8";
        String key9 = "key9";
        String key10 = "key10";
        cache.putIfAbsent(key1, new Config("value_key1")); // 1KB
        cache.putIfAbsent(key2, new Config("value_key2")); // 1KB
        printMemoryStats("添加中 " + 2);
        cache.putIfAbsent(key3, new Config("value_key3")); // 1KB
        cache.putIfAbsent(key4, new Config("value_key4")); // 1KB
        printMemoryStats("添加中 " + 4);

        cache.putIfAbsent(key5, new Config("value_key5")); // 1KB
        cache.putIfAbsent(key6, new Config("value_key6")); // 1KB
        printMemoryStats("添加中 " + 6);

        cache.putIfAbsent(key7, new Config("value_key7")); // 1KB
        cache.putIfAbsent(key8, new Config("value_key8")); // 1KB
        printMemoryStats("添加中 " + 8);

        cache.putIfAbsent(key9, new Config("value_key9")); // 1KB
        cache.putIfAbsent(key10, new Config("value_key10")); // 1KB
        printMemoryStats("添加中 " + 10);

        printMemoryStats("添加后");
        System.out.println("字面量测试完成");
    }

    /**
     * 使用 new String()在堆中创建的新对象,不存在强引用所以key可以回收,value值对象将失去来自 Map 的强引用
     * 没有引用的value对象即可被GC回收。
     * 正常业务使用具体对象
     */
    static void testNewString() {
        System.out.println("\n===== 开始测试 new String() =====");
        printMemoryStats("测试前");
        for (int i = 0; i < 10; i++) {
            // 使用 new String() - 可回收
            cache2.putIfAbsent(new String("key" + i), new Config("value"+i)); // 1KB
            // 每1000次打印一次内存
            if (i % 2 == 0) {
                printMemoryStats("添加中 " + i);
            }
        }
        printMemoryStats("添加后");
        System.out.println("new String() 测试完成");
    }

    /**
     * 执行GC并等待清理完成
     */
    private static void forceGCAndWait() {
        System.out.println("\n触发GC...");
        printMemoryStats("GC前");

        // 多次触发GC确保执行
        for (int i = 0; i < 3; i++) {
            System.gc();
            try {
                Thread.sleep(200); // 给GC时间执行
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        printMemoryStats("GC后");
    }

    /**
     *
     * [程序启动] 堆内存: 4096 KB | 非堆内存: 4038 KB | cache大小: 0 | cache2大小: 0
     *
     * ===== 开始测试字符串字面量 =====
     * [测试前] 堆内存: 4096 KB | 非堆内存: 4229 KB | cache大小: 0 | cache2大小: 0
     * [添加中 2] 堆内存: 4096 KB | 非堆内存: 4234 KB | cache大小: 2 | cache2大小: 0
     * [添加中 4] 堆内存: 4096 KB | 非堆内存: 4238 KB | cache大小: 4 | cache2大小: 0
     * [添加中 6] 堆内存: 4096 KB | 非堆内存: 4239 KB | cache大小: 6 | cache2大小: 0
     * [添加中 8] 堆内存: 4096 KB | 非堆内存: 4244 KB | cache大小: 8 | cache2大小: 0
     * [添加中 10] 堆内存: 4096 KB | 非堆内存: 4246 KB | cache大小: 10 | cache2大小: 0
     * [添加后] 堆内存: 4096 KB | 非堆内存: 4246 KB | cache大小: 10 | cache2大小: 0
     * 字面量测试完成
     *
     * 触发GC...
     * [GC前] 堆内存: 4096 KB | 非堆内存: 4247 KB | cache大小: 10 | cache2大小: 0
     * [GC后] 堆内存: 4076 KB | 非堆内存: 4250 KB | cache大小: 10 | cache2大小: 0
     *
     * ===== 开始测试 new String() =====
     * [测试前] 堆内存: 4076 KB | 非堆内存: 4254 KB | cache大小: 10 | cache2大小: 0
     * [添加中 0] 堆内存: 4076 KB | 非堆内存: 4402 KB | cache大小: 10 | cache2大小: 1
     * [添加中 2] 堆内存: 4076 KB | 非堆内存: 4408 KB | cache大小: 10 | cache2大小: 3
     * [添加中 4] 堆内存: 4076 KB | 非堆内存: 4410 KB | cache大小: 10 | cache2大小: 5
     * [添加中 6] 堆内存: 4076 KB | 非堆内存: 4411 KB | cache大小: 10 | cache2大小: 7
     * [添加中 8] 堆内存: 4076 KB | 非堆内存: 4413 KB | cache大小: 10 | cache2大小: 9
     * [添加后] 堆内存: 4076 KB | 非堆内存: 4416 KB | cache大小: 10 | cache2大小: 10
     * new String() 测试完成
     *
     * 触发GC...
     * [GC前] 堆内存: 4076 KB | 非堆内存: 4420 KB | cache大小: 10 | cache2大小: 10
     * [GC后] 堆内存: 4956 KB | 非堆内存: 4899 KB | cache大小: 10 | cache2大小: 0
     *
     * ===== 最终内存报告 =====
     * [最终状态] 堆内存: 4956 KB | 非堆内存: 4900 KB | cache大小: 10 | cache2大小: 0
     * cache 大小: 10
     * cache2 大小: 0
     * @param args
     */
    public static void main(String[] args) {
        printMemoryStats("程序启动");
        testStringLiteral();  // 内存持续增长
        forceGCAndWait();
        testNewString();      // 内存稳定
        forceGCAndWait();
        // 最终报告
        System.out.println("\n===== 最终内存报告 =====");
        printMemoryStats("最终状态");
        System.out.println("cache 大小: " + cache.size());
        System.out.println("cache2 大小: " + cache2.size());

    }
四、虚引用
说明

虚引用是4个引用中弱最的引用类型,它通过PhantomReference类创建的,并且必须需要配合ReferenceQueue队列使用

,通过虚引用的get()获取对象总是返回null,在对象被GC回收时ReferenceQueue队列可以收到回收通知,如下

    public static void main(String[] args) throws InterruptedException {       
		// 1. 创建引用队列(用于接收被回收对象的虚引用)
        ReferenceQueue<MyResource> queue = new ReferenceQueue<>();

        // 2. 创建资源对象
        MyResource myResource = new MyResource("重要资源");

        // 3. 创建虚引用,关联资源对象和引用队列
        PhantomReference<MyResource> phantomRef =
                new PhantomReference<>(myResource, queue);

        System.out.println("初始状态:");
        System.out.println("  资源对象: " + myResource);
        System.out.println("  虚引用是否指向对象: " + (phantomRef.get() != null)); // 虚引用总是返回null
        System.out.println("  引用队列是否有数据: " + (queue.poll() != null));
        System.out.println();
		// 4. 断开强引用,使资源对象可被回收
        myResource = null;

        // 5. 请求垃圾回收(注意:这只是建议,不保证立即执行),多次请求GC(增加成功率)
        for (int i = 0; i < 3; i++) {
            System.gc();
            System.runFinalization();
        }

        // 6. 给GC一点时间执行
        Thread.sleep(500);

        System.out.println("GC后状态:");
        System.out.println("  虚引用是否指向对象: " + (phantomRef.get() != null));
		// 7. 检查引用队列(虚引用会在对象回收后被加入队列),阻塞方式等待虚引用入队(最多等待2秒)
        PhantomReference<?> refFromQueue = (PhantomReference<?>) queue.remove(2000);

        if (refFromQueue != null) {
            System.out.println("✅ 检测到资源已被回收,虚引用进入队列");
            System.out.println("  队列中的引用: " + refFromQueue);
            System.out.println("  是否与原始虚引用相同: " + (refFromQueue == phantomRef));

            // 这里可以执行资源清理操作
            // resource 普通堆内存对象,JVM 自动回收这个对象占用的堆内存,虚引用入队只是通知你"对象已被回收,不需要手动释放堆内存
		   //            System.out.println("执行清理操作:释放资源关联的内存...");
        } else {
            System.out.println("❌ 超过等待时间仍未检测到资源回收");
            System.out.println("可能原因:");
            System.out.println("1. GC尚未执行完成");
            System.out.println("2. 对象仍有强引用");
            System.out.println("3. JVM忽略了System.gc()");
        }
    }
    static class MyResource {
        private final String name;

        public MyResource(String name) {
            this.name = name;
            System.out.println("创建资源: " + name);
        }

        @Override
        protected void finalize() throws Throwable {
            System.out.println("🔥 垃圾回收器正在回收资源: " + name);
            super.finalize();
        }
    }
使用场景举例

在精确控制资源释放的场景经常使用,在日常业务开发中较少直接使用,但在基础框架、中间件、高性能库中(如Netty、JDK NIO等)有不可替代的作用

在虚引用机制中,虚引用本身不负责释放内存,它的核心作用是提供对象被回收的通知时机,让开发者有机会执行自定义的清理逻辑。是否需要手动释放内存取决于资源的类型,如下:

内存类型 释放责任方 虚引用中的作用
堆内存 JVM自动回收 无需处理
堆外内存 开发者手动释放 在清理回调中释放
其他资源 开发者手动释放 在清理回调中关闭/释放

例子:

/**
 * 虚引用例子
 */
public class PhantomReferenceExample2 {
    // 资源清理接口
    @FunctionalInterface
    public interface ResourceCleaner {
        void clean();
    }

    // 自定义虚引用(携带清理逻辑)
    private static class MemoryReference extends PhantomReference<ByteBuffer> {
        private final ResourceCleaner cleaner;
        private final Object jdkCleaner; // 改为 Object 类型
        private final int size;

        public MemoryReference(ByteBuffer referent,
                               ReferenceQueue<? super ByteBuffer> queue,
                               ResourceCleaner cleaner,
                               Object jdkCleaner, // 改为 Object
                               int size) {
            super(referent, queue);
            this.cleaner = cleaner;
            this.jdkCleaner = jdkCleaner;
            this.size = size;
        }

        public void clean() {
            cleaner.clean(); // 执行自定义清理逻辑
            cleanMemory();   // 实际释放堆外内存
            System.out.printf("✅ 释放堆外内存: 大小=%,d bytes\n", size);
        }

        private void cleanMemory() {
            try {
                // 通过反射调用 clean() 方法
                Method cleanMethod = jdkCleaner.getClass().getMethod("clean");
                cleanMethod.invoke(jdkCleaner);
            } catch (Exception e) {
                throw new RuntimeException("无法调用clean方法", e);
            }
        }
    }

    // 内存管理器
    public static class MemoryManager implements AutoCloseable {
        private final ReferenceQueue<ByteBuffer> queue = new ReferenceQueue<>();
        private final ConcurrentHashMap<MemoryReference, Boolean> refs = new ConcurrentHashMap<>();
        private final ExecutorService cleanerThread = Executors.newSingleThreadExecutor();
        private volatile boolean running = true;

        public MemoryManager() {
            // 启动后台清理线程
            cleanerThread.submit(() -> {
                while (running || !refs.isEmpty()) {
                    try {
                        MemoryReference ref = (MemoryReference) queue.remove(500);
                        if (ref != null) {
                            ref.clean();
                            refs.remove(ref);
                        }
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            });
        }

        /**
         * 分配堆外内存并返回ByteBuffer
         */
        public ByteBuffer allocateDirect(int size) {
            // 分配堆外内存
            ByteBuffer buffer = ByteBuffer.allocateDirect(size);

            // 获取JDK内置的Cleaner
            Object jdkCleaner = getCleaner(buffer);

            // 创建自定义清理逻辑
            ResourceCleaner customCleaner = () -> {
                System.out.println("执行自定义清理操作...");
            };

            // 创建虚引用并注册
            MemoryReference ref = new MemoryReference(
                    buffer, queue, customCleaner, jdkCleaner, size
            );
            refs.put(ref, Boolean.TRUE);

            return buffer;
        }

        @Override
        public void close() throws Exception {
            running = false;
            cleanerThread.shutdown();
            if (!cleanerThread.awaitTermination(5, TimeUnit.SECONDS)) {
                cleanerThread.shutdownNow();
            }
            System.out.println("内存管理器已关闭");
        }

        /**
         * 获取ByteBuffer关联的Cleaner
         */
        private Object getCleaner(ByteBuffer buffer) {
            try {
                // 获取ByteBuffer的cleaner()方法
                Method cleanerMethod = buffer.getClass().getMethod("cleaner");
                cleanerMethod.setAccessible(true);

                // 调用cleaner()方法获取Cleaner实例
                return cleanerMethod.invoke(buffer);
            } catch (Exception e) {
                throw new RuntimeException("无法获取Cleaner", e);
            }
        }
    }

    /**
     *
     * VM options 中,需要添加两个参数:
     * --add-opens java.base/java.nio=ALL-UNNAMED
     * --add-opens java.base/jdk.internal.ref=ALL-UNNAMED
     *
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        try (MemoryManager manager = new MemoryManager()) {
            // 分配堆外内存,但是实例元信息还是在堆中,内存块在堆外
            ByteBuffer buffer = manager.allocateDirect(1024 * 1024); // 1MB

            // 使用缓冲区
            buffer.putInt(0, 42);
            System.out.println("缓冲区值: " + buffer.getInt(0));

            // 释放缓冲区,取消强引用,使之实例元信息可以被GC回收
            // 包含指向堆外内存的指针和其他元数据(注意此处不是元空间的元数据,两者是不同概念)
            // 只有这个Java对象被回收后,虚引用才会被加入队列,
            // 虚引用是桥梁:当堆中的 ByteBuffer 对象被回收时,虚引用会触发回调来释放堆外内存
            buffer = null;

            // 模拟GC,实际生产环境中,不会使用System.gc(),而是使用JVM的垃圾回收器
            System.gc();
            Thread.sleep(1000);
        }
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到