2012-06-30 4 views
5

Mi sono divertito con i ClassLoaders in Java e ho notato una cosa strana. Se un classLoader carica una classe da un jar, questo jar viene bloccato a tempo indeterminato anche se si annulla il riferimento al classLoader.Classe Java Dilemma del caricatore con giare bloccate

Nell'esempio seguente, il jar contiene una classe denominata HelloWorld. Quello che faccio è provare a caricare la classe contenuta nel jar tramite un classLoader che aggiunge il jar dinamicamente. Se si imposta skip su e non si chiama Class.forName, è possibile eliminare il barattolo ma se non si salta e anche se si annulla lo classLoader (classLoader = null), il barattolo non può essere eliminato finché non esce JVM.

Perché è quello?

PS: Sto usando Java 6 e il codice è molto verboso a scopo di test

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

Vuoi una soluzione alternativa o una spiegazione ? – esej

+1

@esej Ho già trovato entrambi, mi interessa controllare la mia risposta e condividere la tua opinione? –

risposta

8

Ho trovato una risposta e una soluzione.

Sulla base di questo article e di questa straordinaria correlazione article, è una cattiva abitudine usare Class.forName(className, true, classLoader) perché mantiene la classe memorizzata nella cache indefinitamente.

La soluzione era quella di utilizzare classLoader.loadClass(clasName) invece, poi una volta finito, il unreference classLoader e chiamare il garbage collector utilizzando:

classLoader = null; 
System.gc(); 

Spero che questo aiuti gli altri! :)

Informazione:

Il mio progetto era un complexe uno: abbiamo avuto un server GWT che agisce come un client RMI a un altro server. Quindi per creare istanze, GWT doveva scaricare le classi dal server e caricarle. Successivamente, GWT rinvierà l'istanza al server per mantenerli nel database utilizzando Hibernate. Per supportare la distribuzione a caldo, abbiamo optato per il caricamento dinamico della classe in cui un utente caricare un jar e notifica al server che caricherà le classi da esso e presentarle come disponibili al server GWT

+0

Si noti che questo funziona solo se * tutte * le classi caricate da questo programma di caricamento classi e * tutte * le istanze di una di queste classi sono anch'esse non referenziate. Finché esiste anche un'istanza di tale classe, il programma di caricamento classe verrà mantenuto in memoria (poiché è possibile chiamare 'instance.getClass(). GetClassLoader()'). –

+0

Eccellente, anche se tutte le istanze e le classi devono essere ignorate e potrebbero finire in PermGen. Penso che si dovrebbe notare che in Java 7 abbiamo il 'URLClassLoader # close()' - che dovrebbe essere usato. Ho vaghi ricordi da incubo di attraversare classpath e caricare file in memoria per caricare le classi da ... non ho mai veramente risolto i problemi. I classloader sono strani animali. – esej

+0

@esej Probabilmente si intende creare una sottoclasse di ClassLoader, aprendo manualmente il file e leggendo il suo contenuto in una matrice di byte e quindi chiamando 'ClassLoader # defineClass (Nome stringa, byte [] b, int off, int len)'? Questo dovrebbe funzionare davvero. –

2

In Java 7 URLClassLoader ha un metodo #close() che risolve questo.

+0

Ho detto che sto usando java 6 e questo non può essere modificato ora: D –

+0

Java 6 è EoL molto presto. Puoi provare a rimuovere tutti i riferimenti al programma di caricamento classi e forzare un completamento GC _and_ in esecuzione. Ma questo è terribile. Oppure puoi scrivere il tuo classloader jar che ha un metodo stretto. –

+1

Ho già trovato una soluzione utilizzando java 6, mi interessa controllare la mia risposta e condividere la tua opinione? –