通过将自己的应用程序类添加到classes.jsa中加快应用程序启动速度。

35
为了加速JVM的启动时间,Sun开发人员决定在安装JVM期间为特定平台预编译标准运行时类是一个好主意。这些预编译类可以在如下位置找到:$JAVA_HOME\jre\bin\client\classes.jsa。我们公司正在开发一款Java独立应用程序,它有自己的JRE,因此将我们自己的应用程序类添加到这个jsa文件中,以加快应用程序启动时间,这将是一个绝妙的选择。
我不认为JSA文件是通过魔法创建的,那么:它是如何创建的?如何欺骗JVM将我的类合并进去?
编辑:我已经找到了以下信息:
classes.jsa文件是通过以下命令创建的:
java -Xshare:dump

可以在$JAVA_HOME/jre/lib/classlist中找到需要包含在转储中的类的列表。

我甚至成功地将自己的类添加到这里(并将它们添加到rt.jar中以便Java能够找到它们),并在classlist文件下面生成了自己的校验和。

最终的问题是:只有java、com.sun和org.w3c包中的类似乎被识别,如果我将相同的类留在它们原来的包中,它们就不会被加载。我在整个OpenJDK源代码中搜索了与此相关的指针,但似乎与保护域有关。如果有人对这个话题感兴趣并且足够了解,请为我添加一些指针以供进一步调查。


你能分享一下你是如何重新计算校验和的吗? - coppit
2
我在嵌入式Java的幻灯片中找到了答案——openjdk源代码树中有一个名为AddJsum的实用程序。http://www.oracle.com/technetwork/jp/ondemand/java/20110519-java-a-1-greg-400531-ja.pdf - coppit
谢谢,我可能会在有时间的时候尝试一下。 - Daniel
1
进一步回复@coppit,这是第61-62页,工具链接:http://hg.openjdk.java.net/jdk6/jdk6/jdk/file/tip/make/tools/src/build/tools - 13ren
@Daniel,这是一个可移植的解决方案吗? - Pacerier
@Pacerier:由于该文件在不同平台上包含不同的类,因此您无论如何都必须在应用程序的安装阶段创建它,因此该方法似乎是可移植的,但所创建的文件本身可能不是。 - Daniel
4个回答

12
您已经接近成功,只需要完成几个步骤即可。要将自己的类添加到clients.js中,需要按以下步骤进行:
  1. 您的类的合格名称(您已经有了)

  2. 这些类的类路径(您已经有了)

  3. 知道如何重新计算校验和(您已经有了)

  4. 转储新文件,并提供您现在正在与Java类一起预编译的类的类路径。

  5. 运行程序,提供与用于转储新classes.jsa的类路径相同的类路径。

要提供包含您要添加到类列表中的类的类路径,请使用-Xbootclasspath/a命令。它将附加目录/ JARs,当JVM搜索引导类所在的位置时。 classes.jsa的默认空间相当小,如果需要改进它,可以使用-XX:SharedReadWriteSize-XX:SharedReadOnlySize命令。 您的转储命令应该类似于此:

java -Xshare:dump -Xbootclasspath/a:C:/myfiles/directoryA/;C:/myfiles/directoryB/;C:/myJars/myJar.jar;

最后一步只需要正常运行Java应用程序,记得打开共享模式。您还需要添加与转储文件相同的Xbootclasspath参数。它看起来会像这样:
java myapp.java -Xshare:on -Xbootclasspath/a:C:/myfiles/directoryA/;C:/myfiles/directoryB/;C:/myJars/myJar.jar;

现在您在类列表中添加的每个类都会与在同一JVM中运行的其他实例共享。


