【问题标题】:Strange behavior with different java application export option不同 java 应用程序导出选项的奇怪行为
【发布时间】:2015-12-06 09:08:11
【问题描述】:

我有 java 服务器应用程序,它使用许多库(netty、guava 等)。我总是将此应用程序导出为一个 .jar。当我在 Eclipse 中运行应用程序时,我没有任何问题。但是,如果我在控制台中启动应用程序(Windows 或 Ubuntu,没关系),我会遇到奇怪的问题:所有通过套接字的连接进程持续时间过长。例如,通过 HttpAsync 或其他(rabbitmq 连接等)的简单 http 连接持续 1-2 分钟。但连接完成后,数据发送/接收速度很快。我想不出是什么问题。如前所述,我使用 Eclipse 进行开发。

如您所知,您可以通过 3 种不同的方式导出项目(在 Eclipse 中):

  1. 将所需的库提取到 JAR 中。
  2. 将所需的库打包到 JAR 中。
  3. 将所需的库复制到 JAR 旁边的子文件夹中。

所以,当我使用 2 选项时,我遇到了问题。当我切换到 3d 选项(主 .jar 附近的文件夹中的所有 .jar)时,问题就解决了。

通常 2 和 3 选项之间没有太大区别(在 2 个所有 .jar 中,一个罐子内)。我认为这是在从 jar 执行时加载新类所需的额外时间的原因。但问题不仅出现在开始时,而且出现在所有新连接上。

有人可以解释这种行为吗?

UPD: Eclipse Luna。无论我使用什么操作系统(Windows 或 Ubuntu),甚至使用什么 jvm(尝试使用不同的 Oracle jdk,甚至尝试打开 jdk)都无关紧要。

【问题讨论】:

标签: java eclipse maven jar jvm


【解决方案1】:

这都是关于打包到 JAR 与提取到 JAR 时的性能差异以及从 Eclipse 运行时与从控制台运行时的性能差异。

打包到 JAR 与提取到 JAR 时的性能差异:

将所需的库提取到 JAR 中:

它的作用:
在此选项中,Eclipse 将从引用的 JAR 中提取所有类并打包到生成的 JAR 中。

如果你打开JAR,你会发现没有被引用的JAR被打包,但是所有被引用的JAR的类都按照包的结构排列,然后在根级别打包在JAR中。与 “将所需的库打包到 jar 文件中”相比,这带来了性能上的关键差异,后者需要额外的运行时解析和在内存中加载 JAR 等成本。

当通过 Eclipse 导出为 JAR 时,如果考虑性能,这是最好的选择。这也是可扩展的选项,因为您可以发送此 JAR

MANIFEST.MF 在这个文件中主要要注意的是你的主类。当您运行 JAR 时,您将直接运行您需要的类。

Main-Class: com.my.jar.TestSSL


将所需的库打包到 JAR 中:

它的作用:
在这个选项中,Eclipse 将:

  • 将所有引用的 JAR 打包到生成的 JAR 中。
  • 通过org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader使用Eclipse的JAR加载机制,你还可以看到org.eclipse.jdt.internal.jarinjarloader包到你生成的JAR中,这个包就在生成的JAR的根目录下。

当然,这是您选择此选项时产生的额外成本,因为当您运行 JAR 时,执行的不是主类,而是将执行 JarRsrcLoader,这将加载您的主类和其他库,并且所有引用的库都被打包。请参阅下面的 MANIFEST.MF 部分

MANIFEST.MF 在这个文件中主要要注意的是你的主类。当您运行 JAR 时,JarRsrcLoader 将运行并做进一步的工作。

Rsrc-Main-Class: com.cgi.tmi.TestSSL
Main-Class: org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader


现在对于最后一个 Eclipse 导出选项 - “将所需的库复制到 JAR 旁边的子文件夹中”,我认为这不是一个非常可扩展的解决方案,因为这会强加您的文件系统依赖性,所以我会说不要这样做。

从 Eclipse 运行与从控制台运行时的性能差异:

当您从 Eclipse 运行应用程序时,它类似于第一个导出选项,其中 Eclipse 不需要在运行时解析和加载 JAR 等等。
然而这是一个非常微不足道的点,关键是考虑 Eclipse JAR 导出选项 1 和/秒选项 2。


最后的话:
  • 使用“将所需的库提取到 JAR”来导出 JAR,您将看到显着的性能提升。
    • 当您从控制台运行时,您的套接字连接不太可能持续很长时间,因为 JVM 运行代码然后从 Eclipse 和控制台运行时它会具有相同或非常相似的性能(考虑到两种情况下相同的 Java 版本)。您可能会因为打包的 JAR 性能而感到。尝试提取 JAR,你应该没问题。
  • 另外,请考虑您正在执行的日志记录量。运行时,根据配置,Eclipse 可能会屏蔽大量日志记录,从而节省您的 i/o 时间。
  • 了解classes are accessed from JAR class path 的作用,这就像从 JAR 中引用类时的额外计算成本。

【讨论】:

    【解决方案2】:

    发生这种情况是因为当您使用“uber jar”方法时,一些元数据可能会丢失。

    这只是一个例子,但如果你下载了thisthis,请查看jar 内部。同一个 META-INF 文件夹中有几个同名文件。

    那些文件可能很重要,当 eclipse 为你重新打包东西时,他可能在合并这些文件方面做得不好。

    这就是你可能发生的事情。

    【讨论】:

      【解决方案3】:

      在第二种方法中,您在 main.jar 中拥有所有依赖项 jar。 因此,除非需要,否则它不会加载任何依赖项 jar。 然而,在第 3 个选项的情况下,您的 main.jar 和其他依赖项 jar 是独立的(与第 2 种方式不同),因此会为连接加载并且可用。

      尝试通过操作依赖项 jar 添加日志语句或 syso 以查看此操作。

      【讨论】:

        【解决方案4】:

        由于我们不知道您的 JAR 的确切结构,这里是一个更一般的解释(假设您使用 java -jar your_app.jar 运行您的应用程序)。

        case 将所需的库复制到 JAR 旁边的子文件夹中。

        • 如果需要加载类,则类加载器(在运行时 JAR 之后)首先检查 your_app.jar 以找到所需的类
        • 如果找不到该类,则遍历子文件夹中的所有 JAR 文件
        • 所有 JAR 文件都可以保存在文件系统缓存中以供进一步阅读

        case 将所需的库打包到 JAR 中

        • 如果需要加载一个类,Eclipse 类加载器 JarRsrcLoader(在运行时 JAR 之后)首先检查 your_app.jar 以找到所需的类
        • 如果找不到该类,它会遍历所有嵌入的 JAR 文件,这意味着首先需要将它们从 your_app.jar 解压缩,然后才能读取内容
        • 提取的嵌入式 JAR 文件不会保存在文件系统缓存中以供进一步阅读(因为它们不是文件系统中的文件)

        如果您有大量的嵌入式库 JAR,这可能会导致类加载速度变慢(但仅在类加载器第一次加载类时)。

        如果你比较输出,你可以看到类加载的差异

        java -verbose:class -jar your_app_external_library_jars.jar
        

        java -verbose:class -jar your_app_embedded_library_jars.jar
        

        可以通过为每个 JAR 文件(例如 your_app.jar 和嵌入式库 JAR)生成一个 INDEX.LIST 文件来提高性能。

        【讨论】:

        • 但是类只加载了那些。那么,为什么我每次连接都有问题?
        • @Suvitruf 正如我所说的we don't know the exact structure of your JARsee the difference in the class loading if you compare the outpout ...。你这样做了吗?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多