【问题标题】:Java Strings : how the memory works with immutable StringsJava 字符串:内存如何与不可变字符串一起工作
【发布时间】:2013-07-30 01:56:55
【问题描述】:

我有一个简单的问题。

byte[] responseData = ...;
String str = new String(responseData);
String withKey = "{\"Abcd\":" + str + "}";

在上面的代码中,这三行是否占用了 3X 内存。例如,如果 responseData 为 1mb,则第 2 行将占用额外的 1mb 内存,然后第 3 行将占用额外的 1mb + xx。这是真的?如果没有,那么它将如何工作。如果是,那么解决此问题的最佳方法是什么。 StringBuffer 能帮上忙吗?

【问题讨论】:

  • 我认为GC 是您唯一可以依靠的人。
  • 问题是我收到 OutOfMemoryException 因为来自服务器的 byte[] 数据很大,这就是为什么我需要首先弄清楚我做错了什么,如果这里一切都很好并且没有因为这些线而创建额外的足迹,那么我会考虑其他方式。
  • 假设 responseData 稍后没有引用并且它没有在静态范围内定义 GC 应该可以完成它的工作。

标签: java android string memory-management stringbuffer


【解决方案1】:

是的,这听起来很对。可能更大,因为您的 1MB 字节数组需要转换为 UTF-16,因此根据编码,它可能更大(如果输入为 ASCII,则为 2MB)。

请注意,一旦使用它的变量超出范围,垃圾收集器就可以回收内存。您可以尽早将它们设置为 null 以帮助它尽可能及时地进行此操作(例如,在您构建字符串之后 responseData = null; )。

如果是,那么解决此问题的最佳方法是什么

“修复”意味着一个问题。如果你有足够的内存没有问题。

问题是我收到 OutOfMemoryException 因为来自服务器的 byte[] 数据非常大,

如果你不这样做,你必须考虑一个更好的替代方案,而不是在内存中保留一个 1MB 的字符串。也许您可以从文件中流式传输数据?还是直接处理字节数组?这是什么数据?

【讨论】:

  • +1 -“最佳”方式取决于 OP 试图优化的内容。但是正如 Thilo 的回答所指出的那样,超出一定的大小,您无法将整个“字符串”保存在内存中……但是您尝试这样做。
  • "或者直接处理字节数组?"如果你能避免它,那个字节数组可能也不需要进入内存。
【解决方案2】:

问题是我收到了OutOfMemoryException,因为来自服务器的byte[] 数据非常大,这就是为什么我需要先弄清楚我做错了什么......

是的。那么基本上你的基本问题是你试图一次将整个字符串保存在内存中。对于足够大的字符串,这总是会失败......即使您以尽可能最佳的内存效率方式对其进行编码。 (这本身就很复杂。)

最终解决方案(即“缩放”的解决方案)是执行以下操作之一:

  • 将数据流式传输到文件系统,或

  • 以您不需要表示整个“字符串”的方式处理它。


您问StringBuffer 是否有帮助。它可能会有所帮助……前提是您正确使用它。诀窍是确保您预先分配StringBuffer(实际上StringBuilder 更好!!)大到足以容纳所有需要的字符。然后使用字符集解码器(直接或使用读取器管道)将数据复制到其中。

但即使使用最佳编码,您也可能需要输入大小为byte[] 3 倍的峰值。


请注意,您的 OOME 问题可能与 GC 或存储泄漏无关。这实际上是关于您正在使用的数据类型的基本空间要求......以及 Java 不提供“字节串”数据类型这一事实。

【讨论】:

    【解决方案3】:

    在我的 apidocs 中没有这样的 OutOfMemoryException。如果是OutOfMemoryError,尤其是在服务器端,你肯定有问题。

    当您收到来自客户端的big 请求时,那些String 相关的语句并不是第一个问题。将 3X 降低到 1X 不是解决方案。

    很抱歉,如果没有更多代码,我将无能为力。

    使用后端存储

    您不应将整个请求正文存储在 byte[] 上。您可以将它们直接存储在任何后端存储上,例如本地文件、远程数据库或云存储。

    我愿意

    copy stream from request to back-end with small chunked buffer
    

    使用流

    如果可以使用 Streams 而不是 Objects。

    我愿意

    response.getWriter().write("{\"Abcd\":");
    copy <your back-end stored data as stream>);
    response.getWriter().write("}");
    

    【讨论】:

      【解决方案4】:

      是的,如果您对现有代码使用 Stringbuffer,您将在最后一步节省 1mb 的堆空间。但是,考虑到您拥有的数据量,我建议您使用外部内存算法,您只需将部分数据放入内存,进行处理,然后再将其放回存储。

      【讨论】:

        【解决方案5】:

        正如其他人所提到的,您真的应该尝试不要在您的移动应用程序中使用这么大的对象,而流式传输应该是您的最佳解决方案。

        也就是说,有一些技术可以减少您的应用现在使用的内存量:

        1. 如果可能,请完全删除byte[] responseData,以便尽快释放它使用的内存(假设它没有在其他任何地方使用)
        2. 首先创建最大的字符串,然后substring() 它,Android 使用 Apache Harmony 作为其标准 Java 库实现。如果您检查its String class implementation,您会看到substring() 是通过创建一个新的String 对象来实现的,该对象具有与原始数据正确的开始和结束偏移量,并且不会创建重复副本。所以执行以下操作可以将整体内存消耗减少至少 1/3

          String withKey = StringBuilder().append("{\"Abcd\").append(str).append("}").toString(); String str = withKey.substring("{\"Abcd\".length(), withKey.length()-"}".length());

        3. 永远不要对大字符串使用类似"{\"Abcd\":" + str + "}" 的东西,在引擎盖下“string_a”+“string_b”被实现为新的StringBuilder().append("string_a").append("string_b").toString();,所以你隐含地创建了两个(或者至少一个,如果编译器是集市)StringBuilders。对于大字符串,最好由您自己接管此过程,因为您对程序有深入的领域知识,而编译器没有,并且知道如何最好地操作字符串。

        【讨论】:

          猜你喜欢
          • 2022-06-28
          • 2012-12-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-23
          • 1970-01-01
          • 2016-11-09
          • 2015-03-30
          相关资源
          最近更新 更多