Java“for”语句实现可防止垃圾回收
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java“for”语句实现可防止垃圾回收,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含5937字,纯文字阅读大概需要9分钟。
内容图文
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”语句实现可防止垃圾回收全部内容,希望文章能够帮你解决Java“for”语句实现可防止垃圾回收所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。