本文介紹了Java:刪除JAR后,類仍被加載的處理方法,對大家解決問題具有一定的參考價值,需要的朋友們下面隨著小編來一起學習吧!
問題描述
我試圖重現一個錯誤,其中JAR被更新(通過Linux機器上的rsync),然后拋出NoClassDefFoundError
。更新的JAR沒有變化,但我在考慮這樣一個事實,即文件在類加載時正在傳輸…
我現在正在嘗試復制該錯誤。
我的應用程序啟動時只有一個JAR的類路徑(/opt/test/myjar.jar
)
其他JAR位于myjar.jar(/opt/test/lib/mylib.jar
)相同路徑下的目錄中。
該庫已注冊到myjar.jar
META-INF/MANIFEST.MF
,文本為
Manifest-Version: 1.0
Built-By: FB
Class-Path: lib/mylib.jar
現在我編寫了一些等待幾秒鐘的代碼,然后使用Class.forName("mylib.MyClass")
加載一些類。
然后我將設置文件夾,啟動Java運行時,然后刪除lib/mylib.jar
文件,并等待Class.forName
失敗。
并且代碼運行正常。我期待的是NoClassDefFoundError
。然后我重新運行代碼,拋出了一個NoClassDefFoundError
。
然后我將mylib.jar
讀取到lib
目錄,重新運行,一切正常。
然后我使用-verbose:class
重新運行代碼,刪除lib/mylib.jar
,然后出現此日志。
[Loaded mylib.MyClass from file:/opt/test/lib/mylib.jar`]
所以類加載是在JAR刪除之后發生的。我不明白這為什么管用。
并且以前未從lib/mylib.jar
加載任何其他類。
使用的JDK為OpenJDK Runtime Environment corretto-8.302.08.1(Build 1.8.0_302-B08)
我不明白JVM如何從我剛剛刪除的文件加載類。我認為JVM可能會在某個地方緩存這些文件(可能是因為它們在MANIFEST.MF
中注冊)。
有人知道這種行為嗎?
P。我用真正的JAR和類測試了這個過程。如果沒有人知道原因,我可以構建一個測試項目。
推薦答案
您使用的系統沒有強制文件鎖定。例如,如果您在Windows下嘗試了相同的操作,則無法同時覆蓋或刪除.jar文件。
類路徑上的JAR文件在JVM啟動時打開,并在運行時保持打開狀態。我們可以使用普通文件操作演示該行為:
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);
}
打印類似
的內容
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
演示了在刪除之后,我們仍然可以從已經打開的文件中寫入和讀取數據,因為只有條目已經從目錄中刪除。JVM現在正在操作一個沒有名稱的文件。但是,一旦此文件句柄關閉,再次嘗試打開它將失敗,因為現在它真的不見了。
然而,覆蓋該文件則是另一回事。打開現有文件時,我們訪問相同的文件并使更改可被察覺。
所以
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));
}
產生類似
的結果
opened /home/tux/test7100435925076742504.tmp
wrote 9 bytes into /home/tux/test7100435925076742504.tmp
cp ran with rc 0
read 9 bytes, cp/proc/
顯示了對已經打開的文件的read
操作導致了cp
寫入的內容,當然,部分原因是緩沖區的大小預先調整到了Java應用程序寫入的內容。這演示了當一些數據已經被讀取并且應用程序嘗試根據它從舊版本中知道的來解釋新數據時,覆蓋打開的文件會如何造成破壞。
這產生了一種解決方案,可以在不使已經運行的JVM崩潰的情況下更新JAR文件。首先刪除舊的JAR文件,這會讓JVM在將新版本復制到相同位置之前,使用已經打開的、現在是私有的舊文件運行。從系統的角度來看,您有兩個不同的文件。當JVM終止時,舊的將不復存在。替換后啟動的JVM將使用新版本。
這篇關于Java:刪除JAR后,類仍被加載的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,