Java >> Java tutorial >  >> Tag >> class

Indlæsning af Java-klasse – effekt på ydeevnen!

java.lang.ClassLoader#loadClass() API bruges af tredjepartsbiblioteker, JDBC-drivere, frameworks, applikationsservere til at indlæse en java-klasse i hukommelsen. Applikationsudviklere bruger ikke denne API ofte. Men når de bruger API'er såsom 'java.lang.Class.forName()' eller 'org.springframework.util.ClassUtils.forName()', kalder de internt denne 'java.lang.ClassLoader#loadClass()' API .

Hyppig brug af denne API blandt forskellige tråde under kørsel kan sænke din applikations ydeevne. Nogle gange kan det endda gøre, at hele applikationen ikke reagerer. Lad os i dette indlæg forstå denne API lidt mere og dens indvirkning på ydeevnen.

Hvad er formålet med 'ClassLoader.loadClass()' API?

Typisk, hvis vi ønsker at instansiere et nyt objekt, skriver vi koden sådan her:

 new io.ycrash.DummyObject();

Du kan dog bruge ClassLoader.loadClass() API og også instansiere objektet. Sådan ser koden ud:

 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
 Class<?> myClass = classLoader.loadClass("io.ycrash.DummyObject");
 myClass.newInstance();

Du kan bemærke i linje #2 'classLoader.loadClass()' er påkaldt. Denne linje indlæser klassen 'io.ycrash.DummyObject' i hukommelsen. I linje #3 instansieres 'io.ycrash.DummyObject'-klassen ved hjælp af 'newInstance()' API.

Denne måde at instantiere objektet på er som at røre ved næsen med hånden ved at gå gennem nakken. Du undrer dig måske over, hvorfor nogen gør dette? Du kan kun instansiere objektet ved at bruge 'ny', hvis du kender navnet på klassen på tidspunktet for skrivning af koden. Under visse omstændigheder kender du måske kun navnet på klassen under løbetiden. Eksempel, hvis du skriver rammer (som Spring Framework, XML-parser, …), vil du kun kende klassenavnene, der skal instansieres under kørsel. Du vil ikke vide, hvilke klasser du vil instansiere, når du skriver koden. Under sådanne omstændigheder bliver du nødt til at ende med at bruge 'ClassLoader.loadClass()' API.

Hvor 'ClassLoader.loadClass()' bruges?

‘ClassLoader.loadClass()’ bruges i flere populære tredjepartsbiblioteker, JDBC-drivere, frameworks og applikationsservere. Dette afsnit fremhæver et par populære rammer, hvor 'ClassLoader.loadClass()' API bruges.

Apache Xalan

Når du bruger Apache Xalan framework til at serialisere og deserialisere XML, vil 'ClassLoader.loadClass()' API blive brugt. Nedenfor er stacktrace af en tråd, der bruger 'ClassLoader.loadClass()' API fra Apache Xalan frameworket.

at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
- locked <0x6d497769 (a com.wm.app.b2b.server.ServerClassLoader)
at com.wm.app.b2b.server.ServerClassLoader.loadClass(ServerClassLoader.java:1175)
at com.wm.app.b2b.server.ServerClassLoader.loadClass(ServerClassLoader.java:1108)
at org.apache.xml.serializer.ObjectFactory.findProviderClass(ObjectFactory.java:503)
at org.apache.xml.serializer.SerializerFactory.getSerializer(SerializerFactory.java:129)
at org.apache.xalan.transformer.TransformerIdentityImpl.createResultContentHandler(TransformerIdentityImpl.java:260)
at org.apache.xalan.transformer.TransformerIdentityImpl.transform(TransformerIdentityImpl.java:330)
at org.springframework.ws.client.core.WebServiceTemplate$4.extractData(WebServiceTemplate.java:441)
:
:

Google GUICE Framework

Når du bruger Google GUICE framework, vil 'ClassLoader.loadClass()' API blive brugt. Nedenfor er stacktrace af en tråd, der bruger 'ClassLoader.loadClass()' API fra Google GUICE frameworket.

