能否通过sun.misc.Unsafe跨类存储并读取字符串?
使用sun.misc.Unsafe跨类读取堆外字符串时JVM崩溃的解决方法
我来帮你分析下问题所在,以及如何解决这个JVM崩溃的问题:
核心错误点拆解
你的代码里有几个关键问题直接导致了JVM致命错误:
内存分配严重不足
在storeString方法中,你只分配了2字节的内存,但"Hello World"有11个字符,每个Java char占2字节,总共需要22字节。这种情况下,后续的循环写入会越界覆盖不属于你的内存区域,直接造成内存 corruption,这是JVM崩溃的主要原因。freeMemory用法完全错误
unsafe.freeMemory()只需要传入allocateMemory()返回的起始地址,调用一次即可释放整个分配的内存块。你循环对每个字符地址调用freeMemory(),会重复释放内存或者释放不属于你的内存,触发内存访问违规。跨进程地址无效(如果是不同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
相关产品推荐
相关产品推荐

