ClassNotFoundException:是否减慢了您的JVM?
大多數(shù)Java開(kāi)發(fā)人員都熟悉臭名昭著且非常常見(jiàn)的java.lang.ClassNotFoundException 。 雖然通常已經(jīng)很好地了解了此問(wèn)題的根源(類路徑中缺少類/庫(kù),類加載器委派問(wèn)題等),但對(duì)整體JVM和性能的影響通常是未知的。 這種情況可能會(huì)對(duì)您的應(yīng)用程序響應(yīng)時(shí)間和可伸縮性產(chǎn)生重大影響。
部署了多個(gè)應(yīng)用程序的大型Java EE企業(yè)系統(tǒng)最容易遭受此類問(wèn)題的影響,因?yàn)樵谶\(yùn)行時(shí)會(huì)激活大量不同的應(yīng)用程序類加載器。 除非確定了明確的業(yè)務(wù)影響并實(shí)施了緊密的日志監(jiān)視,否則這將面臨面臨“未檢測(cè)到” ClassNotFoundException的風(fēng)險(xiǎn),結(jié)果是:持續(xù)的性能影響以及可能的JVM類加載IO和線程鎖爭(zhēng)用。
下面的文章和示例程序?qū)⒄f(shuō)明從客戶生產(chǎn)系統(tǒng)中發(fā)現(xiàn)的ClassNotFoundException的任何出現(xiàn)都應(yīng)予以認(rèn)真對(duì)待并Swift解決。
Java類加載:缺少鏈接以獲得最佳性能
對(duì)這個(gè)性能問(wèn)題的正確理解始于對(duì)Java類加載模型的正確了解。 ClassNotFoundException本質(zhì)上意味著JVM無(wú)法定位和/或加載特定的Java類,例如:
- Class.forName()方法
- ClassLoader.findSystemClass()方法
- ClassLoader.loadClass()方法
雖然應(yīng)用程序的類加載僅在JVM生命周期中(或通過(guò)動(dòng)態(tài)重新部署功能)發(fā)生一次,但某些應(yīng)用程序也依賴于動(dòng)態(tài)類加載操作。
無(wú)論如何,重復(fù)的有效和“失敗”類加載操作可能非常麻煩,特別是當(dāng)默認(rèn)JDK java.lang.ClassLoader本身嘗試加載過(guò)程時(shí)。 實(shí)際上,由于向后兼容性,默認(rèn)的JDK 1.7+行為將僅允許一次加載一個(gè)類,除非將類加載器標(biāo)記為“具有并行功能”。 請(qǐng)記住,即使同步僅在類級(jí)別完成,針對(duì)相同類名的重復(fù)類加載失敗仍將觸發(fā)線程鎖爭(zhēng)用,具體取決于您正在處理的Java線程并發(fā)級(jí)別。 回到JDK 1.6時(shí),情況最糟糕,在類加載器實(shí)例級(jí)別系統(tǒng)地完成了同步。
因此,諸如JBoss WildFly 8之類的Java EE容器正在使用它們自己的內(nèi)部并發(fā)類加載器來(lái)加載應(yīng)用程序類。 這些類加載器實(shí)現(xiàn)了更細(xì)粒度的鎖定,因此允許同時(shí)從類加載器的同一實(shí)例加載不同的類。 這也與最新的JDK 1.7+改進(jìn)保持一致,后者引入了對(duì)多線程自定義類加載器的支持,這也有助于防止某些類加載器出現(xiàn)死鎖情況。
話雖這么說(shuō),系統(tǒng)級(jí)類(例如java。*和Java EE容器模塊)的類加載仍然依靠默認(rèn)的JDK ClassLoader。 這意味著相同類名(例如ClassNotFoundException)的重復(fù)類加載失敗仍然會(huì)觸發(fā)嚴(yán)重的線程鎖爭(zhēng)用。 這正是我們將在本文的其余部分中重復(fù)和演示的內(nèi)容。
線程鎖爭(zhēng)用–問(wèn)題復(fù)制
為了重新創(chuàng)建和模擬此問(wèn)題,我們根據(jù)以下規(guī)范創(chuàng)建了一個(gè)簡(jiǎn)單的應(yīng)用程序:
- 一個(gè)JAX-RS(REST)Web服務(wù),對(duì)系統(tǒng)包級(jí)別“位于”的虛擬類名稱執(zhí)行Class.forName():
字符串className =“ java.lang.WrongClassName”;
類。 forName (className);
- JRE:HotSpot JDK 1.7 @ 64位
- Java EE容器: JBoss WildFly 8
- 負(fù)載測(cè)試工具: Apache JMeter
- Java監(jiān)控:JVisualVM
- Java并發(fā)故障排除: JVM線程轉(zhuǎn)儲(chǔ)分析
該仿真實(shí)際上與20個(gè)線程同時(shí)執(zhí)行JAX-RS Web服務(wù)。 每次調(diào)用都會(huì)生成ClassNotFoundException。 為了減少對(duì)IO的影響并僅關(guān)注類加載爭(zhēng)用,完全禁用了日志記錄。
現(xiàn)在,讓我們看看從30到60秒的運(yùn)行情況來(lái)看JVisualVM的結(jié)果。 我們可以清楚地看到很多BLOCKED線程正在等待獲取對(duì)象監(jiān)視器上的鎖。
對(duì)JVM線程轉(zhuǎn)儲(chǔ)的分析清楚地揭示了問(wèn)題所在:線程鎖爭(zhēng)用。 從執(zhí)行堆棧跟蹤中我們可以看到JBoss將類的加載委托給JDK ClassLoader ...為什么? 這是因?yàn)闄z測(cè)到我們錯(cuò)誤的Java類名稱是系統(tǒng)類路徑的一部分,例如java。*。 在這種情況下,JBoss將把加載委托給系統(tǒng)類加載器,從而觸發(fā)該特定類名稱的系統(tǒng)同步,并等待來(lái)自其他線程的服務(wù)員等待獲取鎖以加載相同的類名稱。
許多線程正在等待獲取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) ……………………..罪犯線程–默認(rèn)任務(wù)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) …………………………………現(xiàn)在,讓我們用標(biāo)記為“應(yīng)用程序”包的一部分的Java類替換我們的類名稱,然后在相同的負(fù)載條件下重新運(yùn)行測(cè)試。
String className = "org.ph.WrongClassName"; Class.forName(className);
如我們所見(jiàn),我們不再處理阻塞的線程……為什么呢? 讓我們看一下JVM線程轉(zhuǎn)儲(chǔ),以更好地理解這種行為變化。
"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) ……………….上面的執(zhí)行堆棧跟蹤非常揭示:
- 由于未檢測(cè)到Java類名稱是Java系統(tǒng)包的一部分,因此不會(huì)執(zhí)行ClassLoader委派,因此不會(huì)進(jìn)行同步。
- 由于JBoss認(rèn)為JDK 1.7+是“安全的” JDK,因此使用了ConcurrentClassLoader .performLoadClassUnchecked()方法,而不觸發(fā)任何對(duì)象監(jiān)視器鎖定。
- 沒(méi)有同步意味著不間斷的ClassNotFoundException錯(cuò)誤不會(huì)觸發(fā)線程鎖爭(zhēng)用。
仍然需要注意的是,盡管在這種情況下JBoss在防止線程鎖爭(zhēng)用方面做得很出色,但是由于與過(guò)多的JAR文件搜索操作相關(guān)的IO開(kāi)銷,重復(fù)的類加載嘗試仍會(huì)在一定程度上降低性能。加強(qiáng)了立即采取糾正措施的必要性。
最后的話
我希望您喜歡這篇文章,并且現(xiàn)在對(duì)由于過(guò)多的類加載操作而可能對(duì)性能產(chǎn)生的影響有了更好的了解。 盡管JDK 1.7和現(xiàn)代Java EE容器在類加載器相關(guān)的問(wèn)題(例如死鎖和線程鎖爭(zhēng)用)上進(jìn)行了重大改進(jìn),但仍然存在潛在的問(wèn)題場(chǎng)景。 因此,我強(qiáng)烈建議您密切監(jiān)視應(yīng)用程序的行為,記錄日志,并確保積極糾正與類加載器相關(guān)的錯(cuò)誤,例如java.lang.ClassNotFoundException和java.lang.NoClassDefFoundError 。
我期待您的意見(jiàn),并請(qǐng)與Java類加載器分享您的故障排除經(jīng)驗(yàn)。
翻譯自: https://www.javacodegeeks.com/2014/04/classnotfoundexception-is-it-slowing-down-your-jvm.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的ClassNotFoundException:是否减慢了您的JVM?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 安卓网页视频下载(安卓网页视频)
- 下一篇: 生育备案有什么用处?(生育备案有什么用)