简体   繁体   English

Java:jar被移除后,class仍然加载

[英]Java: after jar is removed, class is still loaded

I was trying to reproduce a bug where, a jar is updated (via rsync on a linux box) and then a NoClassDefFoundError was thrown.我试图重现一个错误,其中更新了 jar(通过 linux 框上的 rsync),然后引发了NoClassDefFoundError The updated jar was unchanged, but I was thinking about the fact the file was transferring while the class was loading...更新后的 jar 没有改变,但我在考虑在 class 加载时文件正在传输的事实......

I'm now trying to reproduce the bug.我现在正在尝试重现该错误。

My application start with a classpath of only one jar ( /opt/test/myjar.jar )我的应用程序的类路径只有一个 jar ( /opt/test/myjar.jar )

The others jar are inside a directory on the same path of myjar.jar ( /opt/test/lib/mylib.jar ).其他 jar 位于 myjar.jar ( /opt/test/lib/mylib.jar ) 同一路径上的目录中。

The library is registered into the myjar.jar META-INF/MANIFEST.MF with this text该库已注册到myjar.jar META-INF/MANIFEST.MF与此文本

Manifest-Version: 1.0
Built-By: FB
Class-Path: lib/mylib.jar

Now I scripted some code for waiting some seconds, then load some class with Class.forName("mylib.MyClass") .现在我编写了一些代码等待几秒钟,然后用Class.forName("mylib.MyClass")加载一些 class 。

Then I will setup the folder, start the java runtime and then delete the lib/mylib.jar file, and wait for the Class.forName to fail.然后我将设置文件夹,启动 java 运行时,然后删除lib/mylib.jar文件,并等待Class.forName失败。

And the code was running fine.并且代码运行良好。 I was expecting a NoClassDefFoundError .我期待一个NoClassDefFoundError Then I rerun the code, and a NoClassDefFoundError was thrown.然后我重新运行代码,抛出了NoClassDefFoundError

Then I readded the mylib.jar to the lib directory, rerun, all ok.然后我将mylib.jarlib目录,重新运行,一切OK。

Then I rerun the code with -verbose:class , deleted the lib/mylib.jar and then this log appeared.然后我用-verbose:class重新运行代码,删除了lib/mylib.jar然后出现了这个日志。

[Loaded mylib.MyClass from file:/opt/test/lib/mylib.jar`]

So the class loading was happening after the jar deletion.所以 class 加载发生在 jar 删除之后。 I don't understand why this work.我不明白为什么会这样。 And no other classes were loaded from lib/mylib.jar before.并且之前没有从lib/mylib.jar加载其他类。

Jdk used is OpenJDK Runtime Environment Corretto-8.302.08.1 (build 1.8.0_302-b08)使用的 jdk 是 OpenJDK Runtime Environment Corretto-8.302.08.1 (build 1.8.0_302-b08)

I don't understand how the JVM can load a class from a file I just deleted.我不明白 JVM 如何从我刚刚删除的文件中加载 class 。 I think maybe the JVM cache those files somewhere (mabye cause they are registered inside the MANIFEST.MF ).我想也许 JVM 将这些文件缓存在某处(可能是因为它们已在MANIFEST.MF中注册)。

Have anyone an idea of this behavior??有人知道这种行为吗?

Ps.附言。 I tested this exact procedure, but with real jar and classes.我测试了这个确切的过程,但使用了真正的 jar 和类。 If no one have an idea on the why, I can build an test project.如果没有人知道原因,我可以构建一个测试项目。

You are using a system without mandatory file locking.您正在使用没有强制文件锁定的系统。 If you tried the same under Windows, for example, you couldn't do neither, overwrite nor delete the.jar file.例如,如果您在 Windows 下尝试了相同的操作,则既不能覆盖也不能删除 .jar 文件。

The jar files on the class path are opened when the JVM starts and kept open during the runtime. JVM 启动时打开 class 路径上的 jar 文件,并在运行时保持打开状态。 We can demonstrate the behavior using ordinary file operations:我们可以使用普通的文件操作来演示该行为:

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);
}

prints something like打印类似的东西

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

demonstrating that after removing, we can still write and read data from the already opened file, as only the entry has been removed from the directory.证明删除后,我们仍然可以从已打开的文件中写入和读取数据,因为只有条目已从目录中删除。 The JVM is now operating on a file without a name. JVM 现在正在对没有名称的文件进行操作。 But as soon as this filehandle has been closed, trying to open it again will fail, as now it is really gone.但是一旦这个文件句柄被关闭,试图再次打开它就会失败,因为现在它真的消失了。


Overwriting the file, however, is a different thing.然而,覆盖文件是另一回事。 When opening the existing file, we access the same file and make changes perceivable.打开现有文件时,我们访问同一个文件并使更改变得可感知。

So所以

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));
}

produces something like产生类似的东西

opened /home/tux/test7100435925076742504.tmp
wrote 9 bytes into /home/tux/test7100435925076742504.tmp
cp ran with rc 0
read 9 bytes, cp/proc/

Showing that the read operation on the already open file resulted in what cp wrote, partially of course, as the buffer was pre-sized to what the Java application wrote.表明对已打开文件的read操作导致了cp写入的内容,当然,部分原因是缓冲区的大小已预先设置为 Java 应用程序写入的内容。 This demonstrates how overwriting an open file can cause havoc when some data has been read already and the application tries to interpret the new data according to what it knows from the old version.这演示了当一些数据已经被读取并且应用程序尝试根据它从旧版本中知道的内容来解释新数据时,覆盖打开的文件会如何造成破坏。


This leads to a solution for updating a jar file without crashing the already running JVM.这导致了一种更新 jar 文件的解决方案,而不会使已经运行的 JVM 崩溃。 Delete the old jar file first, which lets the JVM run with the already opened, now-private old file, before copying the new version to the same location.首先删除旧的 jar 文件,这让 JVM 可以使用已经打开的、现在为私有的旧文件运行,然后将新版本复制到同一位置。 From the system's perspective, you have two different files then.从系统的角度来看,您有两个不同的文件。 The old one will cease to exist when the JVM terminates.当 JVM 终止时,旧的将不复存在。 JVMs started after the replacement will use the new version.替换后启动的 JVM 将使用新版本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 从动态加载的jar文件中加载具有超类的Java类 - Load Java class with super class from a dynamically loaded jar file 如何验证Java中的ClassLoader是否已加载.jar或类 - How to verify that a .jar or a class has been already loaded by the ClassLoader in Java 签署jar后,Java applet仍然出现安全错误 - Java applet still getting security error after signing jar 片段删除后的代码仍在执行 - Code after fragment is removed still executing java数据库功能已删除-仍在执行 - java database function removed - still executing 为什么里面有jar文件 <JAVA_HOME> / jre / lib目录未由引导程序类加载器加载 - Why jar file inside <JAVA_HOME>/jre/lib directory not loaded by bootstrap class loader 如何获取 jar 的名称,JVM 在运行时从中加载 class - How to get name of jar from which class is loaded at runtime by JVM in Java 11 在删除SQLite数据库JAR之后,为什么java.sql.DriverManager.getConnection()仍然可以工作? - Why is java.sql.DriverManager.getConnection() still working after removing the SQLite database JAR? 如何在Java文件中指定相对文件路径,以便在将文件放入jar文件后它仍然可以工作? - How to specify relative file path in Java file so that it can still work after the file is put in jar file? 在jar中覆盖java类 - overwrite java class in jar
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM