【问题标题】:String vs char[]字符串与字符 []
【发布时间】:2013-12-04 10:35:45
【问题描述】:

我有一些来自 IBM 的幻灯片,名为:"From Java Code to Java Heap: Understanding the Memory Usage of Your Application",也就是说,当我们使用 String 而不是 char[] 时,会有

单个字符的最大开销为 24:1!

但我无法理解这里提到的开销。有人可以帮忙吗?

来源:

【问题讨论】:

  • 能否请您也添加对源的引用?
  • 有一些来自 IBM 的幻灯片,名为:从 Java 代码到 Java 堆:了解应用程序的内存使用情况,没有 URL
  • 最好将此信息添加到问题中,而不是模糊的“某处”。 :)
  • 内存性能

标签: java string memory memory-management


【解决方案1】:

此图与 JDK 6-32 位有关。

JDK 6

在 Java-7 之前的世界中,字符串被实现为指向 char[] 数组区域的指针:

// "8 (4)" reads "8 bytes for x64, 4 bytes for x32"

class String{      //8 (4) house keeping + 8 (4) class pointer
    char[] buf;    //12 (8) bytes + 2 bytes per char -> 24 (16) aligned
    int offset;    //4 bytes                     -> three int
    int length;    //4 bytes                     -> fields align to
    int hash;      //4 bytes                     -> 16 (12) bytes
}

所以我数了数:

36 bytes per new String("a") for JDK 6 x32  <-- the overhead from the article
56 bytes per new String("a") for JDK 6 x64.


JDK 7

只是为了比较一下,在 JDK 7+ 中,String 是一个仅包含 char[] 缓冲区和 hash 字段的类。

class String{      //8 (4) + 8 (4) bytes             -> 16 (8)  aligned
    char[] buf;    //12 (8) bytes + 2 bytes per char -> 24 (16) aligned
    int hash;      //4 bytes                         -> 8  (4)  aligned
}

原来如此:

28 bytes per String for JDK 7 x32 
48 bytes per String for JDK 7 x64.

更新

对于3.75:1 比率,请参阅下面@Andrey 的解释。随着字符串长度的增加,这个比例会下降到 1。

有用的链接:

【讨论】:

  • 我知道现在发生了什么。也许您应该在答案中表明这一点。我很困惑,所以我相信其他人可能会。您正在显示字符串的大小,而不是 char[1] 的大小。两者都是显示比率所必需的
  • @Darkhogg 自 Java 7 Update 6 以来它就已经死了。
  • @Darkhogg 邮件列表上有东西;关键是它造成的伤害大于好处。
  • @Darkhogg 是的,运气不好,它会伤害一些用例。另一方面,它对于小字符串更透明、更可预测且更节省空间,这意味着 Java 程序中使用了 99% 的所有字符串。最终效果可能是堆使用量减少。
  • 这一变化的基本原理在mail.openjdk.java.net/pipermail/core-libs-dev/2012-May/…进行了简要描述
【解决方案2】:

在 JVM 中,字符变量存储在单个 16 位内存分配中,对该 Java 变量的更改会覆盖相同的内存位置。这使得创建或更新字符变量非常快速且内存成本低,但会增加 JVM 的与字符串中使用的静态分配相比的开销。

JVM 将 Java 字符串存储在一个可变大小的内存空间(本质上是一个数组)中,该内存空间与创建 String 对象或第一次分配一个价值。因此,具有初始值“帮助!”的对象将分配 96 位存储空间(6 个字符,每个 16 位大小)。这个值被认为是不可变的,允许 JVM 内联对该变量的引用,从而使静态字符串分配非常快速、非常紧凑,而且从 JVM 的角度来看非常高效。

Reference

【讨论】:

  • 我真的不认为 JVM 需要终止字符
  • @ratchetfreak 注意,如果你有空终止符,你可以很容易地在 JVM 的底层使用一些 C 库的函数来操作字符串。至少,这是 Python 实现字符串 with 字符串长度字段 空终止符的一个 原因。可能与 Java 的原因相同。一般来说,有时有一些冗余是很方便的。
  • 这没什么参考价值。 char[] 不存储零终止符。 Python 是另一回事,它更面向 C。
  • @MarkoTopolnik 可能是当您分配一个 char[n] 时,jvm 将分配一个数组,其中包含一个额外的空终止符点,但这是一个实现细节
【解决方案3】:

我将尝试解释源文章中引用的数字。

本文描述了对象元数据,通常包括:类、标志和锁。

类和锁存储在对象头中,在 32 位 VM 上占用 8 个字节。我还没有找到任何关于 JVM 实现的信息,这些信息在对象标头中有标志信息。可能是因为它存储在外部某处(例如,通过垃圾收集器来计算对对象的引用等)。

所以让我们假设这篇文章讨论了一些 x32 AbstractJVM,它使用 12 字节的内存来存储有关对象的元信息。

那么对于char[],我们有:

  • 12 字节元信息(x32 JDK 6 为 8 字节,x64 JDK 为 16 字节)
  • 4 字节的数组大小
  • 每个字符存储 2 个字节
  • 如果字符数是奇数则对齐 2 个字节(在 x64 JDK 上:2 * (4 - (length + 2) % 4)

对于java.lang.String,我们有:

  • 12 字节元信息(x32 JDK6 上 8 字节,x64 JDK6 上 16 字节)
  • String 字段 16 个字节(JDK6 是这样,JDK7 是 8 个字节)
  • 如上所述存储 char[] 所需的内存

那么,让我们计算一下将"MyString" 存储为String 对象需要多少内存:

12 + 16 + (12 + 4 + 2 * "MyString".length + 2 * ("MyString".length % 2)) = 60 bytes.

从另一方面我们知道,只存储我们需要的数据(没有关于数据类型、长度或其他任何信息):

2 * "MyString".length = 16 bytes

开销是60 / 16 = 3.75

类似地,对于单字符数组,我们得到“最大开销”:

12 + 16 + (12 + 4 + 2 * "a".length + 2 * ("a".length % 2)) = 48 bytes
2 * "a".length = 2 bytes
48 / 2 = 24

按照文章作者的逻辑,当我们存储一个空字符串时,最终实现了值无穷大的最大开销:)。

【讨论】:

    【解决方案4】:

    我从旧的 stackoverflow 答案中读到无法得到它。 在 Oracle 的 JDK 中,String 有四个实例级字段:

    A character array
    An integral offset
    An integral character count
    An integral hash value
    

    这意味着每个字符串都引入了一个额外的对象引用(字符串本身),以及除了字符数组本身之外的三个整数。 (偏移量和字符数允许在通过 String#substring() 方法生成的 String 实例之间共享字符数组,这是其他一些 Java 库实现者避免的设计选择。)除了额外的存储成本之外,还有一个更高级别的间接访问,更不用说 String 保护其字符数组的边界检查了。

    如果您可以只分配和使用基本字符数组,则可以节省空间。不过,在 Java 中这样做当然不是惯用的。明智的 cmets 将有理由证明选择的合理性,最好是提及对差异进行分析的证据。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-30
      • 1970-01-01
      • 2020-09-30
      • 2010-09-12
      • 1970-01-01
      • 2011-05-20
      • 2012-04-21
      相关资源
      最近更新 更多