【问题标题】:First call to JFrame constructor takes a long time during Swing application startup (because of java.awt.Window())在 Swing 应用程序启动期间首次调用 JFrame 构造函数需要很长时间(因为 java.awt.Window())
【发布时间】:2011-08-10 05:19:58
【问题描述】:

我正在尝试使用 Java Swing 构建一个简单、轻量级和响应式的应用程序。 然而,当它启动时,在窗口(一个 JFrame)出现之前有一个明显的延迟(>500 毫秒)。

我已经追踪到 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 记事本一样快(或几乎一样快),并且虽然我可以在记事本打开时将文本打印到控制台(如果我在第一次 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.

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

【问题讨论】:

  • 运行 Windows XP SP3、JRE 1.6.0_07。 AMD 速龙 64 X2 4200+ 2.19GHz,2GB RAM。它很旧,但通常足够快。
  • 如果我不得不猜测,我认为更多的内存会最有帮助。
  • 我实际上有 JRE 1.6.0_26 - 更新 26,而不是 7。Java 快速入门 (JQS) 是在更新 10 中引入的,并且正在我的机器上运行(默认情况下)。我已经升级到 JRE 1.7.0,但问题仍然存在。
  • 我对您的示例进行了更多实验。 JFrame.setDefaultLookAndFeelDecorated(true);额外花费 35 毫秒,frame1.pack(); 115 毫秒,frame1.pack(); 24 毫秒。

标签: java swing optimization


【解决方案1】:

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

原因:D3D 初始化

Swing 使用 Java2D API 进行绘图,根据这个Java SE 7 troubleshooting guide,Java2D 使用了一组渲染管道,“大致可以定义为渲染图元的不同方式”。更具体地说,Java2D 渲染管道似乎将跨平台 Java 代码连接到可能支持硬件加速的原生图形库(OpenGL、X11、D3D、DirectDraw、GDI)。

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

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

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

当然,在大多数系统上,在 D3D 初始化中花费的时间可能可以忽略不计,而且这只是我的系统上的问题,由于某些硬件或驱动程序问题,但我对此有些怀疑(尽管,如果真的,这很容易解决)。


详细跟踪到本机 initD3D()

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

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

我能够找到 source code for D3DGraphicsDevice 的一个版本及其静态 init 块(我真的只是从这个来源假设 initD3D() 是它的 () 需要这么长时间的原因 - 我的探查器似乎不承认本机功能 - 但这是一个合理的猜测)。


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

可以通过运行带有-Dsun.java2d.d3d=false 选项的java 来禁用D3D 管道,根据这个guide on Java2D "system properties"(以及前面提到的troubleshooting guide)。我认为这会禁用 D3D 但不会禁用 DirectDraw,可以使用 Dsun.java2d.noddraw=true 禁用 DirectDraw(然后“所有操作都将使用 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 约为 400 毫秒,D3D 约为 500 毫秒),但延迟仍然很明显。


其他延误

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

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

在我的实际程序中,它有点复杂(必须初始化更多的 Swing 组件),延迟似乎更广泛地分布在 Swing 代码中,但我注意到大量的本地类加载,“UI 安装"(加载默认 UI 属性等)等。不幸的是,我认为在这些方面没有什么可做的(如果有,请说出来)。


结束的想法

最后,能做的只有这么多,我不得不承认 Swing 和 JVM 在过去几年里已经走了多远。

【讨论】:

  • 这个详细、准确的解释为我节省了几天时间来复制您的发现。所以这就是为什么我的第一帧花了这么长时间才出现!谢谢
【解决方案2】:

第一个 jframe 的创建涉及到加载 swing 库。 JVM 不会加载查看导入语句的库。仅当第一次调用该库时才加载这些库。

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

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

您甚至可以通过这种方式对其进行测试。尝试用 JPanel 替换第二个 JFrame 创建,它仍然显示 0。

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      Mac OS X 10.5.8、Java 1.6.0_26。考虑到 JVM 启动时间和重量级图形对等体的创建,这并不意外。

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

      附录:根据Java Performance: Startup time的文章,Java Quick Starter可能在你的平台上可用。

      【讨论】:

      • 那么所有这些都发生在第一次调用 Window 构造函数中了吗?
      • 之前、期间和之后;我上面已经详细说明了。
      • 注意:从 NetBeans IDE 运行的示例可能已经加载了 JVM 使用的一些共享库。
      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 2021-08-28
        • 1970-01-01
        • 1970-01-01
        • 2018-01-01
        • 1970-01-01
        • 2020-09-24
        • 2020-08-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多