【发布时间】:2018-10-13 03:39:33
【问题描述】:
对 Java 应用程序的一些分析表明,它花费大量时间将 UTF-8 字节数组解码为 String 对象。 UTF-8 字节流来自 LMDB 数据库,数据库中的值是 Protobuf 消息,这就是它如此多地解码 UTF-8 的原因。另一个由此引起的问题是,由于在 JVM 中将内存映射解码为字符串对象,字符串占用了大量内存。
我想重构这个应用程序,使它不会在每次从数据库中读取消息时分配一个新字符串。我希望 String 对象中的底层 char 数组简单地指向内存位置。
package testreflect;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class App {
public static void main(String[] args) throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe UNSAFE = (Unsafe) field.get(null);
char[] sourceChars = new char[] { 'b', 'a', 'r', 0x2018 };
// Encoding to a byte array; asBytes would be an LMDB entry
byte[] asBytes = new byte[sourceChars.length * 2];
UNSAFE.copyMemory(sourceChars,
UNSAFE.arrayBaseOffset(sourceChars.getClass()),
asBytes,
UNSAFE.arrayBaseOffset(asBytes.getClass()),
sourceChars.length*(long)UNSAFE.arrayIndexScale(sourceChars.getClass()));
// Copying the byte array to the char array works, but is there a way to
// have the char array simply point to the byte array without copying?
char[] test = new char[sourceChars.length];
UNSAFE.copyMemory(asBytes,
UNSAFE.arrayBaseOffset(asBytes.getClass()),
test,
UNSAFE.arrayBaseOffset(test.getClass()),
asBytes.length*(long)UNSAFE.arrayIndexScale(asBytes.getClass()));
// Allocate a String object, but set its underlying
// byte array manually to avoid the extra memory copy
long stringOffset = UNSAFE.objectFieldOffset(String.class.getDeclaredField("value"));
String stringTest = (String) UNSAFE.allocateInstance(String.class);
UNSAFE.putObject(stringTest, stringOffset, test);
System.out.println(stringTest);
}
}
到目前为止,我已经弄清楚了如何使用 Unsafe 包将字节数组复制到 char 数组并在 String 对象中设置底层数组。这应该会减少应用程序浪费在解码 UTF-8 字节上的 CPU 时间。
但是,这并不能解决内存问题。有没有办法让一个 char 数组指向一个内存位置并完全避免内存分配?完全避免复制将减少 JVM 为这些字符串进行的不必要分配次数,从而为操作系统从 LMDB 数据库缓存条目留出更多空间。
【问题讨论】:
-
如果性能很关键,为什么不实现一个 CharSequence 来满足您的需求?字符串只是一种特殊类型的 CharSequence,它强调的是不变性和封装性,而不是性能。您的 CharSequence 实现可能会考虑其他优先级,例如接受 byte[] 并且不执行复制或额外的内存分配。 docs.oracle.com/javase/7/docs/api/java/lang/CharSequence.html
-
我可以,但这需要重构我们已经拥有的一堆代码,(equals 必须替换为 compareTo 等)。
-
一些澄清,我对使用 CharSequence 犹豫不决,因为编译器不会通过用 CharSequence 替换 String 实例来帮助发现潜在的代码破坏更改。然而,这是一个有效的选择,我会考虑的。但是,保留 String 将是理想的。
标签: java unsafe-pointers