在运行时更新JAR

时间:2011-06-25 11:03:20

标签: java jvm classpath classloader

如果jar在JVM中运行,则可以卸载当前运行的Jar并将其从系统中删除。下载一个新版本并使用与最后一个Jar相同的名称重命名它,然后初始化新的Jar,在JVM中创建Jar的无缝更新。甚至可以指示JVM执行此操作吗?甚至可以在运行时更新Jar吗?

9 个答案:

答案 0 :(得分:24)

  

下载新版本并重命名   与最后一个Jar和同名的   然后初始化新的Jar,创建   Jar内的无缝更新   JVM ......甚至可能   在运行时更新Jar?

JAR文件未“正在运行”,JVM正在运行。您的JAR文件只包含使JVM执行有用工作的类信息(也称为字节代码指令)。在大多数情况下,JVM实际上不会对您的JAR文件进行系统锁定,因此您可以将该文件替换为您的内容。

真正的问题当然是,一旦JVM加载你的JAR,无论你多少次覆盖它,它都会愉快地携带它加载的内容并且永远不会再次从你的JAR文件中读取。这是默认类加载器的行为,无法更改 - 但正如其他人指出的那样 - 您不必使用默认的类加载器。您可以实现自己的,类似于Web应用程序服务器使用的,以便从文件系统加载更新的JARS。但要注意的是 - 定义自己的类加载器被认为是'坏主意',除非你真的知道你在做什么。您可以阅读更多herehere

答案 1 :(得分:21)

这是我以前见过的很多次(也是我自己做过的)。我列出了可能出现的问题/解决方案的一些要点。

  • 如果您覆盖稍后将使用的JAR文件,JVM将因转储而崩溃。
    • 到了后来我的意思是课程被非常懒惰地加载,有些可能只会在你的程序生命的后期加载
    • JVM有一个JAR文件的打开句柄,当jAR和指针出错时,lib会失败
    • 可以通过从JAR文件中预加载所有类和资源来降低概率
    • 如果您有自定义类加载器,则可以自行关闭手柄。
  • 您需要了解如何完成类加载。更好的是控制。
    • 一个自定义类加载器,它将为每个JAR创建一个类加载器并管理版本控制
    • 了解您的应用程序如何使用类加载器以及它如何对新JAR起作用(例如,检查Tomcat在覆盖WAR归档时的作用)
  • 在Windows上,您的JAR文件将被锁定,您无法覆盖它们。如果您处于控制之中,那么您可以在使用后解锁它们(关闭它们)。对于第三方系统,您必须找到相应的标志。例如,您可以在Tomcat上下文配置中检查 antiJARLocking
  • 总是更好地避免覆盖同一个文件,而是进行一些版本控制

总而言之,当您想要实现JAR重新加载时,可能会遇到许多问题。幸运的是,如何最大限度地降低风险。最安全的方法是做类似的事情以获得相同的效果。 Cleanest是自定义类加载器和JAR文件版本。

答案 2 :(得分:1)

一般情况下,你不能这样做,因为据我所知,这种行为尚未正式定义。

但是,您可以使用正式类路径中的jar文件 创建一个类加载器,然后根据需要从中加载类。通过丢弃由类加载器加载的所有类的实例,您可以删除当前资源,然后在新的jar文件上实例化一个新的类加载器,然后加载新类并创建新对象。

这很复杂,所以你可能会改为将jar作为OSGi模块并通过OSGi-loader调用你的程序?

答案 3 :(得分:1)

您无法写入正在运行的jar。用于写入的getResourceInputStream没有等价物。我想如果你尝试使用FileOutputStream编写,当JVM使用它时,你将无法删除它,因为系统会阻止它。

无论如何,仍然可以在不同的罐子里提供不同模块的更新。所以你可以想象有一个应用程序的主jar文件可以通过一个包含更新程序的小的独立可运行jar文件进行更新。

还可以使用JNLP自动更新应用程序。

服务器端应用程序也是隐藏用户更新的替代方法。