at java.lang.Object.wait(Native Method)
-  waiting on hudson.remoting.RemoteInvocationHandler$RPCRequest@1e408f0
at hudson.remoting.Request.call(Request.java:127)
at hudson.remoting.RemoteInvocationHandler.invoke(RemoteInvocationHandler.java:160)
at $Proxy5.fetch2(Unknown Source)
at hudson.remoting.RemoteClassLoader.findClass(RemoteClassLoader.java:122)
at java.lang.ClassLoader.loadClass(ClassLoader.java:321)
-  locked hudson.remoting.RemoteClassLoader@15c7850
at java.lang.ClassLoader.loadClass(ClassLoader.java:266)
at com.google.inject.internal.BindingProcessor.visit(BindingProcessor.java:69)
at com.google.inject.internal.BindingProcessor.visit(BindingProcessor.java:43)
at com.google.inject.internal.BindingImpl.acceptVisitor(BindingImpl.java:93)
at com.google.inject.internal.AbstractProcessor.process(AbstractProcessor.java:56)
at com.google.inject.internal.InjectorShell$Builder.build(InjectorShell.java:183)
at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:104)
-  locked com.google.inject.internal.InheritingState@1c915a5
at com.google.inject.Guice.createInjector(Guice.java:94)
at com.google.inject.Guice.createInjector(Guice.java:71)
at com.google.inject.Guice.createInjector(Guice.java:61)
:
:

Oracle JDBC-driver

Hvis du bruger Oracle JDBC Driver, vil 'ClassLoader.loadClass()' API blive brugt. Nedenfor er stacktrace af en tråd, der bruger 'ClassLoader.loadClass()' API fra Oracle JDBC-driveren.

at com.ibm.ws.classloader.CompoundClassLoader.loadClass(CompoundClassLoader.java:482)
- waiting to lock 0xffffffff11a5f7d8> (a com.ibm.ws.classloader.CompoundClassLoader)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:170)
at oracle.jdbc.driver.PhysicalConnection.safelyGetClassForName(PhysicalConnection.java:4682)
at oracle.jdbc.driver.PhysicalConnection.addClassMapEntry(PhysicalConnection.java:2750)
at oracle.jdbc.driver.PhysicalConnection.addDefaultClassMapEntriesTo(PhysicalConnection.java:2739)
at oracle.jdbc.driver.PhysicalConnection.initializeClassMap(PhysicalConnection.java:2443)
at oracle.jdbc.driver.PhysicalConnection.ensureClassMapExists(PhysicalConnection.java:2436)
:

AspectJ-bibliotek

Hvis du bruger AspectJ-biblioteket, vil 'ClassLoader.loadClass()' API blive brugt. Nedenfor er stacktrace af en tråd, der bruger 'ClassLoader.loadClass()' API fra AspectJ frameworket.

:
:
at [email protected]/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
at [email protected]/java.lang.Class.forName0(Native Method)
at [email protected]/java.lang.Class.forName(Class.java:398)
at app//org.aspectj.weaver.reflect.ReflectionBasedReferenceTypeDelegateFactory.createDelegate(ReflectionBasedReferenceTypeDelegateFactory.java:38)
at app//org.aspectj.weaver.reflect.ReflectionWorld.resolveDelegate(ReflectionWorld.java:195)
at app//org.aspectj.weaver.World.resolveToReferenceType(World.java:486)
at app//org.aspectj.weaver.World.resolve(World.java:321)
 - locked java.lang.Object@1545fe7d
at app//org.aspectj.weaver.World.resolve(World.java:231)
at app//org.aspectj.weaver.World.resolve(World.java:436)
at app//org.aspectj.weaver.internal.tools.PointcutExpressionImpl.couldMatchJoinPointsInType(PointcutExpressionImpl.java:83)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.matches(AspectJExpressionPointcut.java:275)
at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:225)
:
:

Påvirkning af undersøgelse af ydeevne

