2012-01-11 11 views
11

Tuve algunos problemas con WeakHashMap.¿WeakHashMap se borra durante un GC completo?

consideran este código de ejemplo:

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()); 

Este código funciona. Dentro de los bucles, estoy creando un objeto. Cuando se produce un GC menor, el tamaño del mapa es igual a 1 en la 1360.a iteración. Todo está bien.

Ahora cuando comento esta línea:

//anObject = null; 

espero tener un OutOfMemoryError porque el mapSize es siempre igual a 2. Sin embargo en la iteración º 26XXX, un GC completa ocurre y el tamaño del mapa es igual a 0. No entiendo por qué?

Pensé que el mapa no debería haberse borrado porque también hay fuertes referencias a ambos objetos.

+0

Creo que su prueba no es correcta. Si cambia 'while (map.size() == 2) {' to 'while (map.size()> 0) {', las dos pruebas finalizarán hasta que el mapa esté vacío, sin importar que usted comente 'anObject = null' o no. Por cierto, ya lo intenté. – donnior

+0

Imprime 'anObject' y' anOtherObject' al final. El compilador ve que ya no los está usando y puede eliminarlos antes. –

Respuesta

10

El justo a tiempo compilador analiza el código, ve que anObject y anOtherObject no se utilizan después del bucle, y los elimina de la tabla de variables locales o los pone a null, mientras que el bucle todavía está en marcha. Esto se llama compilación OSR.

Más tarde, el GC recoge las cadenas porque no hay referencias sólidas sobre ellas.

Si usaste anObject después del ciclo todavía obtendrías un OutOfMemoryError.

Actualización: Encontrarás una discusión más detallada sobre OSR compilation en mi blog.

+0

Creo que tiene toda la razón, pero ¿no es esta una posible optimización de JIT? Si 'anObject' tiene un finalizador y es GC'd antes de que desaparezca la referencia, el finalizador se ejecutará potencialmente antes de lo previsto. – berry120

+0

¿Qué podría romperse? Cuando se ejecuta el finalizador, la referencia fuerte ya no existe. – Joni

+0

Podría romperse en el sentido de que podría ejecutar un finalizador antes de lo esperado; antes de que la referencia dura haya salido del alcance. – berry120

7

poco de excavación revela que esta está cubierta explícitamente en el JLS, sección 12.6.1:

transformaciones optimización de un programa pueden ser diseñados que reducir el número de objetos que son accesibles a ser inferiores a las que ingenuamente se consideraría alcanzable. Por ejemplo, un compilador o generador de código puede optar por establecer una variable o parámetro que ya no se utilizará para anular para hacer que el almacenamiento de dicho objeto sea potencialmente recuperable antes.

(negrita es mi adición.)

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

Así que en esencia, se permite que el JIT para eliminar las referencias fuertes cada vez que quiera si se puede trabajar que nunca van a ser utilizados de nuevo, que es exactamente lo que está sucediendo aquí.

Sin embargo, esta es una gran pregunta y hace que un gran rompecabezas que puede mostrarse simplemente porque un objeto parece tener una gran referencia en el alcance, no necesariamente significa que no se haya recogido basura. Siguiendo con esto, significa que explícitamente no se puede garantizar nada sobre cuándo se ejecutará un finalizador, ¡incluso en el caso en el que parece que el objeto aún está en el alcance!

Ej:

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"); 

Lo anterior es un ejemplo simple que muestra el objeto se finalizó y GC'd primero a pesar de que todavía existe la referencia a thing

7
(aquí se imprime, a continuación, bam.)

Solo para agregar algo a las excelentes respuestas de Joni Salonen y berry120. Se puede demostrar que el JIT es en realidad el responsable de la "eliminación variable" simplemente apagándolo con -Djava.compiler=NONE. Una vez que lo apagas, obtienes el OOME.

Si queremos saber qué está pasando debajo de los capós, la opción XX:+PrintCompilation muestra la actividad de JIT. Al usarlo con el código de la cuestión de la salida obtenemos es la siguiente:

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 

La última compilación (con la bandera @) es una OSR (En reemplazo de la pila) compilación (comprobar https://gist.github.com/1165804#file_notes.md para más detalles). En palabras simples, permite a la máquina virtual reemplazar un método mientras se está ejecutando y se utiliza para mejorar el rendimiento de los métodos de Java atrapados en los bucles. Supongo que después de que esta compilación se active, el JIT elimina las variables que ya no se usan.

Cuestiones relacionadas