2013-09-05 10 views
5

Ho scritto due programmi di caricamento classe personalizzati per caricare il codice in modo dinamico.Deadlock Java in Class Loader

Il primo fa caricare il codice da un vaso:

package com.customweb.build.bean.include; 

import java.net.URL; 
import java.net.URLClassLoader; 

import com.customweb.build.process.ILeafClassLoader; 

public class JarClassLoader extends URLClassLoader implements ILeafClassLoader { 

    public JarClassLoader(URL[] urls, ClassLoader parent) { 
     super(urls, parent); 
    } 

    @Override 
    public Class<?> findClassWithoutCycles(String name) throws ClassNotFoundException { 

     Class<?> c = findLoadedClass(name); 
     if (c != null) { 
      return c; 
     } 

     return findClass(name); 
    } 

    @Override 
    protected Class<?> findClass(String qualifiedClassName) throws ClassNotFoundException { 
     synchronized (this.getParent()) { 
      synchronized (this) { 
       return super.findClass(qualifiedClassName); 
      } 
     } 
    } 

    @Override 
    public URL findResourceWithoutCycles(String name) { 
     return super.findResource(name); 
    } 

    @Override 
    public Class<?> loadClass(String name) throws ClassNotFoundException { 
     synchronized (this.getParent()) { 
      synchronized (this) { 
       return super.loadClass(name); 
      } 
     } 
    } 

} 

L'altra classe di carico prende più caricatori di classe per consentire di accedere alle classi degli altri caricatori di classe. Durante l'inizializzazione del primo, ho impostato un'istanza di questo caricatore di classi come genitore. Per interrompere il ciclo, utilizzo il metodo 'findClassWithoutCycles'.

package com.customweb.build.process; 

import java.net.URL; 
import java.security.SecureClassLoader; 
import java.util.ArrayList; 
import java.util.List; 

public class MultiClassLoader extends SecureClassLoader { 

    private final List<ClassLoader> classLoaders = new ArrayList<ClassLoader>(); 

    public MultiClassLoader(ClassLoader parent) { 
     super(parent); 
    } 

    public void addClassLoader(ClassLoader loader) { 
     this.classLoaders.add(loader); 
    } 

    @Override 
    protected Class<?> findClass(String name) throws ClassNotFoundException { 

     for (ClassLoader loader : classLoaders) { 
      try { 
       if (loader instanceof ILeafClassLoader) { 
        return ((ILeafClassLoader) loader).findClassWithoutCycles(name); 
       } else { 
        return loader.loadClass(name); 
       } 
      } catch (ClassNotFoundException e) { 
       // Ignore it, we try the next class loader. 
      } 
     } 

     throw new ClassNotFoundException(name); 
    } 

    @Override 
    protected URL findResource(String name) { 

     for (ClassLoader loader : classLoaders) { 
      URL url = null; 
      if (loader instanceof ILeafClassLoader) { 
       url = ((ILeafClassLoader) loader).findResourceWithoutCycles(name); 
      } else { 
       url = loader.getResource(name); 
      } 

      if (url != null) { 
       return url; 
      } 
     } 

     return null; 
    } 
} 

Ma quando uso questi caricatori di classe ottengo il più delle volte un deadlock. Ho passato qui la discarica filo: http://pastebin.com/6wZKv4Y0

Dal momento che i blocchi Java ClassLoader in alcuni metodi il filo sincronizzando su $ questo, cerco di sincronizzazione sul MultiClassLoader prima e poi sul JarClassLoader. Questo dovrebbe prevenire eventuali deadlock, quando l'ordine di acquisizione di un lock è lo stesso. Ma sembra che da qualche parte nella routine di caricamento della classe nativa venga acquisito un blocco sul class loader. Sono giunto a questa conclusione perché il thread 'pool-2-thread-8' è bloccato sull'oggetto '0x00000007b0f7f710'. Ma nel log non riesco a vedere quando viene acquisito questo blocco e da quale thread.

