本文介紹了Java:刪除JAR后,類仍被加載的處理方法,對(duì)大家解決問(wèn)題具有一定的參考價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧!
問(wèn)題描述
我試圖重現(xiàn)一個(gè)錯(cuò)誤,其中JAR被更新(通過(guò)Linux機(jī)器上的rsync),然后拋出NoClassDefFoundError
。更新的JAR沒(méi)有變化,但我在考慮這樣一個(gè)事實(shí),即文件在類加載時(shí)正在傳輸…
我現(xiàn)在正在嘗試復(fù)制該錯(cuò)誤。
我的應(yīng)用程序啟動(dòng)時(shí)只有一個(gè)JAR的類路徑(/opt/test/myjar.jar
)
其他JAR位于myjar.jar(/opt/test/lib/mylib.jar
)相同路徑下的目錄中。
該庫(kù)已注冊(cè)到myjar.jar
META-INF/MANIFEST.MF
,文本為
Manifest-Version: 1.0
Built-By: FB
Class-Path: lib/mylib.jar
現(xiàn)在我編寫(xiě)了一些等待幾秒鐘的代碼,然后使用Class.forName("mylib.MyClass")
加載一些類。
然后我將設(shè)置文件夾,啟動(dòng)Java運(yùn)行時(shí),然后刪除lib/mylib.jar
文件,并等待Class.forName
失敗。
并且代碼運(yùn)行正常。我期待的是NoClassDefFoundError
。然后我重新運(yùn)行代碼,拋出了一個(gè)NoClassDefFoundError
。
然后我將mylib.jar
讀取到lib
目錄,重新運(yùn)行,一切正常。
然后我使用-verbose:class
重新運(yùn)行代碼,刪除lib/mylib.jar
,然后出現(xiàn)此日志。
[Loaded mylib.MyClass from file:/opt/test/lib/mylib.jar`]
所以類加載是在JAR刪除之后發(fā)生的。我不明白這為什么管用。
并且以前未從lib/mylib.jar
加載任何其他類。
使用的JDK為OpenJDK Runtime Environment corretto-8.302.08.1(Build 1.8.0_302-B08)
我不明白JVM如何從我剛剛刪除的文件加載類。我認(rèn)為JVM可能會(huì)在某個(gè)地方緩存這些文件(可能是因?yàn)樗鼈冊(cè)?code>MANIFEST.MF中注冊(cè))。
有人知道這種行為嗎?
P。我用真正的JAR和類測(cè)試了這個(gè)過(guò)程。如果沒(méi)有人知道原因,我可以構(gòu)建一個(gè)測(cè)試項(xiàng)目。
推薦答案
您使用的系統(tǒng)沒(méi)有強(qiáng)制文件鎖定。例如,如果您在Windows下嘗試了相同的操作,則無(wú)法同時(shí)覆蓋或刪除.jar文件。
類路徑上的JAR文件在JVM啟動(dòng)時(shí)打開(kāi),并在運(yùn)行時(shí)保持打開(kāi)狀態(tài)。我們可以使用普通文件操作演示該行為:
Path p = Files.createTempFile(Paths.get(System.getProperty("user.home")),"test",".tmp");
try(FileChannel ch = FileChannel.open(p,
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
System.out.println("opened " + p);
int rc = new ProcessBuilder("rm", "-v", p.toString()).inheritIO().start().waitFor();
System.out.println("rm ran with rc " + rc);
int w = ch.write(StandardCharsets.US_ASCII.encode("test data"));
System.out.println("wrote " + w + " bytes into " + p);
ch.position(0);
ByteBuffer bb = ByteBuffer.allocate(w);
do ch.read(bb); while(bb.hasRemaining());
bb.flip();
System.out.println("read " + bb.remaining() + " bytes, "
+ StandardCharsets.US_ASCII.decode(bb));
}
System.out.println("closed, reopening");
try(FileChannel ch = FileChannel.open(p,
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
System.out.println("opened " + p);
}
catch(IOException ex) {
System.out.println("Reopening " + p + ": " + ex);
}
打印類似
的內(nèi)容
opened /home/tux/test722563514590118445.tmp
removed '/home/tux/test722563514590118445.tmp'
rm ran with rc 0
wrote 9 bytes into /home/tux/test722563514590118445.tmp
read 9 bytes, test data
closed, reopening
Reopening /home/tux/test722563514590118445.tmp: java.nio.file.NoSuchFileException: /home/tux/test722563514590118445.tmp
演示了在刪除之后,我們?nèi)匀豢梢詮囊呀?jīng)打開(kāi)的文件中寫(xiě)入和讀取數(shù)據(jù),因?yàn)橹挥袟l目已經(jīng)從目錄中刪除。JVM現(xiàn)在正在操作一個(gè)沒(méi)有名稱的文件。但是,一旦此文件句柄關(guān)閉,再次嘗試打開(kāi)它將失敗,因?yàn)楝F(xiàn)在它真的不見(jiàn)了。
然而,覆蓋該文件則是另一回事。打開(kāi)現(xiàn)有文件時(shí),我們?cè)L問(wèn)相同的文件并使更改可被察覺(jué)。
所以
Path p = Files.createTempFile(Paths.get(System.getProperty("user.home")),"test",".tmp");
try(FileChannel ch = FileChannel.open(p,
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
System.out.println("opened " + p);
int w = ch.write(StandardCharsets.US_ASCII.encode("test data"));
System.out.println("wrote " + w + " bytes into " + p);
int rc = new ProcessBuilder("cp", "/proc/self/cmdline", p.toString())
.inheritIO().start().waitFor();
System.out.println("cp ran with rc " + rc);
ch.position(0);
ByteBuffer bb = ByteBuffer.allocate(w);
do ch.read(bb); while(bb.hasRemaining());
bb.flip();
System.out.println("read " + bb.remaining() + " bytes, "
+ StandardCharsets.US_ASCII.decode(bb));
}
產(chǎn)生類似
的結(jié)果
opened /home/tux/test7100435925076742504.tmp
wrote 9 bytes into /home/tux/test7100435925076742504.tmp
cp ran with rc 0
read 9 bytes, cp/proc/
顯示了對(duì)已經(jīng)打開(kāi)的文件的read
操作導(dǎo)致了cp
寫(xiě)入的內(nèi)容,當(dāng)然,部分原因是緩沖區(qū)的大小預(yù)先調(diào)整到了Java應(yīng)用程序?qū)懭氲膬?nèi)容。這演示了當(dāng)一些數(shù)據(jù)已經(jīng)被讀取并且應(yīng)用程序嘗試根據(jù)它從舊版本中知道的來(lái)解釋新數(shù)據(jù)時(shí),覆蓋打開(kāi)的文件會(huì)如何造成破壞。
這產(chǎn)生了一種解決方案,可以在不使已經(jīng)運(yùn)行的JVM崩潰的情況下更新JAR文件。首先刪除舊的JAR文件,這會(huì)讓JVM在將新版本復(fù)制到相同位置之前,使用已經(jīng)打開(kāi)的、現(xiàn)在是私有的舊文件運(yùn)行。從系統(tǒng)的角度來(lái)看,您有兩個(gè)不同的文件。當(dāng)JVM終止時(shí),舊的將不復(fù)存在。替換后啟動(dòng)的JVM將使用新版本。
這篇關(guān)于Java:刪除JAR后,類仍被加載的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,