You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否通过sun.misc.Unsafe跨类存储并读取字符串?

使用sun.misc.Unsafe跨类读取堆外字符串时JVM崩溃的解决方法

我来帮你分析下问题所在,以及如何解决这个JVM崩溃的问题:

核心错误点拆解

你的代码里有几个关键问题直接导致了JVM致命错误:

  1. 内存分配严重不足
    在storeString方法中,你只分配了2字节的内存,但"Hello World"有11个字符,每个Java char占2字节,总共需要22字节。这种情况下,后续的循环写入会越界覆盖不属于你的内存区域,直接造成内存 corruption,这是JVM崩溃的主要原因。

  2. freeMemory用法完全错误
    unsafe.freeMemory()只需要传入allocateMemory()返回的起始地址,调用一次即可释放整个分配的内存块。你循环对每个字符地址调用freeMemory(),会重复释放内存或者释放不属于你的内存,触发内存访问违规。

  3. 跨进程地址无效(如果是不同JVM进程的话)
    堆外内存是进程私有资源,如果你在一个JVM进程中得到的地址,在另一个JVM进程中使用,这个地址指向的内存根本不属于新进程,必然会抛出EXCEPTION_ACCESS_VIOLATION错误。

修正后的完整代码

首先修正OffHeapStorage类

package com.util.OffHeapStorage;
import java.lang.reflect.Field;
import sun.misc.Unsafe;

public class OffHeapStorage {
    static Unsafe unsafe = getUnsafe();

    static Unsafe getUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            return (Unsafe) f.get(null);
        } catch (Exception x) {
            x.printStackTrace();
        }
        return null;
    }

    // 修正:根据字符串长度分配足够的内存
    static long storeString(String str) {
        int strLength = str.length();
        long totalBytes = (long) strLength * 2; // 每个char占2字节,用long避免int溢出
        long startAddress = unsafe.allocateMemory(totalBytes);
        
        for (int i = 0; i < strLength; i++) {
            char ch = str.charAt(i);
            unsafe.putChar(startAddress + (long)i * 2, ch);
            System.out.println("Stored " + ch + " to address " + (startAddress + (long)i * 2));
        }
        return startAddress;
    }

    static String fetchString(long startAddress, int strLength) {
        StringBuilder strOut = new StringBuilder(strLength);
        for (int i = 0; i < strLength; i++) {
            char ch = unsafe.getChar(startAddress + (long)i * 2);
            strOut.append(ch);
        }
        return strOut.toString();
    }

    // 修正:只释放起始地址一次
    static void freeStringMemory(long startAddress) {
        unsafe.freeMemory(startAddress);
    }

    public static void main(String[] args) {
        String message = "Hello World";
        long stringAddress = storeString(message);
        System.out.println(fetchString(stringAddress, message.length()));
        freeStringMemory(stringAddress); // 用完务必释放,避免内存泄漏
    }
}

另一个类的调用代码(同一JVM进程内)

import com.util.OffHeapStorage.OffHeapStorage;

public class OffHeapReader {
    public static void main(String[] args) {
        // 注意:这个地址必须是当前JVM进程中由OffHeapStorage.storeString分配的有效地址
        // 如果是不同进程,地址无效,需要用操作系统级别的共享内存机制
        long validAddress = 413100824; // 替换成你实际运行OffHeapStorage时得到的地址
        int strLength = "Hello World".length();

        if (OffHeapStorage.unsafe != null) {
            try {
                String retrievedStr = OffHeapStorage.fetchString(validAddress, strLength);
                System.out.println("读取到的字符串:" + retrievedStr);
                OffHeapStorage.freeStringMemory(validAddress);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

关键注意事项

  • 同一进程内使用:只有在同一个JVM进程内,你得到的堆外内存地址才有效。如果需要跨进程共享内存,你需要使用操作系统提供的共享内存API(比如Windows的CreateFileMapping,Linux的mmap),Unsafe的地址无法跨进程使用。
  • 内存泄漏风险:堆外内存不受JVM垃圾回收管理,务必在使用完后调用freeMemory()释放,否则会造成内存泄漏。
  • Unsafe的危险性:Unsafe直接操作底层内存,任何越界访问、错误释放都会导致JVM崩溃或内存 corruption,使用时务必小心。

内容的提问来源于stack exchange,提问作者ishallwin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:36:33