Nu antager jeg, at du har tilstrækkelig forståelse for Java-klassens indlæsning. Nu er det tid til at studere dens præstationspåvirkning. For at lette vores undersøgelse oprettede jeg dette enkle program:

package io.ycrash.classloader;
 
 public class MyApp extends Thread {
    
   @Override
   public void run() {
       
       try {
          
          while (true) {
             
             ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
             Class<?> myClass = classLoader.loadClass("io.ycrash.DummyObject");
             myClass.newInstance();
          }      
       } catch (Exception e) {
          
       }
   }
    
   public static void main(String args[]) throws Exception {
       
       for (int counter = 0; counter < 10; ++counter) {
          
          new MyApp().start();
       }
   }
 }

Hvis du bemærker dette program, opretter jeg 10 tråde i main()-metoden.

Hver tråd går på en uendelig løkke og instansierer 'io.ycrash.DummyObject' i run()-metoden ved at bruge 'classLoader.loadClass()' API'et i linje#13. Det betyder, at 'classLoader.loadClass()' bliver kaldet gentagne gange igen og igen af ​​alle disse 10 tråde.

ClassLoader.loadClass() – BLOKEREDE tråde

Vi udførte ovenstående program. Mens programmet kørte, kørte vi open source yCrash-scriptet. Dette script fanger 360-graders data (tråd dump, GC log, heap dump, netstat, VMstat, iostat, top, kernel logs,...) fra applikationen. Vi analyserede den fangede tråddump ved hjælp af fastThread – et tråddumpanalyseværktøj. Tråddumpanalyserapport genereret af dette værktøj til dette program kan findes her. Tool rapporterede, at 9 tråde ud af 10 var i BLOKERET tilstand. Hvis en tråd er i BLOKERET tilstand, indikerer det, at den sidder fast for en ressource. Når det er i en BLOKERET tilstand, vil det ikke gå fremad. Det vil hæmme applikationens ydeevne. Du undrer dig måske - Hvorfor får det ovenstående simple program trådene til at gå ind i BLOKERET tilstand.

Ovenfor er uddraget fra tråddumpanalyserapporten. Du kan se, at 9 tråde ('Tråd-0', 'Tråd-1', 'Tråd-2', 'Tråd-3', 'Tråd-4', 'Tråd-5', 'Tråd-7', ' Thread-8', 'Thread-9') blokeres af 'Thread-6'. Nedenfor er stak-sporet af den ene BLOKEREDE tilstandstråd (dvs. Thread-9):

Thread-9
Stack Trace is:
java.lang.Thread.State: BLOCKED (on object monitor)
at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
- waiting to lock <0x00000003db200ae0> (a java.lang.Object)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at io.ycrash.classloader.MyApp.run(MyApp.java:13)
Locked ownable synchronizers:
- None

Du kan bemærke, at 'Thread-9' er BLOKERET på java.lang.ClassLoader.loadClass() metoden. Den venter på at få en lås på '<0x00000003db200ae0>'. Alle andre resterende 8 tråde, som er i BLOKERET tilstand, har også nøjagtig samme stacktrace.

Nedenfor er stak-sporet af 'Thread-6', der blokerer alle andre 9 tråde:

 Thread-6
 java.lang.Thread.State: RUNNABLE
 at java.lang.ClassLoader.findLoadedClass0(Native Method)
 at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:1038)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:406)
 - locked <0x00000003db200ae0> (a java.lang.Object)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
 at io.ycrash.classloader.MyApp.run(MyApp.java:13)
 Locked ownable synchronizers:
- None

Du kan bemærke, at 'Thread-6' var i stand til at erhverve låsen (dvs. '<0x00000003db200ae0>') og komme videre. Alle andre 9 tråde sidder dog fast og venter på at få denne lås.

Hvorfor bliver tråde BLOKERET, når ClassLoader.loadClass() påkaldes?

