2013-09-05 11 views
5

Napisałem dwa niestandardowe programy ładujące klasy, aby dynamicznie ładować kod.Zakleszczenie Java w ClassLoaders

Pierwszy z nich ma kod obciążenia ze słoika:

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

} 

Druga klasa ładowarka ładowarki trwa kilka klas, aby umożliwić dostęp do klasy innych ładowarek klasy. Podczas inicjowania pierwszego ustawiłem instancję tego programu ładującego klasy jako rodzica. Aby przerwać cykl, używam metody "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; 
    } 
} 

Ale kiedy używam tych ładowarek, najczęściej dostaję impasu. Mam przeszłość tutaj wysypisko wątku: http://pastebin.com/6wZKv4Y0

ponieważ bloki Java classloader w niektórych metodach wątek synchronizując na $ to staram się synchronizacji na MultiClassLoader a potem na JarClassLoader. Powinno to zapobiec jakimkolwiek zakleszczeniom, gdy kolejność pozyskania zamka jest taka sama. Ale wydaje się, że gdzieś w rodzimej procedurze ładowania klasy jest pobierana blokada programu ładującego klasy. Doszedłem do tego wniosku, ponieważ wątek "pool-2-thread-8" jest zablokowany na obiekcie "0x00000007b0f7f710". Ale w dzienniku nie widzę, kiedy ten zamek został zdobyty i przez który wątek.

Jak mogę się dowiedzieć, który wątek synchronizuje się z programem ładującym klasy?

Edycja: Rozwiązałem go, synchronizując wszystkie ładowniki klasy przed wywołaniem metody loadClass MultiClassLoader.

+0

Dlaczego synchronizacja jest w ładowarce klasy nadrzędnej? Nie widzę żadnego powodu. – Holger

+0

Podczas definicji klasy ClassLoader służy jako blokada (synchronizacja (ta)). Po wywołaniu metody loadClass() w narzędziu MultiClassLoader następuje synchronizacja tej klasy ClassLoader. Oznacza: Są przypadki, gdy rodzic (MultiClassLoader) jest zsynchronizowany przed zsynchronizowaniem JarClassLoader. W takim przypadku możesz wejść w zakleszczenie. Poprzez synchronizację możesz uniknąć tego zakleszczenia. –

+0

Nie widzę, jak dodanie większej synchronizacji powinno być w stanie uniknąć zakleszczenia. Twoje pytanie dowodzi, że ta koncepcja nie działa. Mam jednak nadzieję, że moja odpowiedź pomoże. – Holger

Odpowiedz

4

JVM nabywa blokadę klasy ClassLoader, zanim wywoła metodę loadClass. Dzieje się tak, jeśli klasa załadowana za pośrednictwem jednego z JarClassLoader odwołuje się do innej klasy i JVM próbuje rozwiązać to odwołanie. Przejdzie bezpośrednio do klasy ClassLoader, która utworzyła klasę, zablokuje ją i wywoła metodę loadClass. Ale wtedy próbujesz uzyskać blokadę nadrzędnego programu ładującego przed ponownym zablokowaniem JarClassLoader. Więc zamawianie dwóch zamków nie działa.

Ale nie widzę powodu dla żadnej z dwóch blokad, ponieważ nie masz dostępu do zasobów wymagających synchronizacji. Dziedziczony stan wewnętrzny obiektu URLClassLoader jest utrzymywany przez samą jego implementację.

Jeśli jednak chcesz dodać więcej stanów do klas wymagających synchronizacji, powinieneś użyć innych mechanizmów jako blokujących instancje ClassLoader.

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

Jeśli masz ładowarka zwyczaj klasy z ryzykiem deadlocking, z Java SE 7 wersji, można uniknąć zakleszczenia wykonując następujące zasady:

  1. zapewnić, że Twój niestandardowy program ładujący klasy jest wielowątkowy, bezpieczny do jednoczesnego ładowania klas.

    a. Wybierz wewnętrzny schemat blokowania. Na przykład java.lang.ClassLoader używa schematu blokowania na podstawie żądanej nazwy klasy.

    b.Usuń całą synchronizację z blokady obiektu modułu ładującego klasy samodzielnie:.

    c. Upewnij się, że krytyczne sekcje są bezpieczne dla wielu wątków ładujących różne klasy.

  2. W inicjatorze statycznym niestandardowego modułu ładującego klasy wywołaj statyczną metodę java.lang.ClassLoader registerAsParallelCapable(). Ta rejestracja wskazuje, że wszystkie wystąpienia niestandardowego programu ładującego klasy są wielowątkowe.

  3. Sprawdź, czy wszystkie klasy modułu ładującego klasy, które rozszerza ten niestandardowy program ładujący klasy, również wywołują metodę registerAsParallelCapable() w inicjatorach klasy. Upewnij się, że są wielowątkowe bezpieczne dla jednoczesnego ładowania klas.

Jeśli ładowarka zwyczaj klasa nadpisuje tylko findClass (String), nie ma potrzeby dalszych zmian. Jest to zalecany mechanizm tworzenia niestandardowego programu ładującego klasy.

+0

@Holgar: Twoja odpowiedź była bardzo pomocna. Nie wiedziałem, że JVM blokuje w programie ładującym klasy, gdy klasa jest ładowana. Ale nie udało się użyć registerAsParallelCapable(). Mam blokadę wszystkich programów ładujących klasy w MultiClassLoader, przed wywołaniem metody loadClass(). Zachowuje to kolejność nabywania zamków. –

Powiązane problemy