标签:java for-loop garbage-collection memory-management iterator
UPD 21.11.2017:错误在JDK中修复,见comment from Vicente Romero
摘要:
如果for语句用于任何Iterable实现,则集合将保留在堆内存中,直到当前作用域(方法,语句体)结束,即使您没有对集合的任何其他引用,也不会进行垃圾回收.应用程序需要分配新内存.
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8175883
https://bugs.openjdk.java.net/browse/JDK-8175883
这个例子:
如果我有下一个代码,它分配一个包含随机内容的大字符串列表:
import java.util.ArrayList;
public class IteratorAndGc {
// number of strings and the size of every string
static final int N = 7500;
public static void main(String[] args) {
System.gc();
gcInMethod();
System.gc();
showMemoryUsage("GC after the method body");
ArrayList<String> strings2 = generateLargeStringsArray(N);
showMemoryUsage("Third allocation outside the method is always successful");
}
// main testable method
public static void gcInMethod() {
showMemoryUsage("Before first memory allocating");
ArrayList<String> strings = generateLargeStringsArray(N);
showMemoryUsage("After first memory allocation");
// this is only one difference - after the iterator created, memory won't be collected till end of this function
for (String string : strings);
showMemoryUsage("After iteration");
strings = null; // discard the reference to the array
// one says this doesn't guarantee garbage collection,
// Oracle says "the Java Virtual Machine has made a best effort to reclaim space from all discarded objects".
// but no matter - the program behavior remains the same with or without this line. You may skip it and test.
System.gc();
showMemoryUsage("After force GC in the method body");
try {
System.out.println("Try to allocate memory in the method body again:");
ArrayList<String> strings2 = generateLargeStringsArray(N);
showMemoryUsage("After secondary memory allocation");
} catch (OutOfMemoryError e) {
showMemoryUsage("!!!! Out of memory error !!!!");
System.out.println();
}
}
// function to allocate and return a reference to a lot of memory
private static ArrayList<String> generateLargeStringsArray(int N) {
ArrayList<String> strings = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
StringBuilder sb = new StringBuilder(N);
for (int j = 0; j < N; j++) {
sb.append((char)Math.round(Math.random() * 0xFFFF));
}
strings.add(sb.toString());
}
return strings;
}
// helper method to display current memory status
public static void showMemoryUsage(String action) {
long free = Runtime.getRuntime().freeMemory();
long total = Runtime.getRuntime().totalMemory();
long max = Runtime.getRuntime().maxMemory();
long used = total - free;
System.out.printf("\t%40s: %10dk of max %10dk%n", action, used / 1024, max / 1024);
}
}
用有限的内存编译和运行它,像这样(180mb):
javac IteratorAndGc.java && java -Xms180m -Xmx180m IteratorAndGc
在运行时我有:
Before first memory allocating: 1251k of max 176640k
After first memory allocation: 131426k of max 176640k
After iteration: 131426k of max 176640k
After force GC in the method body: 110682k of max 176640k (almost nothing collected)
Try to allocate memory in the method body again:
06002
GC after the method body: 459k of max 176640k (the garbage is collected!)
Third allocation outside the method is always successful: 117740k of max 163840k
所以,在gcInMethod()里面我尝试分配列表,迭代它,丢弃对列表的引用,(可选)强制垃圾收集并再次分配类似的列表.但由于内存不足,我无法分配第二个数组.
同时,在函数体之外我可以成功强制垃圾收集(可选)并再次分配相同的数组大小!
为了避免在函数体内部出现这种OutOfMemoryError,只需删除/注释这一行:
for(String string:strings); < - 这是邪恶的! 然后输出如下:
Before first memory allocating: 1251k of max 176640k
After first memory allocation: 131409k of max 176640k
After iteration: 131409k of max 176640k
After force GC in the method body: 497k of max 176640k (the garbage is collected!)
Try to allocate memory in the method body again:
After secondary memory allocation: 115541k of max 163840k
GC after the method body: 493k of max 163840k (the garbage is collected!)
Third allocation outside the method is always successful: 121300k of max 163840k
因此,在不丢弃对字符串的引用之后迭代成功收集的垃圾,并且第二次分配(在函数体内)并分配第三次(在方法之外).
我的假设:
用于编译语法构造
Iterator iter = strings.iterator();
while(iter.hasNext()){
iter.next()
}
(我检查了这个反编译的javap -c IteratorAndGc.class)
并且看起来像这样的iter引用保持在范围直到结束.您无权访问该引用以使其无效,并且GC无法执行该集合.
也许这是正常的行为(甚至可能在javac中指定,但我还没有找到),但恕我直言,如果编译器创建了一些实例,它应该关心在使用后将它们从范围中丢弃.
这就是我期望实现for语句的方式:
Iterator iter = strings.iterator();
while(iter.hasNext()){
iter.next()
}
iter = null; // <--- flush the water!
使用的java编译器和运行时版本:
javac 1.8.0_111
java version "1.8.0_111"
Java(TM) SE Runtime Environment (build 1.8.0_111-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)
注意:
>问题不在于编程风格,最佳实践,
约定等等,问题是关于Java的效率
平台.
>问题不在于System.gc()行为(您可以删除所有
来自示例的gc调用) – 在第二个字符串分配期间,JVM必须释放被分配的内存.
Reference to the test java class,Online compiler to test(但此资源只有50 Mb的堆,因此使用N = 5000)
解决方法:
感谢错误报告.我们已经修复了这个bug,参见JDK-8175883.正如在增强for的情况下这里评论的那样,javac正在生成合成变量,所以对于像这样的代码:
void foo(String[] data) {
for (String s : data);
}
javac近似产生:
for (String[] arr$= data, len$= arr$.length, i$= 0; i$< len$; ++i$) {
String s = arr$[i$];
}
如上所述,这种转换方法意味着合成变量arr $保存对数组数据的引用,该引用阻止GC在方法内部不再引用时收集数组.通过生成此代码修复了此错误:
String[] arr$= data;
String s;
for (int len$= arr$.length, i$= 0; i$< len$; ++i$) {
s = arr$[i$];
}
arr$= null;
s = null;
我们的想法是将由javac创建的引用类型的任何合成变量设置为null以转换循环.如果我们讨论的是基本类型的数组,那么编译器不会生成对null的最后一次赋值.该错误已在repo JDK repo中修复
标签:java,for-loop,garbage-collection,memory-management,iterator 来源: https://codeday.me/bug/20191001/1837433.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。