Come posso sapere quale thread esegue la sincronizzazione sul classloader?

Modifica: Ho risolto il problema sincronizzandomi su tutti i programmi di caricamento classe prima di richiamare il loadClass di MultiClassLoader.

+0

Perché la sincronizzazione sul caricatore della classe padre? Non vedo alcuna ragione per questo. – Holger

+0

Durante la definizione della classe, ClassLoader viene utilizzato come Blocco (sincronizza (questo)). Quando viene effettuata una chiamata loadClass() in MultiClassLoader, anche questo ClassLoader viene sincronizzato. Mezzi: ci sono casi in cui il genitore (MultiClassLoader) viene sincronizzato prima che JarClassLoader sia sincronizzato. In questo caso si entra in una situazione di stallo. Sincronizzando puoi evitare questo deadlock. –

+0

Non vedo come aggiungere più sincronizzazione dovrebbe essere in grado di evitare un deadlock. La tua domanda dimostra che questo concetto non funziona. Comunque spero che la mia risposta possa essere d'aiuto. – Holger

risposta

4

JVM acquisisce un blocco sul ClassLoader prima di richiamare loadClass. Ciò accade se la classe caricata tramite uno dei tuoi JarClassLoader fa riferimento a un'altra classe e la JVM tenta di risolvere tale riferimento. Andrà direttamente al ClassLoader che ha creato la classe, bloccarlo e invocare loadClass. Ma poi stai cercando di ottenere un blocco sul caricatore genitore prima di bloccare nuovamente JarClassLoader. Quindi l'ordine delle due serrature non funziona.

Ma non vedo i motivi per nessuno dei due blocchi poiché non si accede alle risorse che richiedono la sincronizzazione. Lo stato interno ereditato di URLClassLoader viene mantenuto dalla sua implementazione stessa.

Tuttavia, se si desidera aggiungere più stato alle classi che richiedono la sincronizzazione, è necessario utilizzare meccanismi diversi per bloccare le istanze di ClassLoader.

http://docs.oracle.com/javase/7/docs/technotes/guides/lang/cl-mt.html dice

Se si dispone di un caricatore di classe personalizzato con un rischio di deadlocking, con il 7 rilascio di Java SE, è possibile evitare situazioni di stallo seguendo queste regole:

  1. garantire che il tuo caricatore di classi personalizzato è multithread sicuro per il caricamento simultaneo di classi.

    a. Stabilire uno schema di blocco interno. Ad esempio, java.lang.ClassLoader utilizza uno schema di blocco basato sul nome della classe richiesta.

    b.Rimuovi tutta la sincronizzazione sul blocco oggetto del programma di caricamento da solo.

    c. Assicurarsi che le sezioni critiche siano sicure per più thread che caricano classi diverse.

  2. Nel proprio inizializzatore statico del programma di caricamento personalizzato, richiamare il metodo statico di java.lang.ClassLoader registerAsParallelCapable(). Questa registrazione indica che tutte le istanze del caricatore di classi personalizzate sono sicure per il multithread.

  3. Verificare che tutte le classi di caricamento classi che questo programma di caricamento classe personalizzato estenda invocano anche il metodo registerAsParallelCapable() nei relativi inizializzatori di classe. Assicurati che siano multithread sicuri per il caricamento simultaneo della classe.

Se il caricatore di classe personalizzata sovrascrive solo findClass (String), non è necessario ulteriori modifiche. Questo è il meccanismo consigliato per creare un caricatore di classi personalizzato.

+0

@Holgar: la tua risposta è stata molto utile. Non ho saputo che la JVM si blocca sul caricatore di classi, quando viene caricata una classe. Ma non è riuscito a utilizzare registerAsParallelCapable(). Ho bloccato tutti i programmi di caricamento classe in MultiClassLoader, prima di richiamare loadClass(). Ciò preserva l'ordine di acquisizione delle serrature. –