ClassNotFoundException:是否会减慢您的JVM?
大多數Java開發人員都熟悉臭名昭著且非常常見的java.lang.ClassNotFoundException 。 雖然通常已經很好地了解了此問題的根源(類路徑中缺少類/庫,類加載器委派問題等),但對整體JVM和性能的影響通常是未知的。 這種情況可能會嚴重影響您的應用程序響應時間和可伸縮性。
部署了多個應用程序的大型Java EE企業系統最容易遭受此類問題的影響,因為在運行時會激活大量不同的應用程序類加載器。 除非確定了明確的業務影響并實施了緊密的日志監視,否則這將面臨面臨“未檢測到” ClassNotFoundException的風險,從而導致:持續的性能影響以及可能的JVM類加載IO和線程鎖爭用。
下面的文章和示例程序將說明從客戶生產系統中發現的ClassNotFoundException的任何出現都應予以認真對待并Swift解決。
Java類加載:缺少鏈接以獲得最佳性能
對性能問題的正確理解始于對Java類加載模型的正確了解。 ClassNotFoundException本質上意味著JVM無法定位和/或加載特定的Java類,例如:
- Class.forName()方法
- ClassLoader.findSystemClass()方法
- ClassLoader.loadClass()方法
盡管應用程序類的類加載在JVM生命周期中(或通過動態重新部署功能)應該只發生一次,但某些應用程序也依賴于動態類加載操作。
無論如何,重復的有效和“失敗”類加載操作可能非常麻煩,特別是當默認JDK java.lang.ClassLoader本身嘗試加載過程時。 實際上,由于向后兼容性,默認的JDK 1.7+行為將僅允許一次加載一個類,除非將類加載器標記為“具有并行功能”。 請記住,即使同步僅在類級別完成,針對相同類名的重復類加載失敗仍將觸發線程鎖爭用,具體取決于您正在處理的Java線程并發級別。 回到JDK 1.6時,情況最糟糕,在類加載器實例級別系統地完成了同步。
因此,諸如JBoss WildFly 8之類的Java EE容器正在使用它們自己的內部并發類加載器來加載應用程序類。 這些類加載器實現了更細粒度的鎖定,因此允許從類加載器的同一實例中同時加載不同的類。 這也與最新的JDK 1.7+改進保持一致,后者引入了對多線程自定義類加載器的支持,這也有助于防止某些類加載器出現死鎖情況。
話雖這么說,系統級類(例如java。*和Java EE容器模塊)的類加載仍然依靠默認的JDK ClassLoader。 這意味著相同類名(例如ClassNotFoundException)的重復類加載失敗仍會觸發嚴重的線程鎖爭用。 這正是我們將在本文的其余部分中重復和演示的內容。
線程鎖爭用–問題復制
為了重新創建和模擬此問題,我們根據以下規范創建了一個簡單的應用程序:
- 一個JAX-RS(REST)Web服務,對系統包級別中“定位”的虛擬類名稱執行Class.forName():
字符串className =“ java.lang.WrongClassName”;
類。 forName (className);
- JRE:HotSpot JDK 1.7 @ 64位
- Java EE容器: JBoss WildFly 8
- 負載測試工具: Apache JMeter
- Java監控:JVisualVM
- Java并發故障排除: JVM線程轉儲分析
該仿真實際上與20個線程同時執行JAX-RS Web服務。 每次調用都會生成ClassNotFoundException。 為了減少對IO的影響并僅關注類加載爭用,完全禁用了日志記錄。
現在,讓我們看看從30到60秒的運行情況來看JVisualVM的結果。 我們可以清楚地看到很多BLOCKED線程正在等待獲取對象監視器上的鎖。
對JVM線程轉儲的分析清楚地揭示了問題所在:線程鎖爭用。 從執行堆棧跟蹤中我們可以看到JBoss將類的加載委托給JDK ClassLoader ...為什么? 這是因為檢測到我們錯誤的Java類名稱是系統類路徑的一部分,例如java。*。 在這種情況下,JBoss將把加載委托給系統類加載器,從而觸發該特定類名稱的系統同步,并等待來自其他線程的服務員等待獲取鎖以加載相同的類名稱。
許多線程正在等待獲取LOCK 0x00000000ab84c0c8…
"default task-15" prio=6 tid=0x0000000014849800 nid=0x2050 waiting for monitor entry [0x000000001009d000] java.lang.Thread.State: BLOCKED (on object monitor) at java.lang.ClassLoader.loadClass(ClassLoader.java:403) - waiting to lock <0x00000000ab84c0c8> (a java.lang.Object)// Waiting to acquire a LOCK held by Thread “default task-20” at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) at java.lang.ClassLoader.loadClass(ClassLoader.java:356) // JBoss now delegates to system ClassLoader.. at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:371) at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:119) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:186) at org.jboss.tools.examples.rest.MemberResourceRESTService.SystemCLFailure(MemberResourceRESTService.java:176) at org.jboss.tools.examples.rest.MemberResourceRESTService$Proxy$_$$_WeldClientProxy.SystemCLFailure(Unknown Source) at sun.reflect.GeneratedMethodAccessor15.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) ……………………..罪魁禍首線程–默認任務20
"default task-20" prio=6 tid=0x000000000e3a3000 nid=0x21d8 runnable [0x0000000010e7d000] java.lang.Thread.State: RUNNABLE at java.lang.Throwable.fillInStackTrace(Native Method) at java.lang.Throwable.fillInStackTrace(Throwable.java:782) - locked <0x00000000a09585c8> (a java.lang.ClassNotFoundException) at java.lang.Throwable.<init>(Throwable.java:287) at java.lang.Exception.<init>(Exception.java:84) at java.lang.ReflectiveOperationException.<init>(ReflectiveOperationException.java:75) at java.lang.ClassNotFoundException.<init>(ClassNotFoundException.java:82) // ClassNotFoundException! at java.net.URLClassLoader$1.run(URLClassLoader.java:366) at java.net.URLClassLoader$1.run(URLClassLoader.java:355) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:354) at java.lang.ClassLoader.loadClass(ClassLoader.java:423) - locked <0x00000000ab84c0e0> (a java.lang.Object) at java.lang.ClassLoader.loadClass(ClassLoader.java:410) - locked <0x00000000ab84c0c8> (a java.lang.Object) // java.lang.ClassLoader: LOCK acquired at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) at java.lang.ClassLoader.loadClass(ClassLoader.java:356) at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:371) at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:119) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:186) at org.jboss.tools.examples.rest.MemberResourceRESTService.SystemCLFailure(MemberResourceRESTService.java:176) at org.jboss.tools.examples.rest.MemberResourceRESTService$Proxy$_$$_WeldClientProxy.SystemCLFailure(Unknown Source) …………………………………現在,讓我們用標記為“應用程序”包的一部分的Java類替換我們的類名稱,然后在相同的負載條件下重新運行測試。
String className = "org.ph.WrongClassName"; Class.forName(className);
如我們所見,我們不再處理阻塞的線程……為什么呢? 讓我們看一下JVM線程轉儲,以更好地了解這種行為變化。
"default task-51" prio=6 tid=0x000000000dd33000 nid=0x200c runnable [0x000000001d76d000] java.lang.Thread.State: RUNNABLE at java.io.WinNTFileSystem.getBooleanAttributes(Native Method) // IO overhead due to JAR file search operation at java.io.File.exists(File.java:772) at org.jboss.vfs.spi.RootFileSystem.exists(RootFileSystem.java:99) at org.jboss.vfs.VirtualFile.exists(VirtualFile.java:192) at org.jboss.as.server.deployment.module.VFSResourceLoader$2.run(VFSResourceLoader.java:127) at org.jboss.as.server.deployment.module.VFSResourceLoader$2.run(VFSResourceLoader.java:124) at java.security.AccessController.doPrivileged(Native Method) at org.jboss.as.server.deployment.module.VFSResourceLoader.getClassSpec(VFSResourceLoader.java:124) at org.jboss.modules.ModuleClassLoader.loadClassLocal(ModuleClassLoader.java:252) at org.jboss.modules.ModuleClassLoader$1.loadClassLocal(ModuleClassLoader.java:76) at org.jboss.modules.Module.loadModuleClass(Module.java:526) at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:189) // JBoss now fully responsible to load the class at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:444) // Unchecked since using JDK 1.7 e.g. tagged as “safe” JDK at org.jboss.modules.ConcurrentClassLoader.performLoadClassChecked(ConcurrentClassLoader.java:432) at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:374) at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:119) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:186) at org.jboss.tools.examples.rest.MemberResourceRESTService.AppCLFailure(MemberResourceRESTService.java:196) at org.jboss.tools.examples.rest.MemberResourceRESTService$Proxy$_$$_WeldClientProxy.AppCLFailure(Unknown Source) at sun.reflect.GeneratedMethodAccessor60.invoke(Unknown Source) ……………….上面的執行堆棧跟蹤非常揭示:
- 由于未檢測到Java類名稱是Java系統包的一部分,因此不會執行ClassLoader委派,因此不會進行同步。
- 由于JBoss認為JDK 1.7+是“安全的” JDK,因此使用了ConcurrentClassLoader .performLoadClassUnchecked()方法,而不觸發任何對象監視器鎖定。
- 沒有同步意味著由于不間斷的ClassNotFoundException錯誤而沒有觸發線程鎖爭用。
仍然需要注意的是,盡管在這種情況下JBoss在防止線程鎖爭用方面做得很出色,但是由于與過多的JAR文件搜索操作相關的IO開銷,重復的類加載嘗試仍會在一定程度上降低性能。加強了立即采取糾正措施的需要。
最后的話
我希望您喜歡這篇文章,并且現在對由于過多的類加載操作而可能對性能產生的影響有了更好的了解。 盡管JDK 1.7和現代Java EE容器在類加載器相關問題(例如死鎖和線程鎖爭用)上帶來了很大的改進,但仍然存在潛在的問題場景。 因此,我強烈建議您密切監視應用程序的行為,記錄日志,并確保積極糾正與類加載器相關的錯誤,例如java.lang.ClassNotFoundException和java.lang.NoClassDefFoundError 。
我期待您的意見,并請與Java類加載器分享您的故障排除經驗。
翻譯自: https://www.javacodegeeks.com/2014/04/classnotfoundexception-is-it-slowing-down-your-jvm.html
總結
以上是生活随笔為你收集整理的ClassNotFoundException:是否会减慢您的JVM?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何查看linux电脑内存使用情况(li
- 下一篇: OpenShift Origin中的Ku