字符串和内存管理

时间:2011-08-08 15:09:25

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

我正在努力了解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)创建一个全新的String,为什么我的函数会分配5Mb,这是内存的10倍?不应该在1Mb左右?

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

4 个答案:

答案 0 :(得分:5)

replace每次调用时都会生成一个全新的String对象,因为Java中的字符串是不可变的,因此无法进行“修改”。此外,您将String添加到ArrayList,该{{1}}将保留对新对象的引用,从而阻止其被收集。

答案 1 :(得分:2)

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

此外,System.gc()只是对VM的推荐,它绝不会保证垃圾回收。您无法强制执行垃圾回收。 (除了耗尽所有可用内存)

答案 2 :(得分:2)

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

也就是说,垃圾收集器只收集未引用的对象,并且您生成的所有字符串仍然被对象array保留。完全有可能在System.gc()

之后添加行
for (String item : array) {
   System.out.println(item);
}

这可以证明垃圾收集者不会破坏字符串的智慧。

如果您确实需要重用String,则可以使用String intern方法(它将扫描分配的字符串作为重复项)并返回对单个保留字符串的引用,从而权衡CPU周期以获得可能更低的内存占用量找到。

您可能会争辩说,在您的情况下,垃圾收集器提示可以调用编译时优化技术,以实现该数组不在下游使用,因此在程序结束时通常被解除引用之前就会破坏字符串;但是,它会增加在某些运行时环境(如调试会话)中看起来很奇怪的副作用。

最后,不可能准确地执行反射,代码步进,编译绑定到源代码,以及使用这种奇特的编译时优化的各种类型的反射。也就是说,考虑到你开箱即用的功能,Java可以很好地优化自己。

答案 3 :(得分:1)

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