此致  斯特凡

答案 4 :(得分:1)

答案在于Java类加载器。这些人从JARS,.class文件或byte[]值或URL或其他任何东西加载类。无论何时访问类,都隐式使用类加载器为您提供正确的类实例。

创建一个您选择的类加载器,只需在需要“刷新”类时切换类加载器。看看Thread.setContextClassLoader方法 - 这将改变Thread的类加载器。

定义自己的类加载器非常简单 - 只需继承ClassLoader类并覆盖其findClass method

答案 5 :(得分:1)

请注意汤姆哈伯德的评论。 如果某个类尚未在 Runtime 中使用(因此未加载),如果您在新 JAR 中删除该类 - 您会遇到一些问题。

例如,如果您将执行以下代码:

System.Text.Json

如果您将上述内容放入 Jar 并执行它,并且在 30 秒过去之前,您将更改 Jar,使得类“Me1”根本不包含“method1”(当然您将删除标有“###”的行),执行 30 秒后会出现异常:

    public static void main(String[] args) throws InterruptedException {
        long startTime = System.currentTimeMillis();
        while (true) {
            long now = System.currentTimeMillis();
            long delta = (now - startTime);
            System.out.println("Running... delta is " + delta);
            if (TimeUnit.MILLISECONDS.toSeconds(delta) > 30) {
                Me1 m1 = new M1();
                me1.method1(); // ###
            }
            Thread.sleep(1000);
        }
    }

答案 6 :(得分:0)

您可以使用HotswapAgent实现它。它支持一些广泛使用的框架的插件,也有助于编写新的自定义插件

HotswapAgent-https://github.com/HotswapProjects/HotswapAgent

答案 7 :(得分:0)

在我的软件套件中,它是一个复杂的模块化客户端网格,链接到一个进行长时间间隔摄影的中央服务器,它添加了更新软件的功能。

byte[] file = recieve();

FileOutputStream fos = new FileOutputStream("software.jar");
fos.flush(); fos.write(file); fos.close();

此过程完成后,客户端需要执行一系列步骤才能重新启动。在这些过程中,它们包括长时间的线程休眠,文件读取和写入以及网络交互。

我没有查明可能是或不是什么错误,但是,在某些情况下,我发现hs_err_pid.log导致崩溃。

我还有一个变量final和static,称为“ SOFTWARE_VERSION”。我确认该变量确实可以更新(从服务器界面观察时),而在更换软件后无需重新启动软件。

但是,经过慎重考虑,我决定在软件更新后立即重新启动计算机(此程序将在启动时执行)。由于观察到更新的完整性不可靠,因此我发现最好重新开始。可能(未经测试)执行以下操作:

Runtime.getRuntime().exec("sudo java -jar software.jar");
System.exit(0);

但是,我不知道那将有多可靠。您还可以尝试运行以下内容:

Runtime.getRuntime().exec("run_software.sh")
System.exit(0);

然后在run_software.sh中:

sleep 1000
sudo java -jar software.jar

我想知道是否行得通。

希望这会有所帮助!

答案 8 :(得分:0)

如果JVM重启允许

正如人们所提到的,JVM 进程将打开 jar 文件的文件句柄,直到进程生命周期结束。如果 Linux 是您的目标操作系统,那么您可以执行以下技巧。

  1. 使用 unlink(2) 或等效方法删除原始 JAR 文件。路径将立即从文件系统元数据中删除,但物理文件数据将保留直到最后一个文件句柄关闭,因此不会发生损坏。
  2. 将新文件写入相同的路径。即使路径相同,实际数据也会转到另一个块,所以再说一遍 - 没有数据损坏,一切都是 100% 合法的。
  3. 使用与启动原始进程相同的命令行重新启动 JVM 进程。

如果JVM重启不允许

然后更新类的唯一方法是 - 即使使用重新定义的类加载器 - 强制 JVM 首先卸载类。这里的问题是 JVM 只会在垃圾收集类加载器时这样做。使用标准实现,几乎无法控制该过程。