我正在努力了解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左右?
(我已经考虑过一种解决方法,但我想确保没有办法在不使用大量内存的情况下动态生成字符串)
答案 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()
不会强制垃圾收集器运行。这只是一个暗示,你希望它在未来的某个时刻运行。