10

Since upgrading to install4j 7.0.5 and Java 10, users that run our application on Windows more and more often report that the application throws

java.lang.NoSuchMethodError: <init>
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.staticScreen_getScreens(Native Method)
    at javafx.graphics/com.sun.glass.ui.Screen.initScreens(Unknown Source)
    at javafx.graphics/com.sun.glass.ui.Application.lambda$run$1(Unknown Source)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(Unknown Source)
    at java.base/java.lang.Thread.run(Unknown Source)


UiLauncher (WAITING)
    at java.base@10.0.1/jdk.internal.misc.Unsafe.park(Native Method)
    at java.base@10.0.1/java.util.concurrent.locks.LockSupport.park(Unknown Source)
    at java.base@10.0.1/java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(Unknown Source)
    at java.base@10.0.1/java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(Unknown Source)
    at java.base@10.0.1/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(Unknown Source)
    at java.base@10.0.1/java.util.concurrent.CountDownLatch.await(Unknown Source)
    at platform/javafx.graphics@10.0.1/com.sun.javafx.tk.quantum.QuantumToolkit.startup(Unknown Source)
    at platform/javafx.graphics@10.0.1/com.sun.javafx.application.PlatformImpl.startup(Unknown Source)
    at platform/javafx.graphics@10.0.1/com.sun.javafx.application.PlatformImpl.startup(Unknown Source)
    at platform/javafx.swing@10.0.1/javafx.embed.swing.JFXPanel.initFx(Unknown Source)
    at platform/javafx.swing@10.0.1/javafx.embed.swing.JFXPanel.<init>(Unknown Source)
    at java.base@10.0.1/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base@10.0.1/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at java.base@10.0.1/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.base@10.0.1/java.lang.reflect.Constructor.newInstance(Unknown Source)
    at java.base@10.0.1/java.lang.Class.newInstance(Unknown Source)
    at app//...

when starting the application though the install4j created exe file. The error is triggered by creating an instance of javafx.embed.swing.JFXPanel through reflection:

Class.forName("javafx.embed.swing.JFXPanel").newInstance();

We currently suspect that for some reason an incompatible DLL is loaded (glass.dll seems to contain the native method mentioned in the stacktrace).

Does anyone know how to prevent this error? E.g. is there are way to restrict the java.library.path used when executing the application through the install4j-generated exe to the Java runtime environment that was embedded in the installer and installed locally with the application? According to one user, the error does not occur if the application is started "manually" using the

java -jar app.jar

command. So it seems the problem lies with the install4j created executable.

