我正在服务器上运行openjdk 11.0.3。每当服务器重新启动时(每晚):对于首次启动我的应用程序,用户必须等待35秒才能启动应用程序。 (在从主方法编写第一个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秒。然后只有1-2秒才能显示我的第一个JFrame。 重启后,使用java8(sun)启动我的应用程序平均要比第一个System.out.println花费16秒。但是然后我的第一个JFrame出现之前有25秒的延迟。 在已经使用java8启动之后,用java11启动我的应用程序平均需要4到6秒钟。
答案 0 :(得分:2)
您的应用程序可能正在缺少“类数据共享(CDS)存档”的困扰。这样的归档文件可以更快地加载标准类,并且由以前版本的某些安装程序默认生成,但是OpenJDK 11没有安装程序。
此问题由JEP 341解决:
当前,JDK映像在
lib
目录中包含在构建时生成的默认类列表。想要利用CDS的用户,即使仅使用JDK中提供的默认类列表,也必须运行java -Xshare:dump
作为额外的步骤。该选项已被记录,但是许多用户不知道它。
因此,尽管此JEP是关于JDK 12自动执行必要步骤的,但它也提到了JDK 11的修复:只需在命令行上运行java -Xshare:dump
一次即可生成档案。
请注意,您可以通过在CDS中包含应用程序类来进一步缩短启动时间。另请参见JDK 11文档的Class Data Sharing部分。
答案 1 :(得分:0)
我现在已经进行了广泛的测试,并且准备发布我的结果以及我提出的两种不同的“解决方案”。 首先,让我解释一下我的应用程序。这是一个摇摆的企业应用程序,它的使用始于13年前,并且自此以后一直得到扩展。 因此,该应用程序很大,可以做很多不同的事情(尽管大多数用户只使用其中的一部分),并且其类路径上有大约120个jar文件,包括所有第三方jar。 如前所述,重新启动服务器后,需要35秒才能显示我的第一个登录JFrame。
解决方案1: 这是我的第一个解决方案,不是慢启动的解决方案,而是用户不启动应用程序多个实例的解决方案。 我注意到,尽管我的应用程序在初次启动时非常慢,但其他应用程序却没有。 因此,一种解决方法是制作一个小的独立应用程序来显示初始屏幕,我在程序中以此开始:
splashProcess = Runtime.getRuntime().exec("javaw -jar splash.jar");
后来我只是用
杀死了它splashProcess.destroy();
请注意,如果我应该使用新的JFrame()创建启动画面,则通常需要35秒钟才能显示出来。
解决方案2: 在测试时,我发现可以删除所有的jar文件并将其复制回去,从而可以模拟重启。 除了减少测试时间外,我发现仅使用初始启动所需的4-5个jar文件启动应用程序非常快(尽管稍后会导致ClassNotFoundExceptions), 另外,我还可以尝试找出导致挂起的jar文件,方法是先将所有jar文件复制回去,然后再删除一个和一个。 但是,我发现这不是一个jar文件。每次我删除一些jar文件时,应用程序启动之前所花费的秒数都会稳步减少。 因此,问题似乎是我第一次在应用程序中调用new JFrame()时,java似乎在类路径中建立了某种索引或所有类的某种东西,尽管此时未使用它们。 我不知道为什么要这么做,但是这个过程花了一些时间,而classpath上有120个jar文件。 这导致我找到解决方案nr2。当我的应用程序现在启动时,我检查参数“ startSilent”。 如果存在,我的应用程序唯一要做的就是显示一个大小为0,0的新JDialog,然后调用System.exit(0);。 然后,我编写了一个脚本,该脚本使用“ startSilent”参数运行我的应用程序,该参数在用户登录时启动。 现在,如果用户登录到服务器并等待至少35秒钟,然后再启动我们的应用程序,则启动现在很快,因为该应用程序已经启动并退出了一次,因此“ classpath-index”或它具有的任何内容已建成。 如果用户在较短的时间后启动应用程序,则启动时间将减少静默脚本已运行了多长时间。 (而且脚本总是在桌面准备就绪之前启动,因此启动总是至少比以前快很多)。
这些是我的发现的结果。我希望其他人会发现它们有用,并且如果有人可以解释为什么我创建所谓的“ classpath-index”,那么我会受到欢迎。