【问题标题】:Strings and Memory Management字符串和内存管理
【发布时间】:2011-10-22 11:47:02
【问题描述】:

我很难理解 Android (Java) 中的字符串和内存管理发生了什么。

为了简化,看一下这段简单的代码:

public void createArray(){
        String s = "";
        String foo = "foo";
        String lorem = "Lorem ipsum ad his scripta blandit partiendo, eum 
                 fastidii accumsan euripidis in, eum liber hendrerit an.";
        ArrayList<Item> array = new ArrayList<Item>();

        for( int i=0; i<20000; i++ ){
            s = foo.replace("foo", lorem);
            array.add( new Item(s) );
        }
        System.gc();
    }

private class Item{
    String name;

    public Item(String name){
        this.name = name;
    }
}

如果执行,createArray 将分配超过 5Mb 的内存。即使使用System.gc() 指令,循环后内存也不会被释放。

现在,如果我们将s = foo.replace("foo", lorem); 替换为s = lorem;,分配的内存只会增加0.5Mb。

我需要了解如何提高我的应用程序的性能。

谁能解释在这种情况下我应该如何替换字符串?为什么System.gc() 不释放内存?

谢谢。

更新

感谢您的回答,现在我明白System.gc() 只是一个提示。

澄清另一个问题(重要的):

如何动态生成 20000 个字符串(“foo1”、“foo2”...“foo20000”)将它们添加到 ArrayList 并且不会耗尽内存?如果这 20000 个字符串是静态的,它们分配的内存不会超过 0.5Mb。此外,如果s = foo.replace("foo", lorem) 创建了一个全新的字符串,为什么我的函数分配了 10 倍的 5Mb 内存?不应该在 1Mb 左右?

(我已经在考虑一种解决方法,但我想确定没有办法在不使用大量内存的情况下动态生成字符串)

【问题讨论】:

  • 在你的 for 循环末尾放 array = null。这应该可以为 GC 释放引用。

标签: java android string memory-management memory-leaks


【解决方案1】:

replace 每次被调用时都会生成一个全新的String 对象,因为 Java 中的字符串是不可变的,因此不能“就地”进行修改。此外,您将 String 添加到 ArrayList 中,该 ArrayList 将保存对新对象的引用,从而防止它被收集。

【讨论】:

  • 但是如何在不耗尽内存的情况下使用替换指令正确创建字符串?假设我需要“foo 1”、“foo 2”、...“foo 20000”。我假设 concat 也有同样的问题。
  • 如果你真的需要存储 20000 个字符串,那么你需要 20000 个字符串的内存。如果这比您拥有的内存更多,您将不得不想办法解决这个问题(可能以 5000 个或类似的方式生成和使用它们。)如果您需要生成、使用然后摆脱 20000字符串,不要将每一个都存储在ArrayList中。
  • s = lorem;在内存中存储20000个字符串没有问题。我的问题是我需要动态生成的字符串。
  • 正确,因为使用s = lorem 您不会创建新字符串。您只是将lorem 的引用分配给s。所以你仍然只有一个字符串。
  • @Xavi,dlev 触及了问题的核心。如果你没有太多的内存,关键不是如何使用比你拥有的更多的内存,而是如何用更少的内存做同样数量的事情。可能不需要预先存储所有字符串,您可能可以生成一个字符串,对其进行操作,然后将其处理(循环 20000 次)。
【解决方案2】:

因为String 是不可变的,所以每次调用foo.replace("foo", lorem) 都会创建一个新的String。在您仅设置s = lorem 的示例中,不会创建新的String

此外,System.gc() 只是对 VM 的建议,它绝不会保证垃圾回收。您无法强制进行垃圾收集。 (除了用完所有可用内存)

【讨论】:

  • 但是如何在不耗尽内存的情况下使用替换指令正确创建字符串?假设我需要“foo 1”、“foo 2”、...“foo 20000”。我假设 concat 也有同样的问题。
  • String 的目的是什么?它们可以是 int 原语吗?
【解决方案3】:

System.gc() 提示垃圾收集器在有时间时再次运行。

也就是说,垃圾收集器只收集未引用的对象,而您生成的所有字符串仍由对象array 持有。您完全有可能在System.gc() 之后添加行

for (String item : array) {
   System.out.println(item);
}

这将证明垃圾收集器在不破坏字符串方面的智慧。

如果您确实需要重用字符串,您可以使用 String intern 方法(它会扫描分配的字符串以查找重复项并返回对单个保留字符串的引用,如果找到了。

您可能会争辩说,在您的情况下,垃圾收集器提示可以调用编译时优化技术来实现下游未使用数组,从而在字符串通常在程序结束时被取消引用之前销毁字符串;但是,它会添加在某些运行时环境中看起来很奇怪的副作用(例如调试会话)。

最后,通过这种奇异的编译时间优化,不可能准确地执行反射、代码步进、编译绑定到源代码以及各种类型的反射。也就是说,考虑到有多少开箱即用的功能,Java 可以很好地优化自身。

【讨论】:

    【解决方案4】:

    请记住,System.gc() 不会强制垃圾收集器运行。这只是暗示您希望它在未来的某个时间运行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-28
      • 2013-06-17
      • 2010-10-11
      • 1970-01-01
      • 2011-01-06
      • 2017-01-31
      • 2013-04-16
      • 1970-01-01
      相关资源
      最近更新 更多