2012-06-30 17 views
5

Estaba jugando con classLoaders en Java y noté algo extraño. Si un classLoader carga una clase desde un jar, este jar está bloqueado indefinidamente incluso si deshaces tu classLoader.dilema de Java classLoader con jaros bloqueados

En el siguiente ejemplo, el contenedor contiene una clase llamada HelloWorld. Lo que hago es tratar de cargar la clase contenida en el jar a través de un classLoader que agrega el jar dinámicamente. Si configura skip en true y no llama al Class.forName, puede eliminar el jar pero si no se salta e incluso si desvincula el classLoader (classLoader = null), el jar no se puede eliminar hasta que salga la JVM.

¿Por qué es eso?

PS: Estoy usando Java 6 y el código es muy detallado para propósitos de prueba

package loader; 

import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.net.MalformedURLException; 
import java.net.URL; 
import java.net.URLClassLoader; 

public class TestClassLoader { 

    private URLClassLoader classLoader; 

    public TestClassLoader() throws MalformedURLException, IOException { 
     System.out.println("Copying jar"); 
     if (copyJar()) { 
      System.out.println("Copying SUCCESS"); 
      performFirstCheck(); 
     } else { 
      System.out.println("Copying FAILED"); 
     } 
    } 

    public static void main(String[] args) throws IOException { 
     System.out.println("Test started"); 
     TestClassLoader testClassLoader = new TestClassLoader(); 
     System.out.println("Bye!"); 
    } 

    public void performFirstCheck() throws IOException { 
     System.out.println("Checking class HelloWorld does not exist"); 
     if (!checkClassFound(TestClassLoader.class.getClassLoader(), false)) { 
      System.out.println("Deleting jar"); 
      deleteJar(); 
      System.out.println("First Check SUCCESS"); 
      performSecondCheck(); 
     } else { 
      System.out.println("First Check FAILED"); 
     } 
    } 

    private void performSecondCheck() throws IOException { 
     System.out.println("Copying jar"); 
     if (copyJar()) { 
      System.out.println("Copying SUCCESS"); 
      createClassLoaderAndCheck(); 
     } else { 
      System.out.println("Copying FAILED"); 
     } 
    } 

    private void createClassLoaderAndCheck() throws MalformedURLException { 
     System.out.println("Creating classLoader"); 
     createClassLoader(); 
     System.out.println("Checking class HelloWorld exist"); 
     if (checkClassFound(classLoader, true)) { 
      System.out.println("Second Check SUCCESS"); 
        classLoader = null; 
      System.out.println("Deleting jar"); 
      if (deleteJar()) { 
       System.out.println("Deleting SUCCESS"); 
      } else { 
       System.out.println("Deleting FAILED"); 
      } 
     } else { 
      System.out.println("Second Check FAILED"); 
     } 
    } 

    public void createClassLoader() throws MalformedURLException { 
     URL[] urls = new URL[1]; 
     File classFile = new File("C:\\Users\\Adel\\Desktop\\classes.jar"); 
     urls[0] = classFile.toURI().toURL(); 
     classLoader = new URLClassLoader(urls); 
    } 

    public boolean checkClassFound(ClassLoader classLoader, boolean skip) { 
     if (skip) { 
      System.out.println("Skiping class loading"); 
      return true; 
     } else { 
      try { 
       Class.forName("HelloWorld", true, classLoader); 
       return true; 
      } catch (ClassNotFoundException e) { 
       return false; 
      } 
     } 
    } 

    public URLClassLoader getClassLoader() { 
     return classLoader; 
    } 

    public boolean copyJar() throws IOException { 
     File sourceJar = new File("C:\\Users\\Adel\\Desktop\\Folder\\classes.jar"); 
     File destJar = new File("C:\\Users\\Adel\\Desktop\\classes.jar"); 
     if (destJar.exists()) { 
      return false; 
     } else { 
      FileInputStream finput = new FileInputStream(sourceJar); 
      FileOutputStream foutput = new FileOutputStream(destJar); 
      byte[] buf = new byte[1024]; 
      int len; 
      while ((len = finput.read(buf)) > 0) { 
       foutput.write(buf, 0, len); 
      } 
      finput.close(); 
      foutput.close(); 
      return true; 
     } 
    } 

    public boolean deleteJar() { 
     File destJar = new File("C:\\Users\\Adel\\Desktop\\classes.jar"); 
     return destJar.delete(); 
    } 

} 
+0

¿Desea una solución o una explicación? ? – esej

+1

@esej Ya encontré las dos, me interesé por verificar mi respuesta y compartir tu opinión? –

Respuesta

8

He encontrado una respuesta y una solución.

Basado en este article y este sorprendente relacionado article, es un mal hábito utilizar Class.forName(className, true, classLoader) porque mantiene la clase en la memoria caché en la memoria indefinidamente.

La solución fue utilizar classLoader.loadClass(clasName) lugar, a continuación, una vez terminado, el classLoader Deshacer referencia y llamar al recolector de basura usando:

classLoader = null; 
System.gc(); 

Esperamos que esto ayude a otros! :)

Antecedentes:

Mi proyecto era un Complexe uno: tuvimos un servidor GWT actúa como cliente RMI a otro servidor. Entonces, para crear instancias, GWT necesitaba descargar las clases del servidor y cargarlas. Más tarde, GWT volvería a enviar la instancia al servidor para persistir en la base de datos usando Hibernate. Con el fin de admitir la implementación en caliente, optamos por la carga de clase dinámica donde un usuario cargaría un jar y notificaría al servidor quién cargaría las clases y presentarlas como disponibles para el servidor GWT

+0

Tenga en cuenta que esto solo funciona si las clases * all * cargadas por este cargador de clases y las instancias * all * de cualquiera de estas clases tampoco tienen referencia. Siempre que haya una sola instancia de dicha clase, el cargador de clases se mantendrá en la memoria (porque puede llamar a 'instancia.getClass(). GetClassLoader()'). –

+0

Excelente, aunque todas las instancias y clases deben ser dereferidas, y pueden terminar en PermGen. Creo que uno debe tener en cuenta que en Java 7 tenemos el 'URLClassLoader # close()' - que debe ser utilizado. Tengo recuerdos vagos de pesadilla al atravesar classpaths y cargar archivos en la memoria para cargar las clases desde ... realmente nunca resolvió realmente los problemas. Los cargadores de clases son animales extraños. – esej

+0

@esej Probablemente quiera decir crear una subclase de ClassLoader, abrir manualmente el archivo y leer su contenido en una matriz de bytes y luego llamar a 'ClassLoader # defineClass (String name, byte [] b, int off, int len)'? Esto debería funcionar. –

2

En Java 7 URLClassLoader tiene un método que soluciona este #close().

+0

Mencioné que estoy usando Java 6 y esto no se puede cambiar ahora: D –

+0

Java 6 es EoL realmente pronto. Puede intentar eliminar todas las referencias al cargador de clases y forzar una finalización completa de GC _y_ ejecución. Pero esto es terrible. O puede escribir su propio cargador de clases jar que tenga un método de cierre. –

+1

Ya encontré una solución usando java 6, me interesa verificar mi respuesta y compartir su opinión? –