For at forstå, hvorfor tråde går ind i BLOCKED-tilstand, når de kalder 'ClassLoader.loadClass()'-metoden, bliver vi nødt til at se på dens kildekode. Nedenfor er kildekodeuddraget af ClassLoader.loadClass()-metoden. Hvis du gerne vil se den komplette kildekode for java.lang.ClassLoader, kan du henvise her:

  protected Class<?≶ loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?≶ c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                    :
                    :

I den fremhævede linje i kildekoden vil du se brugen af ​​'synkroniseret' kodeblok. Når en kodeblok er synkroniseret, vil kun én tråd få lov til at komme ind i den blok. I vores ovenstående eksempel forsøger 10 tråde at få adgang til 'ClassLoader.loadClass()' samtidigt. Kun én tråd får lov til at komme ind i den synkroniserede kodeblok, de resterende 9 tråde vil blive sat i BLOKERET tilstand.

Nedenfor er kildekoden til 'getClassLoadingLock()'-metoden, som returnerer et objekt, og hvorefter synkronisering sker.

  protected Object getClassLoadingLock(String className) {
   Object lock = this;
   if (parallelLockMap != null) {
      Object newLock = new Object();
      lock = parallelLockMap.putIfAbsent(className, newLock);
      if (lock == null) {
	lock = newLock;
      }
   }
   return lock;
}

Du kan bemærke, at 'getClassLoadingLock()'-metoden vil returnere det samme objekt hver gang for det samme klassenavn. dvs. hvis klassenavnet er 'io.ycrash.DummyObject' - vil det returnere det samme objekt hver gang. Således vil alle de 10 tråde få det samme objekt tilbage. Og på dette ene objekt vil synkronisering ske. Det vil sætte alle tråde i BLOKERET tilstand.

Hvordan løser man dette problem?

Dette problem skyldes, at klassen 'io.ycrash.DummyObject' indlæses igen og igen ved hver loop-iteration. Dette får trådene til at gå ind i BLOKERET tilstand. Dette problem kan kortsluttes, hvis vi kun kan indlæse klassen én gang under applikationens opstartstid. Dette kan opnås ved at ændre koden som vist nedenfor.

  package io.ycrash.classloader;
 
 public class MyApp extends Thread {
   
   private Class<?≶ myClass = initClass();
   
   private Class<?≶ initClass() {
      
      try {         
         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
         return classLoader.loadClass("io.ycrash.DummyObject");
      } catch (Exception e) {         
      }      
      
      return null;
   }
   
   @Override
   public void run() {
      
      while (true) {
      
         try {            
            myClass.newInstance();
         } catch (Exception e) {         
         }
     }
   }
   
   public static void main(String args[]) throws Exception {
      
      for (int counter = 0; counter < 10; ++counter) {
        
         new MyApp().start();
      }
   }
 }

At foretage denne kodeændring løste problemet. Hvis du nu ser 'myClass' initialiseres i linje # 5. I modsætning til den tidligere tilgang, hvor myClass blev initialiseret hver eneste loop-iteration, initialiseres myClass nu kun én gang, når tråden instansieres. På grund af dette skift i koden vil 'ClassLoader.loadClass()' API ikke blive kaldt flere gange. Det vil således forhindre tråde i at gå ind i BLOKERT tilstand.

Løsninger

Hvis din applikation også støder på dette problem med klasseindlæsning, så er her de potentielle løsninger til at løse det.

en. Prøv at se, om du kan kalde 'ClassLoader.loadClass()' API under applikationens opstartstid i stedet for kørselstid.

b. Hvis din applikation indlæser den samme klasse igen og igen under kørsel, så prøv kun at indlæse klassen én gang. Efter det tidspunkt skal du cache klassen og genbruge den, som vist i eksemplet ovenfor.

c. Brug fejlfindingsværktøjerne som fastThread, yCrash, … til at opdage, hvilket framework eller tredjepartsbibliotek eller kodesti, der udløser problemet. Tjek om frameworks har givet nogen rettelser i deres seneste version, hvis det er tilfældet, opgrader til den nyeste version.

Java tag