在Swing应用程序启动期间首次调用JFrame构造函数需要很长时间(因为java.awt.Window())

我正在尝试使用Java Swing构建一个简单,轻量级且响应迅速的应用程序。 但是,当它启动时,在窗口(JFrame)出现之前会有明显的延迟(> 500ms)。

我已将其跟踪到java.awt.Window类的构造函数,该类是JFrame的祖先。

奇怪的是,构造函数只对第一次调用很慢。 如果我创建了多个JFrame对象,则构造函数在第一个对象上花费的时间约为600毫秒,但对于后续对象,通常测量为0毫秒。

这是一个简单的例子,在我的系统中,显示了第一个构造函数调用的显着延迟,但不是第二个:

public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { long start; start = System.currentTimeMillis(); JFrame frame1 = new JFrame(); System.out.println((System.currentTimeMillis() - start) + " for first JFrame."); start = System.currentTimeMillis(); JFrame frame2 = new JFrame(); System.out.println((System.currentTimeMillis() - start) + " for second JFrame."); } }); } 

具有典型输出:

 641 for first JFrame. 0 for second JFrame. 

如果我在JFrame对象之前添加此Window对象初始化:

 java.awt.Window window = new java.awt.Window(null); 

然后输出变为如下:

 578 for first Window. 47 for first JFrame. 0 for second JFrame. 

当我尝试使用Window,java.awt.Container的超类时,Window构造函数仍然需要很长时间才能执行(因此问题不会超出Window类)。

由于JFrame构造函数调用Window构造函数,上面似乎表明对Window构造函数的第一次调用很昂贵。

第一次调用构造函数需要花费这么长时间才会发生什么,我能做些什么呢? 有一些简单的解决方案还是Swing / AWT的基本问题? 或者它可能是我的系统/设置特有的问题?

我希望我的应用程序能够像MS Notepad一样快速(或几乎同样快)打开,并且,虽然我可以在记事本打开时将文本打印到控制台(如果我在第一次JFrame初始化之前放置代码),上述问题意味着在窗口可见之前几乎有一整秒的延迟。 我是否需要使用不同的语言或GUI框架来获得我追求的性能?


编辑 :如果我添加Thread.sleep(10000)作为run()的第一行,结果不会改变(它们只是在10秒后出现)。 这表明问题不是由某些异步启动代码引起的,而是由构造函数调用直接触发。

编辑2 :实现NetBeans Profiler可以在JRE类中进行概要分析,并发现大部分时间都花在初始化sun.java2d.d3d.D3DGraphicsDevice对象上(Window对象需要屏幕边界和插入),这是“用于Microsoft Windows平台的Direct3D加速渲染管道,默认启用“,在Java 6u10中引入。 可以通过将“-Dsun.java2d.d3d = false”属性传递给JVM来禁用它,这会将启动时间减少大约3/4,但我还不确定是否需要它(D3D)或者是否有其他方法可以让它加载更快。 如果我将该参数放在命令行上,则输出如下:

 0 for first Window 47 for first JFrame. 0 for second JFrame. 

我稍后深入挖掘后,我会回来清理这篇文章。

这个答案记录了我到目前为止所发现的内容。 如果有人有更多信息,请评论或发布答案。 我对简单地禁用Swing使用D3D并不完全满意,并且对其他解决方案持开放态度。

原因:D3D初始化

Swing使用Java2D API进行绘制,根据Java SE 7故障排除指南 ,Java2D使用一组渲染管道,“可以粗略地定义为渲染基元的不同方式”。 更具体地说,Java2D渲染管道似乎将跨平台Java代码连接到可支持硬件加速的本机图形库(OpenGL,X11,D3D,DirectDraw,GDI)。

在Java 1.6.0_10(又名6u10)中 ,基于Direct3D的“全硬件加速图形管道”被添加到Java2D for Windows中,以提高Swing和Java2D应用程序中的渲染性能(默认情况下启用)。

默认情况下,当在Windows系统上使用Java2D时,默认情况下启用此Direct3D管道和DirectDraw / GDI管道(我假设它们各自用于不同的事物)。

至少D3D库只在需要时加载和初始化,并且第一次构建Window(或Window后代)时调用的本机D3D初始化函数需要~500ms(对我而言)并导致报告的缓慢初始化和禁用D3D管道似乎删除了对此本机函数的调用,大大减少了启动时间。 (虽然我更喜欢延迟,预先计算,共享(跨不同的Java应用程序),或优化D3D初始化,我想知道其他语言是否会这么慢。)

