Java 中的零垃圾大字符串反序列化,巨大的对象问题

Zero-garbage large String deserialization in Java, Humongous object issue

我正在寻找一种从 Java 中的 byte[] 反序列化 String 的方法,同时尽可能少地产生垃圾。因为我正在创建自己的序列化器和反序列化器,所以我可以完全自由地在服务器端(即序列化数据时)和客户端(即反序列化数据时)实施任何解决方案。

我通过遍历 String's 个字符(String.charAt(i))和将每个 char(16 位值)转换为 2x 8 位值。关于这个 here 有一场很好的辩论。另一种方法是使用反射直接访问底层 char[]String's,但这超出了问题的范围。

但是,我似乎不可能在不创建 char[] 两次 的情况下反序列化 byte[],这看起来很奇怪。

程序:

  1. 创建char[]
  2. 遍历 byte[] 并填写 char[]
  3. 使用 String(char[]) 构造函数创建字符串

由于 Java 的 String 不变性规则,构造函数复制 char[],产生 2 倍的 GC 开销。我总是可以使用机制来规避这种情况(不安全 String 分配 + 设置 char[] 实例的反射),但我只是想问一下除了我打破关于 String's 不变性。

当然,对此最明智的回应是"come on, stop doing this and have trust in GC, the original char[] will be extremely short-lived and G1 will get rid of it momentarily",这实际上是有道理的,如果char[]小于G1区域大小的1/2。如果它更大,char[] 将被直接分配为一个巨大的对象(即自动传播到 G1 区域之外)。此类对象极难在 G1 中进行有效的垃圾回收。这就是每次分配都很重要的原因。

关于如何解决这个问题有什么想法吗?

非常感谢。

Such objects are extremely hard to be efficiently garbage collected in G1.

这可能不再正确,但您必须针对自己的应用程序对其进行评估。 JDK 错误 8027959 and 8048179 引入了用于收集巨大的、短暂的对象的新机制。根据错误标记,您可能必须 运行 使用 jdk ≥8u40 和 ≥8u60 的版本才能获得各自的好处。

感兴趣的实验选项:

-XX:+G1ReclaimDeadHumongousObjectsAtYoungGC

追踪:

-XX:+G1TraceReclaimDeadHumongousObjectsAtYoungGC

有关这些功能的进一步建议和问题,我建议您访问 hotspot-gc-use 邮件列表。

我找到了一个解决方案,但如果您有一个非托管环境,它就没用了。

java.lang.String class 有一个包私有的构造函数 String(char[] value, boolean share)

来源:

/*
* Package private constructor which shares value array for speed.
* this constructor is always expected to be called with share==true.
* a separate constructor is needed because we already have a public
* String(char[]) constructor that makes a copy of the given char[].
*/
String(char[] value, boolean share) {
    // assert share : "unshared not supported";
    this.value = value;
}

这在 Java 中被广泛使用,例如在 Integer.toString()Long.toString()String.concat(String)String.replace(char, char)String.valueOf(char)

解决方案(或黑客,无论你想怎么称呼它)是将 class 移动到 java.lang 包并访问包私有构造函数。这对安全管理器来说不是好兆头,但这可以避免。

使用简单的 "secret" 本机 Java 库找到了可行的解决方案:

String longString = StringUtils.repeat("bla", 1000000);
char[] longArray = longString.toCharArray();
String fastCopiedString = SharedSecrets.getJavaLangAccess().newStringUnsafe(longArray);