Thomas Behr
  • 761
  • 4
  • 10
  • Can you include more of the stack trace? – Alan Bateman Jun 29 '18 at 12:38
  • Is the calling code in a module that has `requires javafx.swing;` specified? Note: `clazz.newInstance` was deprecated in favor of `clazz.getDeclaredConstructor().invoke()`. – fabian Jun 29 '18 at 13:30
  • 1
    @AlanBateman Done, but I strongly suspect that it will not help much. The abbreviated line in the stacktrace is the application code that calls `Class.newInstance`. – Thomas Behr Jun 29 '18 at 13:45
  • @fabian The application is a Java 8 legacy application. It does not use modules at all. It is run on Java 10 only for HDPI support on Windows. Moreover, the `JFXPanel` class is found (otherwise there would be an error much earlier)! `Class.newInstance` is deprecated for exception processing reasons only - it should not break down in this case. – Thomas Behr Jun 29 '18 at 13:49
  • Where you have the installer application launching your Java app, it would be useful to get an `echo` of the exact settings your Java app is being launched with. When you run `java -jar app.jar` it will either use the `CLASS_PATH` variable or the current directory as the classpath value. You should also dump the classpath from code and compare the value when launching in each style (manually or via installer lib). – flakes Jul 02 '18 at 22:10
  • Just to confirm, as [this link](https://www.ej-technologies.com/products/install4j/whatsnew7.html) reads *"For Windows, install4j already had an option to make launchers DPI-aware, the new default option "Java 9+" now enables DPI-awareness if the minimum Java version is at least 9."*..what's the java version of your project? – Naman Jul 03 '18 at 04:14
  • The `Class.newInstance()` call has nothing to do with the problem, neither has `JFXPanel`. As the stack trace clearly shows, the exception happens in an entirely different thread not containing any application code at all. It would happen the same way, if the startup was triggered in a different way. Since it is very unlikely that the JavaFX bundled with the JRE has linkage errors, the next question would be, do you bundle JavaFX classes/libs with your application? Or does install4j? – Holger Jul 03 '18 at 09:26
  • @flakes Unfortunately, the error does not occur on our test machines but only for some users of the application. Moreover, the application is not necessarily targeted at tech-savvy users. Thus I have not been able to get debug dumps. – Thomas Behr Jul 03 '18 at 10:14
  • @nullpointer The JRE bundled with the install4j installer is 10.0.1. The application itself is a Java 8 compatible legacy application. – Thomas Behr Jul 03 '18 at 10:16
  • 1
    @Holger install4j is used to created installers with bundled JREs for the application. The error occurs only for some users (case in point: it does not occur on our test machines), so I do not think that the bundled JRE is broken. Instead I suspect that an incompatible DLL from another Java installation is loaded for some reason. Which is why I asked if someone knew how to restrict the `java.library.path` (by default all kinds of system paths like `C:\ProgramData\Oracle\Java\javapath;C:\WINDOWS\system32;C:\WINDOWS;...` are part of `java.library.path`). – Thomas Behr Jul 03 '18 at 10:29
  • @ThomasBehr in that case I really implore you to think about creating meaningful logs in a way that anyone can find them easily. If you output log files to an easy to find directory, then it would be trivial for your users to upload them. Considering this installer application runs once on a host, I would just make them verbose by default and not worry too much about log bloat. Operations are so commonly forgotten about when developing, but they are the key to creating long lasting, maintainable software. – flakes Jul 08 '18 at 06:41
  • I investigated this further: In order to reproduce download yEd with install4j installer and bundled jre. Look at your system path variable and *anywhere* in your path, add copy a "glass.dll" from JavaFX from a previous JRE version 8 - Procmon.exe shows that java (or rather the install4j-generated-exe) will look through all the path elements to load "glass.dll" and if there is one before the bundled JRE is checked (which is the *last* check) it will try to use this and crash. Why does the system path have a higher priority than the bundled JRE? – Sebastian Jul 16 '18 at 07:56
  • I guess this should help: https://stackoverflow.com/a/12566979/351836 but shouldn't install4j already make sure that it works like this? after all the linked answer is by @ingo-kegel, one of the makers of install4j... – Sebastian Jul 16 '18 at 08:34

1 Answers1

5

The workaround to this problem seems to be to remove all occurences of "glass.dll" from your system %PATH%.

I believe a fix must be implemented somewhere else, though; either in the Java runtime or in the Install4j code, but cannot be implemented in the Java code of the actual app:

For some reason the Java runtime version in Install4j checks the bundled JRE last when locating the libraries. In this case the problematic native library is glass.dll which should contain the requested <init> method but if anywhere in your %PATH% there is an older, incompatible version of glass.dll (e.g. from a previous Java 8 installation) that file will be loaded with a higher precedence and then the application will crash natively.

This is not a problem in the code of the application (the java code), nor a problem with the bundled JDK, this is a problem of how the install-4j-generated exe files (or maybe Java internally) tries to resolve native dlls. Instead of checking all path elements first, it should be checking the bundled JRE directory, first.

With Procmon you can see that it loads arbitrarily placed glass.dll files in the path, first: I added one from JDK 1.8 into one of my path elements and got this (plus the crash):

procmon log showing java loading the first glass.dll it can find

Sebastian
  • 7,729
  • 2
  • 37
  • 69
  • 1
    This then appears to be rooted in the DLL search order. Someone (probably Java) seems to use the legacy order which includes things like PATH or the current working directory as the first options. The recommended way to load DLLs has been `SafeDllSearchMode` or `LOAD_WITH_ALTERED_SEARCH_PATH` for quite a while now. But that wouldn't be the only legacy part in how Java interfaces with Windows ... – Joey Jul 16 '18 at 10:31