当然,在大多数系统中,D3D初始化所花费的时间可以忽略不计,而且由于某些硬件或驱动程序问题,这只是我系统的一个问题,但我对此持怀疑态度(尽管如果是真的,那就是是一个容易修复)。


将跟踪细化到本机initD3D()

更详细(如果你不关心,请跳过以下段落),我使用Netbeans分析器和调试器来查找:

初始化JFrame(调用构造函数)时,将调用祖先类java.awt.Window的构造函数。 Window初始化其GraphicsConfiguration设备,该设备尝试检索默认屏幕设备,依此类推。 第一次发生这种情况时(初始化第一个Window或Window后代),屏幕设备不存在,因此它是构建的。 在这个过程中,sun.java2D.d3d.D3DGraphicsDevice类被初始化,并在其静态初始化块(参见() )中调用一个本机函数initD3D(),这需要很长的时间来执行(~500ms) )。

我能够找到D3DGraphicsDevice及其静态初始化块的源代码版本(我真的只是假设从这个源开始,initD3D()是什么让它的()花了这么长时间 – 我的探查器没有’似乎承认原生function – 但这是一个合理的猜测。


一种解决方法 – 为Java2D禁用D3D

根据Java2D“系统属性” 指南 (以及上述故障排除指南 ),可以通过使用-Dsun.java2d.d3d=false选项运行java来禁用D3D管道。 我认为这会禁用D3D但不能禁用DirectDraw,可以使用Dsun.java2d.noddraw=true禁用它(然后“所有操作都将使用GDI执行”),但这并没有显着改善初始化时间。

例如,我可能会使用如下命令来运行没有D3D的MyJar.jar:

 java -jar -Dsun.java2d.d3d=false MyJar.jar 

使用问题中的代码(初始化Window然后初始化2个JFrame对象),我得到如下结果:

 0 for first Window 47 for first JFrame. 0 for second JFrame. 

而不是像这样的结果:

 547 for first Window 31 for first JFrame. 0 for second JFrame. 

(请注意,时间以毫秒为单位,在Windows上使用System.currentTimeMillis()进行测量,我认为其分辨率大约为15到16毫秒。)


OpenGL与Direct3D

如果使用-Dsun.java2d.opengl=True选项,则使用OpenGL而不是Direct3D。 在我的系统上略有改进(OpenGL约为400ms,D3D为约500ms),但延迟仍然很明显。


其他延误

我注意到第一个JFrame对象的初始化,即使它不是第一个Window,也比后续JFrame对象的初始化花费更多的时间(记录为31到47毫秒对比0毫秒)。

分析表明这与第一个玻璃窗格(JPanel)的创建有关,并且最终似乎是由javax.swing.UIManager类和对象初始化代码中的外观和系统属性初始化/加载引起的。 它并不太重要,但确实解释了观察到的exception现象。

在我的实际程序中有点复杂(必须初始化更多的Swing组件),延迟似乎在Swing代码中更加分散,但我注意到了大量的本机类加载,“UI安装”(加载默认UI属性等),等等。 不幸的是,我认为这些问题还有很多工作要做(如果有,请说出来)。


结束思考

最后,只有那么多可以做到的事情,我必须认识到Swing和JVM在过去几年里已经走了多远。

第一个jframe的创建涉及加载swing库。 JVM不会加载查看import语句的库。 只有在第一次调用该库时才会加载库。

在这种情况下,frame1创建是第一个调用Swing库的语句。 在创建frame2实例时,已经加载了swing库,因此frame2的对象创建速度太快,甚至没有注意到一些时间的流逝。 因此它显示0。

这解释了为什么当你将Window语句加在两者之上时它显示578,47,0。 这是因为第一个语句需要时间来加载java.awt库。 第二个需要时间来加载swing库。 并且三分之一显示为0,因为其创建所需的库已经加载。

你甚至可以用这种方式测试它。 尝试用JPanel替换第二个JFrame创建,它仍然显示0。

我的猜测是它正在加载本机库。 如果是这种情况,那么你无能为力。

Mac OS X 10.5.8,Java 1.6.0_26。 考虑到JVM启动时间和重量级图形对等创建,并不出人意料。

第一个JFrame的247。
 0表示第二个JFrame。

附录:根据Java性能:启动时间一文Java Quick Starter可能在您的平台上可用。

加载所有swing类需要时间,然后加载本机awt库需要时间。 也许,类加载需要更多时间,因为如果你只是创建一个JLabel而不是第一个JFrame,它仍然需要更多时间。