Java.lang.ClassLoader # loadClass () API 被第三方库、 JDBC 驱动程序、框架和应用服务器用来将 Java 类装载到内存中。应用程序开发人员不经常使用这个 API。但是当他们使用诸如‘ java.lang 这样的 API 时。ForName ()’或者‘ org.springframework.util。ForName ()’,他们内部称之为‘ java.lang。ClassLoader # loadClass ()’API。
在运行时不同线程之间频繁使用此 API 会降低应用程序的性能。有时它甚至会使整个应用程序无响应。在这篇文章中,让我们更多地了解这个 API 及其对性能的影响。
通常,如果我们想要实例化一个新对象,我们可以这样编写代码:
Java
new io.ycrash.DummyObject();
但是,您可以使用 ClassLoader.loadClass () API 并实例化该对象:
Java
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Class<?> myClass = classLoader.loadClass("io.ycrash.DummyObject"); myClass.newInstance();
您可以注意到第2行中调用了“ classLoader.loadClass ()”。这条线路将载入‘ io.ycrash。类转换为内存。在3号线上。DummyObject’类是使用‘ newInstance ()’API 实例化的。
这种实例化对象的方法就像用手触摸鼻子,通过你的脖子后面。你可能想知道为什么会有人这么做。只有在编写代码时知道类的名称时,才能使用“ new”实例化对象。在某些情况下,您可能只在运行时知道类的名称。例如,如果您正在编写框架(如 Spring Framework、 XML 解析器等) ,那么您将知道仅在运行时实例化的类名。在编写代码时,您不会知道将实例化哪些类。在这种情况下,您最终必须使用‘ ClassLoader.loadClass ()’API。
LoadClass ()用于几个流行的第三方库、 JDBC 驱动程序、框架和应用服务器。本节重点介绍一些使用“ ClassLoader.loadClass ()”API 的流行框架。
当使用 Apache Xalan 框架序列化和反序列化 XML 时,将使用‘ ClassLoader.loadClass ()’API。下面是使用 Apache Xalan 框架中的‘ ClassLoader.loadClass ()’API 的线程的堆栈跟踪。
Shell
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 框架时,将使用‘ ClassLoader.loadClass ()’API。下面是使用来自 Google GUICE 框架的‘ ClassLoader.loadClass ()’API 的线程的堆栈跟踪。
Shell
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,则将使用‘ ClassLoader.loadClass ()’API。下面是使用 Oracle JDBC 驱动程序中的‘ ClassLoader.loadClass ()’API 的线程的堆栈跟踪。
Shell
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 库,则将使用‘ ClassLoader.loadClass ()’API。下面是使用 AspectJ 框架中的‘ ClassLoader.loadClass ()’API 的线程的堆栈跟踪。
Shell
: :at java.base@11.0.7/java.lang.ClassLoader.loadClass(ClassLoader.java:522) at java.base@11.0.7/java.lang.Class.forName0(Native Method) at java.base@11.0.7/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) ::
现在我假设您已经对 Java 类装入有了足够的理解。现在是时候研究它的性能影响了。为了方便我们的学习,我创建了这个简单的程序:
Java
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(); } } }
如果您注意到这个程序,我正在 main ()方法中创建10个线程。
每个线程都进行一个无限循环,并实例化‘ io.ycrash。在 run ()方法中,使用第13行中的‘ classLoader.loadClass ()’API。这意味着‘ classLoader.loadClass ()’将被所有这10个线程反复调用。
我们执行了上述程序。当程序执行时,我们运行开源的 yCrash 脚本。该脚本从应用程序捕获360度数据(线程转储、 GC 日志、堆转储、 netstat、 VMstat、 iostat、 top、内核日志...)。我们使用 fastThread-一个线程转储分析工具分析了捕获的线程转储。这个工具为这个程序生成的线程转储分析报告可以在这里找到。工具报告10个线程中有9个处于 BLOCKED 状态。如果一个线程处于 BLOCKED 状态,则表示它被某个资源卡住了。当它处于阻塞状态时,它不会前进。它会影响应用程序的性能。您可能想知道-为什么上述简单的程序使线程进入 BLOCKED 状态?
图: 显示9个 BLOCKED 线程的传递图(由 fastThread 生成)
以上是线程转储分析报告的摘录。你可以看到9个线程(‘ Thread-0’,‘ Thread-1’,‘ Thread-2’,‘ Thread-3’,‘ Thread-4’,‘ Thread-5’,‘ Thread-7’,‘ Thread-8’,‘ Thread-9’)被‘ Thread-6’阻塞。下面是一个 BLOCKED 状态线程(即 Thread-9)的堆栈跟踪:
Shell
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
您可以注意到‘ Thread-9’在 java.lang 上是 BLOCKED。LoadClass ()方法。它正在等待获得“ < 0x00000003db200ae0 >”的锁定。处于 BLOCKED 状态的所有其他8个线程也具有完全相同的堆栈跟踪。
下面是“ Thread-6”的堆栈跟踪,它阻塞了所有其他9个线程:
Shell
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
您可以注意到,‘ Thread-6’能够获取锁(即‘ < 0x0000003db200ae0 >’)并进一步进行。但是,所有其他9个线程都在等待获得这个锁。
要理解为什么线程在调用‘ ClassLoader.loadClass ()’方法时进入 BLOCKED 状态,我们必须查看它的源代码。下面是 ClassLoader.loadClass ()方法的源代码摘录。如果你想看完整的 java.lang 源代码。ClassLoader,你可以在这里参考:
Java
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); } : :
在源代码的突出显示的行中,您将看到“ synized”代码块的用法。同步代码块时,只允许一个线程进入该块。在我们上面的示例中,有10个线程试图同时访问“ ClassLoader.loadClass ()”。只允许一个线程进入同步代码块,其余9个线程将进入 BLOCKED 状态。
下面是‘ getClassLoadingLock ()’方法的源代码,该方法返回一个对象并在其上发生同步。
Java
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; }
您可以注意到,‘ getClassLoadingLock ()’方法将每次为相同的类名返回相同的对象。例如,如果类名是‘ io.ycrash。DummyObject’-它每次都会返回相同的对象。因此,所有10个线程将返回同一个对象。在这个单一的对象上,同步将发生。它将把所有线程置于 BLOCKED 状态。
这个问题之所以出现是因为‘ io.ycrash’。DummyObject 的类在每次循环迭代中反复加载。这会导致线程进入 BLOCKED 状态。如果我们只能在应用程序启动时加载该类一次,那么这个问题可能会短路。这可以通过修改代码来实现,如下所示。
Java
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(); } } }
进行此代码更改解决了这个问题。如果你现在看到‘ myClass’在第5行被初始化。不同于以前的方法,在每次循环迭代中都初始化 myClass,现在 myClass 只在实例化 Thread 时初始化一次。由于代码中的这种变化,‘ ClassLoader.loadClass ()’API 将不会被多次调用。因此,它将防止线程进入 BLOCKED 状态。
如果您的应用程序也遇到这种类加载性能问题,那么下面是解决这个问题的潜在解决方案。
留言与评论(共有 0 条评论) “” |