UPD 21.11.2017: ошибка исправлена в версии JDK, см comment from Vicente RomeroJava «для» реализации личных данных предотвращает мусор сбора
Резюме:
Если 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);
}
}
компиляции и запустите его с помощью ограниченный объем памяти, как это (180 Мб):
javac IteratorAndGc.java && java -Xms180m -Xmx180m IteratorAndGc
и во время выполнения я есть:
Перед первой памяти распределения: 1251k от максимального 176640k
После первого распределения памяти: 131426k из макс 176640k
После итерации: 131426k от максимального 176640k
После того, как силы GC в теле метода: 110682k от максимального 176640k (почти ничего не собирали)
Try для выделения памяти в теле метода снова:
!!!! Out of memory error !!!!: 168948k of max 176640k
ГХ после того, как тела метода: 459k от максимального 176640k (мусор собирают)
Третье распределение вне метода всегда успешно: 117740k от максимального 163840k
Таким образом, внутри gcInMethod() Я попытался выделить список, перебрать его, отбросить ссылку на список, (необязательно) принудительно собрать мусор и снова разместить аналогичный список. Но я не могу выделить второй массив из-за нехватки памяти.
В то же время за пределами тела функции я могу успешно принудительно собрать сбор мусора (необязательно) и снова распределить тот же размер массива!
Чтобы избежать этого OutOfMemoryError внутри тела функции этого достаточно, чтобы удалить/комментировать только одну строку:
for (String string : strings);
< - это зло !!!
, а затем результат выглядит следующим образом:
Перед первой выделения памяти: 1251k от максимального 176640k
После первого выделения памяти: 131409k от максимального 176640k
После итерации: 131409k от максимального 176640k
После усилия GC в корпусе метода: 497k макс. 176640k (сбор мусора!)
Попытка выделить память в теле метода снова:
После вторичного распределения памяти: 115541k от максимального 163840k
ГХ после тела метода: 493k от максимального 163840k (! Мусор собирают)
третьего распределения вне метода всегда успешно: 121300k от максимального 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)
Примечание:
Q uestion не о стиле программирования, передовой практике, соглашениях и т. д., вопрос об эффективности платформы Java .
речь идет не о
System.gc()
поведения (вы можете удалить все Gc вызовов из примера) - во втором распределении струн JVM должны освободить dicarded памяти.
Reference to the test java class, Online compiler to test (но этот ресурс имеет только 50 Мб динамической памяти, поэтому используйте N = 5000)
Вы неправильно понимаете, как работает GC. Нет никакой гарантии, что GC будет собирать что-либо после одного звонка. – Andremoniy
Черт, прочитайте описание, PLS! вопрос не о вызове GC !!! «вопрос не о поведении System.gc() (вы можете удалить все вызовы gc из примера) - во время выделения второй строки JVM должна освободить дисковое пространство». Вопрос о реализации «для». – radistao
@ Andremoniy pls, прочитайте описание и удалите флаг дублирования - вопрос о реализации «для», а не о вызове System.gc() – radistao