5

我在服务器上运行 openjdk 11.0.3。每当服务器重新启动(每晚):对于我的应用程序的第一次初始启动,用户必须等待 35 秒才能启动应用程序。(在从 main 方法编写第一个 System.out.println 之前。)(尽管后续启动非常快)我尝试了以下选项来调试它:

-Xlog:class+load:file=classload.txt

以下是最重要的发现:

... 
[2.284s][info][class,load] jdk.internal.loader.URLClassPath$FileLoader source: jrt:/java.base
[5.032s][info][class,load] sun.security.rsa.RSASignature$SHA1withRSA source: jrt:/java.base
…
[5.051s][info][class,load] java.util.LinkedList$Node source: jrt:/java.base 
[8.121s][info][class,load] pos.LFChangeable source: file:/C:/Users/rho/AppData/Roaming/edapp/pos.jar
…
[8.135s][info][class,load] java.io.FileNotFoundException source: jrt:/java.base
[10.584s][info][class,load] sun.reflect.misc.ReflectUtil source: jrt:/java.base
…
[11.744s][info][class,load] java.security.NoSuchAlgorithmException source: jrt:/java.base
[34.853s][info][class,load] jdk.internal.logger.DefaultLoggerFinder source: jrt:/java.base

为什么在加载 java.security.NoSuchAlgorithmException 和 jdk.internal.logger.DefaultLoggerFinder 之间会挂起 23 秒?那么其他几秒钟的减速呢?

编辑:根据评论,我会澄清一些。这是一个 Windows rdp 服务器。实际上,它不止一台服务器,但问题仍然存在于所有服务器上。该应用程序是一个独立的应用程序。所以每天早上都会出现问题,因为登录启动应用程序的用户会在“没有任何反应”时尝试多次启动它。我现在尝试重新启动其中一台服务器很多次,这就是我发现的:

重新启动后使用 java11 启动我的应用程序在第一个 System.out.println 之前平均需要 40 秒。然后在我的第一个 JFrame 显示之前只有 1-2 秒。重新启动后使用 java8 (sun) 启动我的应用程序在第一个 System.out.println 之前平均需要 16 秒。但是在我的第一个 JFrame 显示之前,我会延迟 25 秒。在已经使用 java8 启动之后使用 java11 启动我的应用程序平均需要 4-6 秒。

4

2 回答 2

4

您的应用程序可能因缺少“类数据共享 (CDS) 存档”而受到影响。这样的存档可以更快地加载标准类,并且默认情况下已由以前版本的某些安装程序生成,但 OpenJDK 11 没有安装程序。

JEP 341解决了这个问题:

目前,JDK 映像在目录中包含一个在构建时生成的默认类列表lib。想要利用 CDS 的用户,即使只使用 JDK 中提供的默认类列表,也必须java -Xshare:dump作为额外步骤运行。此选项已记录在案,但许多用户不知道。

因此,虽然这个 JEP 是关于 JDK 12 自动执行必要的步骤,但它还提到了 JDK 11 的修复:只需java -Xshare:dump在命令行上运行一次,即可生成存档。

请注意,您可以通过在 CDS 中包含应用程序类来进一步缩短启动时间。另请参阅 JDK 11 文档的类数据共享部分。

于 2019-04-29T15:58:31.477 回答
0

我现在已经进行了广泛的测试,我准备发布我的结果以及我提出的两种不同的“解决方案”。首先,让我解释一下我的应用程序。它是一个摇摆的企业应用程序,它在 13 年前就开始了它的生命,并且从那以后一直在扩展。因此,这个应用程序很大,做了很多不同的事情(尽管大多数用户只使用了其中的一部分),并且在它的类路径上有大约 120 个 jar 文件,包括所有第三方 jar。如前所述,重新启动服务器后需要 35 秒才能显示我的第一个 login-JFrame。

解决方案1:这是我的第一个解决方案,不是解决慢启动的解决方案,而是解决用户不启动应用程序的多个实例的解决方案。我注意到虽然我的应用程序在第一次初始启动时非常慢,但其他应用程序却没有。因此,一种解决方法是制作一个小的独立应用程序来显示启动屏幕,我在我的程序中这样开始:

splashProcess = Runtime.getRuntime().exec("javaw -jar splash.jar");

后来我把它杀了

splashProcess.destroy();

请注意,如果我应该使用 new JFrame() 创建启动画面,通常需要 35 秒才能显示。

解决方案 2:在测试时,我发现我可以通过删除所有 jar 文件并将它们复制回来来模拟重启。除了减少测试时间之外,我发现仅使用初始启动所需的 4-5 个 jar 文件启动应用程序非常快(尽管这会导致稍后出现 ClassNotFoundExceptions),这也说明我可以尝试找出导致挂起的 jar 文件,首先将所有 jar 文件复制回来,然后再省略一个。但是,我发现这不是一个 jar 文件应该受到责备的。每次我删除一些 jar 文件时,应用程序启动前所需的秒数都会稳步减少一点。所以,问题似乎是我第一次在我的应用程序中调用 new JFrame(),java 似乎在类路径中构建某种索引或所有类的某种东西,尽管它们目前没有使用。我不知道它为什么这样做,但是这个过程需要相当长的时间,类路径上有 120 个 jar 文件。这导致我找到解决方案 nr 2。当我的应用程序现在启动时,我检查参数“startSilent”。如果存在,我的应用程序唯一要做的就是显示一个大小为 0,0 的新 JDialog,然后调用 System.exit(0); 然后,我制作了一个脚本,该脚本使用“startSilent”参数运行我的应用程序,该参数在用户登录时启动。现在,如果用户登录服务器并在启动我们的应用程序之前等待至少 35 秒,现在启动速度快如闪电,因为应用程序已经启动并退出了一次,因此“classpath-index”或其他任何内容都已构建。如果用户在较短的时间后启动应用程序,则启动时间会减少静默脚本已经运行的时间。(并且启动总是至少比以前快一点,因为脚本在桌面准备好之前启动)。

所以这些是我的发现的结果。我希望其他人会发现它们很有用,如果有人能解释为什么我称之为“类路径索引”的东西是按原样创建的,我会很受欢迎。

于 2019-05-14T06:36:39.917 回答