是否有 java.lang.String 的内存高效替代品?在JVM的一点点帮助下...目的证明手段的合理性让它变得重要!

2022-09-01 05:46:27

在阅读了这篇测量几种对象类型的内存消耗的旧文章后,我惊讶地发现Java中使用了多少内存:String

length: 0, {class java.lang.String} size = 40 bytes
length: 7, {class java.lang.String} size = 56 bytes

虽然本文有一些技巧可以最大限度地减少这种情况,但我发现它们并不完全令人满意。用于存储数据似乎是浪费的。对于大多数西方语言来说,明显的改进是使用和像UTF-8这样的编码,因为你只需要一个字节来存储最常见的字符,而不是两个字节。char[]byte[]

当然,可以使用 和 。甚至 String 实例本身的开销也会消失。但是在那里你失去了非常方便的方法,如,,,...String.getBytes("UTF-8")new String(bytes, "UTF-8")equals()hashCode()length()

据我所知,Sun拥有Strings表示的专利byte[]

在Java编程环境中有效表示字符串对象的框架...
这些技术可以实现,以便在适当的时候将Java字符串对象创建为单字节字符数组...

但我未能找到该专利的API。

我为什么在乎?
在大多数情况下,我没有。但是我处理的应用程序具有巨大的缓存,包含大量字符串,这将受益于更有效地使用内存。

有人知道这样的API吗?或者有没有另一种方法可以保持字符串的内存占用量较小,即使以牺牲CPU性能或更丑陋的API为代价?

请不要重复上述文章中的建议:

  • 自己的变体(可能带有String.intern()SoftReferences)
  • 存储单个并利用当前实现以避免数据复制(讨厌)char[]String.subString(.)

更新

我在 Sun 当前的 JVM (1.6.0_10) 上运行了文章中的代码。它产生了与2002年相同的结果。


答案 1

在JVM的一点点帮助下...

警告:此解决方案在较新的 Java SE 版本中现已过时。请参阅下面的其他即席解决方案。

如果您使用 HotSpot JVM,则从 Java 6 update 21 开始,您可以使用以下命令行选项:

-XX:+UseCompressedStrings

JVM 选项页面显示:

对字符串使用 byte[],它可以表示为纯 ASCII。(在 Java 6 Update 21 性能版本中引入)

更新:此功能在更高版本中被破坏,并且应该按照6u25 b03发行说明中提到的在Java SE 6u25中再次修复(但是我们在6u25最终发行说明中没有看到它)。出于安全原因,7016213 bug 报告不可见。因此,请小心使用并首先检查。像任何选项一样,它被认为是实验性的,并且可能会在没有太多通知的情况下进行更改,因此最好不要在生产服务器的启动scrip中使用它。-XX

2013-03年更新(感谢Aleksey Maximus的评论):请参阅此相关问题及其可接受的答案。该选项现在似乎已经死亡。这在 bug 7129417报告中得到了进一步的证实。

目的证明手段的合理性

警告:针对特定需求的(丑陋)解决方案

这有点开箱即用,而且水平较低,但是既然你问了...不要打信使!

您自己的更轻的字符串表示

如果ASCII可以满足您的需求,那么您为什么不直接推出自己的实现呢?

正如你所提到的,你可以而不是在内部。但这还不是全部。byte[]char[]

为了做到更轻量级,为什么不简单地使用一个帮助器类,而不是将字节数组包装在一个类中,为什么不简单地使用一个帮助器类,该类主要包含对你传递的这些字节数组进行操作的静态方法呢?当然,它会感觉很C-ish,但它会起作用,并且可以为您节省与对象相关的巨大开销。String

当然,它会错过一些不错的功能...除非你重新实现它们。如果你真的需要它们,那么没有太多的选择。多亏了OpenJDK和许多其他好的项目,你完全可以推出自己的模糊类,这些类只对参数进行操作。每次需要调用函数时,您都会想洗澡,但您将节省大量内存。LiteStringsbyte[]

我建议让它与类的合约非常相似,并提供有意义的适配器和生成器来转换和转换,并且您可能还希望具有往返和的适配器,以及您可能需要的其他内容的一些镜像实现。绝对是一些工作,但可能是值得的(请参阅下面的“让它计数!”部分)。StringStringStringBufferStringBuilder

动态压缩/解压缩

您完全可以在内存中压缩字符串,并在需要时即时解压缩它们。毕竟,您只需要在访问它们时能够阅读它们,对吧?

当然,如此暴力将意味着:

  • 更复杂的(因此可维护性较差)的代码,
  • 更强的处理能力,
  • 需要相对较长的字符串才能使压缩相关(或者通过实现自己的存储系统将多个字符串压缩为一个字符串,以使压缩更有效)。

同时执行这两项操作

对于完全头痛,当然你可以做所有这些:

  • C-ish 助手类,
  • 字节数组,
  • 动态压缩存储。

确保将其开源。:)

让它变得重要!

顺便说一句,请参阅N. Mitchell和G. Sevitsky关于构建内存效率高的Java应用程序的精彩演讲:[2008版本],[2009版本]。

从这个演示中,我们看到一个8个字符的字符串在32位系统上吃掉64个字节(对于64位系统是96个字节!!),其中大部分是由于JVM开销。从本文中我们看到,一个8字节的数组将“仅”吃24个字节:12个字节的标头,8 x 1字节+ 4个字节的对齐)。

听起来,如果你真的操纵了很多这些东西(并且可能会加快速度,因为你会花更少的时间来分配内存,但不要引用我的话并对其进行基准测试;而且这将在很大程度上取决于你的实现)。


答案 2

在Terracotta,我们在某些情况下,我们会压缩大字符串,因为它们在网络上发送时,实际上将它们压缩,直到需要解压缩。我们通过将 char[] 转换为 byte[],压缩 byte[],然后将该 byte[] 编码回原始 char[] 来做到这一点。对于某些操作,如哈希和长度,我们可以在不解码压缩字符串的情况下回答这些问题。对于像大型 XML 字符串这样的数据,您可以通过这种方式获得大量压缩。

在网络中移动压缩数据是一个明确的胜利。保持压缩取决于用例。当然,我们有一些旋钮来关闭它并更改压缩打开的长度等。

这一切都是通过java.lang.String上的字节码检测完成的,我们发现由于在启动时使用的String很早,因此非常微妙,但如果你遵循一些准则,它是稳定的。