太好了,我一定会去看看! - Daniel
我正在尝试这个并且失败了。我创建了一个新的classfiles文件并添加了校验和,并指定了-Xbootclasspath/a:/path/to/jars/*. 生成的classes.jsa文件与原始文件大小相同。是否可能像Daniel建议的那样,-Xshare:dump只会转储特定包中的类?我感兴趣的原因是我们使用了很多第三方库,这些库使我们的RSS内存使用率膨胀。如果能够像标准库一样在JVM之间共享这些库就太好了。(是的,我已经看到了链接问题中的警告。;)) - coppit
你是否正在用你修改过的版本替换classlist文件? - Daniel Pereira
@DanielPereira,默认情况下不是已经开启共享模式了吗? - Pacerier
1
@Pacerier 默认行为是 -Xshare:autoautoon 之间有区别:auto 会尝试以共享模式运行,但如果无法完成,则会关闭共享模式,然后正常执行应用程序。如果您定义了 on,它将强制共享模式执行,并使您的应用程序在出现问题时无法执行。 - Daniel Pereira

11

从Java 8u40(以及嵌入式Java 8u51)开始,Java现在支持应用程序类数据共享(AppCDS)(即您自己的类在共享存档中)。 在我们的嵌入式java上,我们发现启动时间提高了超过40%! 这对我们来说几乎没有工作量,相当不错...

https://blogs.oracle.com/thejavatutorials/entry/jdk_8u40_released


正是我想要的。可惜它只作为商业功能提供。但至少它似乎能够工作。 - Daniel
很遗憾,Oracle只在8u51版本的嵌入式中保留了AppCDS功能...我们一直在努力寻求关于它何时会从Oracle回归的答案...但是没有运气。我们得到了一个关于“哇-我们的产品团队很想知道您对此的体验”的部分答案,但是我们从未得到与他们联系的方式的回复。 - Ben

8
有趣的想法。然而,据我阅读,它用于在虚拟机之间共享数据和加速类加载,而不是编译。我不确定你能得到多少提升,但如果你已经有一个很大的启动延迟(尽管虚拟机已经试图减轻这种情况),那么也许值得一试。

关于自己尝试,似乎这个文件通常在安装Sun VM时创建,但您也可以控制它。一些细节在这篇较旧的Sun Java 5 Class Data Sharing文档中(您可能已经看过了?)。一些Sun Java 6文档也几次提到它, 但对文档没有太多补充。据说这原本是一个IBM VM功能。而且,为了继续链接转储,它在这篇文章中有一些解释。

我个人对此不太了解,所以不知道如何控制它。您可以重新生成它,但我认为它并不适合您放置自定义内容。此外,即使您可以“欺骗”它,那也可能违反某种Sun/Oracle许可证(例如,您不能干扰rt.jar并进行再分发)。而且,说了这么多,除非您的应用程序中有成千上万的类,否则我怀疑您是否会看到启动时间的显着提高? (这并不是一个真正的答案,我知道,但它太大了,无法适合评论,并且我发现这个问题很有趣,所以我进行了一些调查,并在这里放置了链接,以防有人发现相同的信息有用。)

非常感谢您的巨大努力。我会仔细阅读您提供的链接,并希望能够回来找到一个可行的解决方案。此外,我不知道我不能随意更改rt.jar并重新分发它,但无论如何我都不会在意,因为唯一使用它的应用程序将是我的。 - Daniel

0

需要一些思考,但我已经成功地运行了4个Java8虚拟机(版本1.8.0_162),并使用共享类。以下脚本用于设置和测试共享,稍加修改即可在其他地方使用:

#!/bin/bash

# Libraries to load
LIBS1="./lib/protobuf-java-2.6.1.jar:\
./lib/jetty-server-9.2.18.v20160721.jar:./lib/jetty-util-9.2.18.v20160721.jar:./lib/servlet-api-3.1.jar:./lib/jetty-http-9.2.18.v20160721.jar:./lib/jetty-io-9.2.18.v20160721.jar:\
./lib/derby.jar:\
./lib/json-simple-1.1.1.jar:"
LIBS2=":./lib/GTFS.jar"

# Uncomment these lines for the first phase where you are determining the classes to archive. During this phase aim to get as many classes loaded as possible
# which means loading a schedule and retrieving the stop list and next vehicle information
#
#APPCDS="-Xshare:off -XX:+UnlockCommercialFeatures -XX:+UseAppCDS -XX:DumpLoadedClassList=../GtfsAppCds.lst"
#java -Xmx512m $APPCDS -Dderby.system.home=database -classpath $LIBS1$LIBS2 com.transitrtd.GtfsOperatorManager

# Uncomment these lines when the class list is created and run to create the shared archive. Classes marked as unverifiable will need to be removed from the
# archived class list in GtfsAppCds.lst and the lines below run again. LIBS2 above contains jars which are left out of the archive. These are jars which change
# frequently and would therefore cause the archive to be frequently rebuilt.
#                                                                                                                                                                                                               
#APPCDS="-Xshare:dump -XX:+UnlockCommercialFeatures -XX:+UseAppCDS -XX:SharedClassListFile=../GtfsAppCds.lst -XX:SharedArchiveFile=../GtfsAppCds.jsa"
#java -Xmx512m $APPCDS -classpath $LIBS1

# Uncomment these lines when wishing to verify the application is using the shared archive.
#
#APPCDS="-Xshare:on -XX:+UnlockCommercialFeatures -XX:+UseAppCDS -XX:SharedArchiveFile=../GtfsAppCds.jsa -verbose:class"
#java -Xmx512m $APPCDS -Dderby.system.home=database -classpath $LIBS1$LIBS2 com.transitrtd.GtfsOperatorManager

请注意,共享存档文件(即jsa文件)是与架构相关的,并且需要在每个目标平台类型上进行构建。
此外,如果一个jar使用密封包,会抛出安全异常,请参见。

https://docs.oracle.com/javase/tutorial/deployment/jar/sealman.html

有关封装包的信息,请参阅。这在上面的情况下是适用的,例如derby.jar,但可以通过解压缩jar文件,将清单中的Sealed:true替换为Sealed:false来解决问题。

使用旧版本的Java构建的jar文件无法在共享存档中使用,在上述情况中,需要将derby版本从10.10升级到10.14以获得好处。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接