gpt4 book ai didi

Java "for"语句实现阻止垃圾收集

转载 作者:搜寻专家 更新时间:2023-10-30 19:40:13 25 4
gpt4 key购买 nike

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:

     !!!! Out of memory error !!!!:     168948k of max     176640k

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

因此,无需 for 迭代,垃圾在丢弃对字符串的引用后成功收集,并分配第二次(在函数体内)和第三次分配(在方法外)。

我的假设:

for 语法构造被编译为

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)

最佳答案

感谢您的错误报告。我们已修复此错误,请参阅 JDK-8175883 .正如这里在 enhanced 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$ 持有对数组 data 的引用,一旦未引用该数组就会阻止 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"语句实现阻止垃圾收集,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42403347/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com