Удалены ли WeakHashMap во время полного GC?

Я столкнулся с некоторыми проблемами с WeakHashMap.

Рассмотрим этот пример кода:

List<byte[]> list = new ArrayList<byte[]>();

Map<String, Calendar> map = new WeakHashMap<String, Calendar>();
String anObject = new String("string 1");
String anOtherObject = new String("string 2");

map.put(anObject, Calendar.getInstance());
map.put(anOtherObject, Calendar.getInstance());
// In order to test if the weakHashMap works, i remove the StrongReference in this object
anObject = null;
int i = 0;
while (map.size() == 2) {
   byte[] tab = new byte[10000];
   System.out.println("iteration " + i++ + "map size :" + map.size());
   list.add(tab);
}
System.out.println("Map size " + map.size());

Этот код работает. Внутри циклов я создаю объект. Когда возникает незначительный GC, размер карты равен 1 на 1360-й итерации. Все в порядке.

Теперь, когда я прокомментирую эту строку:

//anObject = null; 

Я ожидаю иметь OutOfMemoryError, потому что mapSize всегда равен 2. Однако на 26XXX-й итерации происходит полный GC, а размер карты равен 0. Я не понимаю, почему?

Я думал, что карта не должна быть очищена, потому что есть и сильные ссылки на оба объекта.

Ответ 1

Компилятор "точно в срок" анализирует код, видит, что anObject и anOtherObject не используются после цикла и удаляют их из локальной таблицы переменных или устанавливают их в null, тогда как цикл Все еще работает. Это называется компиляцией OSR.

Позже GC собирает строки, потому что нет сильных ссылок на них.

Если вы использовали anObject после цикла, вы все равно получите OutOfMemoryError.

Обновление: В моем блоге вы найдете более подробное обсуждение компиляции OSR.

Ответ 2

Бит копания показывает, что это явно описано в JLS, раздел 12.6.1:

Оптимизация преобразований программы может быть сконструирована таким образом, чтобы уменьшить количество объектов, которые достижимы, меньше, чем те, которые наивно считались бы доступными. Например, компилятор или генератор кода может выбрать установку переменной или параметра, который больше не будет использоваться для нулевого значения, чтобы скорее сократить потенциальную возможность хранения такого объекта.

(Bolding - мое дополнение.)

http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.6.1

Таким образом, по сути, JIT разрешает удалять сильные ссылки всякий раз, когда захочет, может ли он работать, что они больше никогда не будут использоваться - что именно происходит здесь.

Это большой вопрос, хотя и делает для большой головоломки, который может легко показать только потому, что у объекта есть сильная ссылка в сфере видимости, не обязательно означает, что он не был собран мусором. Вследствие этого это означает, что вы явно не можете ничего гарантировать, когда запускается финализатор, это может быть даже в том случае, когда кажется, что объект все еще в области видимости!

Например:

List<byte[]> list = new ArrayList<byte[]>();

Object thing = new Object() {
    protected void finalize() {
        System.out.println("here");
    }
};
WeakReference<Object> ref = new WeakReference<Object>(thing);

while(ref.get()!=null) {
    list.add(new byte[10000]);
}
System.out.println("bam");

Вышеприведенный пример, который показывает, что объект завершается, и GC'd сначала, даже если ссылка на thing все еще существует (здесь напечатано, затем bam.)

Ответ 3

Просто добавьте немного замечательных ответов от Joni Salonen и berry120. Можно показать, что JIT на самом деле ответственен за "удаление переменных", просто отключая его с помощью -Djava.compiler=NONE. Как только вы отключите его, вы получите OOME.

Если мы хотим знать, что происходит под капотами, опция XX:+PrintCompilation показывает активность JIT. Используя его с кодом из вопроса, мы получим следующее:

1       java.lang.String::hashCode (64 bytes)
2       java.lang.String::charAt (33 bytes)
3       java.lang.String::indexOf (151 bytes)
4       java.util.ArrayList::add (29 bytes)
5       java.util.ArrayList::ensureCapacity (58 bytes)
6  !    java.lang.ref.ReferenceQueue::poll (28 bytes)
7       java.util.WeakHashMap::expungeStaleEntries (125 bytes)
8       java.util.WeakHashMap::size (18 bytes)
1%      WeakHM::main @ 63 (126 bytes)
Map size 0

Последняя компиляция (с флагом @) является компиляцией OSR (On Stack Replacement) (подробнее о https://gist.github.com/1165804#file_notes.md). Говоря простыми словами, он позволяет VM заменять метод во время его работы и используется для повышения производительности Java-методов, застрявших в циклах. Я предполагаю, что после запуска этой компиляции JIT удаляет переменные, которые больше не используются.