Skip to content

Show splash-screen using standalone SWT in Equinox launcher#1279

Draft
HannesWell wants to merge 2 commits into
eclipse-equinox:masterfrom
HannesWell:java-splashscreen
Draft

Show splash-screen using standalone SWT in Equinox launcher#1279
HannesWell wants to merge 2 commits into
eclipse-equinox:masterfrom
HannesWell:java-splashscreen

Conversation

@HannesWell
Copy link
Copy Markdown
Member

Create the splash-screen immediately at application start-up using standalone SWT within the Equinox launcher, which is effectively a plain Java application with flat classpath.
The SWT jars are obtained from the to be launched OSGi runtime but are loaded in this preliminary phase using a flat-classpath.

Fixes

This is currently a draft because, although showing the splash-screen works as desired, the launched application has a severely broken UI, at least under Windows. But at least it launches...
It's probably an interference of the Display created within the launcher and the display created within the OSGi runtime. E.g. disposing the launchers display, crashes the entire application and just creating the display without using it further causes the UI break. But I have high hopes that this can be fixed eventually.

It would be interesting for me how the situation is under Linux and macOS.
If anybody who has these OS at hand could check this out and just launch an Eclipse application containing e.g. the o.e.sdk it would be interesting if the application starts at all and what the state of the UI is.

@eclipse-equinox-bot
Copy link
Copy Markdown
Contributor

eclipse-equinox-bot commented Apr 27, 2026

This pull request changes some projects for the first time in this development cycle.
Therefore the following files need a version increment:

bundles/org.eclipse.equinox.launcher/META-INF/MANIFEST.MF

An additional commit containing all the necessary changes was pushed to the top of this PR's branch. To obtain these changes (for example if you want to push more changes) either fetch from your fork or apply the git patch.

Git patch
From 123b755722da59fc05b5bc9df79392f35e9624f4 Mon Sep 17 00:00:00 2001
From: Eclipse Equinox Bot <equinox-bot@eclipse.org>
Date: Thu, 30 Apr 2026 06:01:23 +0000
Subject: [PATCH] Version bump(s) for 4.40 stream


diff --git a/bundles/org.eclipse.equinox.launcher/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.launcher/META-INF/MANIFEST.MF
index db1e7fb7e..9c82aadd6 100644
--- a/bundles/org.eclipse.equinox.launcher/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.launcher/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.equinox.launcher;singleton:=true
-Bundle-Version: 1.7.100.qualifier
+Bundle-Version: 1.7.200.qualifier
 Main-Class: org.eclipse.equinox.launcher.Main
 Require-Bundle: org.eclipse.swt;bundle-version="3.134.0"
 Bundle-Vendor: %providerName
-- 
2.53.0

Further information are available in Common Build Issues - Missing version increments.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 27, 2026

Test Results

  231 files  ±0    231 suites  ±0   47m 13s ⏱️ - 4m 20s
2 213 tests ±0  2 164 ✅ ±0   49 💤 ±0  0 ❌ ±0 
6 402 runs  ±0  6 289 ✅ ±0  113 💤 ±0  0 ❌ ±0 

Results for commit c25975c. ± Comparison against base commit 84add06.

♻️ This comment has been updated with latest results.

@vogella
Copy link
Copy Markdown
Contributor

vogella commented Apr 29, 2026

Did you measure how long it takes to show a Splash? JVM startup + class loading + SWT native library extraction + Display init might take similar compared to wait for the regular splash. In my removal of the native code the display of the splash felt laggy as it showed up after approx. a second. IIRC 400 ms are acceptable for showing a splash.

Also I think SWT only supports one Display per process, which makes this approach harder. You would need to hand over the Display most likey the issue you are describing.

Create the splash-screen immediately at application start-up using
standalone SWT within the Equinox launcher, which is effectively a plain
Java application with flat classpath.
The SWT jars are obtained from the to be launched OSGi runtime but are
loaded in this preliminary phase using a flat-classpath.
@HannesWell
Copy link
Copy Markdown
Member Author

Did you measure how long it takes to show a Splash? JVM startup + class loading + SWT native library extraction + Display init might take similar compared to wait for the regular splash. In my removal of the native code the display of the splash felt laggy as it showed up after approx. a second. IIRC 400 ms are acceptable for showing a splash.

Yes I did a very simple measurement (using System.currentTimeMillis) and that indicated a runtime of ~400ms to show the splash using SWT and ~500ms on the first invocation where the native library files have to be extracted (for standalone SWT).
Subjectively it felt (almost) instantaneously or at least not laggy.

Also I think SWT only supports one Display per process, which makes this approach harder. You would need to hand over the Display most likey the issue you are describing.

In fact it depends on the platform how many displays are supported, at least on Windows it works on macOS it's forbidden and for Linux I don't know, maybe it depends on the distro. See e.g. also

But yes this doesn't make it simpler. But this case is currently even more special since it loads the SWT jar twice with different classloaders and the native library at different locations (stand-alone extracts to the user-home, while in OSGi it is located in some Equinox specific areas). Loading to copies of the same native library from different locations prevents a complaint from the JVM that the native library is already loaded (this check is based on paths IIRC), but then later fails at runtime, which are probably the errors I currently encountered.

I did some 'research' on this topic and asked the Gemini AI if two identical copies of a native library generally can be loaded with different classloaders and it negated it for the following conceptual reasons

- Symbol Collision:
Native libraries often use global static variables. If two different classloaders loaded the same library, they would both be
trying to manipulate the same memory space in the native heap, leading to unpredictable crashes or data corruption.

- Namespace Mapping:
The JVM maintains a mapping between Java classes and native symbols. Allowing multiple classloaders to map to the same
native symbols would create ambiguity—the JVM wouldn't know which Java class "owns" a specific native call.

- The Static State Disaster:
Most native libraries (C/C++) use static or global variables. In most operating systems, when a library is loaded into a process,
those global variables exist in a shared data segment.
The complete conversation

Question 1: Can a native library be loaded multiple times with multiple classloaders in Java?

The short answer is no. In a standard Java Virtual Machine (JVM), a native library (like a .dll, .so, or .dylib) can be loaded by exactly one classloader at a time.

If you attempt to load the same native library using a second classloader while the first one still "owns" it, the JVM will throw a rather grumpy UnsatisfiedLinkError.


Why the Restriction Exists

Java is a bit of a jealous partner when it comes to native code. This limitation isn't just a random rule; it’s a safeguard for the stability of your application.

  • Symbol Collision: Native libraries often use global static variables. If two different classloaders loaded the same library, they would both be trying to manipulate the same memory space in the native heap, leading to unpredictable crashes or data corruption.
  • Namespace Mapping: The JVM maintains a mapping between Java classes and native symbols. Allowing multiple classloaders to map to the same native symbols would create ambiguity—the JVM wouldn't know which Java class "owns" a specific native call.

The Error You'll See

If you try to load a library that is already linked elsewhere, you’ll encounter an error that looks something like this:

java.lang.UnsatisfiedLinkError: Native Library /path/to/library.so already loaded in another classloader

How to Work Around It

If you find yourself in a situation where multiple parts of your application (perhaps different plugins or modules) need access to the same native library, you generally have two paths:

  1. Load at the Parent Level:
    Load the native library in a parent classloader (like the System or Bootstrap classloader). Since child classloaders can see classes/methods loaded by their parents, they can all share the native functionality without attempting to reload the library themselves.

  2. The Wrapper Pattern:
    Create a "Wrapper" class in a shared classloader that performs the System.loadLibrary() call. Other classloaders then interact with this wrapper rather than calling the native methods directly.

  3. Library Unloading (The "Waiting" Game):
    A native library is only released when its classloader is garbage collected. If you absolutely must reload a library, you have to nullify all references to the loading classloader and wait for a GC cycle to finish. This is notoriously finicky and not recommended for production-critical logic.


A Modern Note on JNI

While the above holds true for standard JNI (Java Native Interface), some modern frameworks and newer versions of the JDK have explored ways to make native interop more flexible, but the core "one library, one classloader" rule remains the standard behavior for safety.

If you're dealing with a complex modular system (like OSGi), this is often the #1 headache developers face, so centralizing that native load is usually your best bet.


Question 2: Does this rule apply if I load an exact copy of a native library at a different location through another classloader?

Technically, you might bypass the "already loaded" check if the file paths are different, but you will almost certainly crash or fail during the linking phase.

Even if you make an exact copy of the library and put it in a different directory, the JVM and the underlying Operating System (OS) still have to deal with the Global Symbol Table.


1. The "Symbol Collision" Wall

Native libraries aren't just files; they are bundles of named functions (symbols). When the JVM loads a library to support a native method in Java, it looks for a specifically named symbol in the native code (e.g., Java_com_example_NativeClass_doWork).

If you load two copies of the library:

  1. Classloader A loads copy_1/library.so and maps the Java method to the native symbol.
  2. Classloader B loads copy_2/library.so.

When Classloader B tries to link its Java methods, the JVM sees that the symbol Java_com_example_NativeClass_doWork is already registered in its internal lookup table. Because the JNI (Java Native Interface) specification requires these symbol names to be unique within the process, the JVM will typically throw an UnsatisfiedLinkError or simply fail to link the second set of classes.

2. The Static State Disaster

Suppose you found a way to trick the JVM into loading both (perhaps by renaming the internal symbols, which is a massive chore). You would then run into Native Memory Corruption.

Most native libraries (C/C++) use static or global variables. In most operating systems, when a library is loaded into a process, those global variables exist in a shared data segment.

  • Copy A changes a global flag to true.
  • Copy B (thinking it’s independent) reads that same memory address and sees true instead of its expected default false.

Since they are running in the same process memory space, they aren't actually isolated. They will fight over the same memory addresses, leading to "Heisenbugs" or immediate segmentation faults.


Is there any way to make this work?

If you truly need multiple versions of the same native logic running simultaneously, you have two real options:

  • Renaming (The "Shading" Approach): You would have to rename the library, rename the JNI symbols inside the C code, and rename the Java packages/classes that call them. This makes them "unique" to the JVM.
  • Process Isolation: This is the standard industry solution. Run the two different parts of your app in separate JVM processes. They can then communicate via IPC (Inter-Process Communication) like Sockets, gRPC, or Shared Memory. This provides 100% isolation for the native libraries.

Bottom Line: The JVM treats the native layer as a "singleton" per process. Trying to clone the library file is like giving a person two different passports with the same name and face—the border control (the JVM) is still going to know they are the same person.

Does your use case involve running two different versions of the same library, or are you trying to isolate two different plugins?

The proposed resolution for this problem to use process isolation and to launch a sub-process instead sounds reasonable.
My first idea was to just launch a simple plain java process that runs the splash-screen and just let the main OSGi app then just draw the splash-screen over it, when it comes to showing a progress bar.
In a private conversation @HeikoKlare suggested to handle the splash entirely in a sub-process including the progress-bar and use IPC to drive the progress bar and set further information like the app title, build versions, etc.

On the other hand, this would avoid the need to pass a shell-handle and maybe even makes the Shell.internal_new() factory obsolete, called at:
https://github.com/eclipse-platform/eclipse.platform.ui/blob/f2e2f88a3dd95d5e25bbdefc50ea741fa95b0429/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/WorkbenchPlugin.java#L1210

Of course this then requires adaption of the WorkbenchPlugin.

I plan to investigate the sub-process approach further.

If SWT does not use JNI at some point in the future (and only FFM), we could explore again if running the splash from the same JVM with two classloaders works.

@vogella
Copy link
Copy Markdown
Contributor

vogella commented Apr 30, 2026

400 ms or 500 ms sounds great for a splash screen. What about starting it in the OSGI instance that later loads the rest of the app? This way you would have to load SWT only once.

For workbench I already had a patch for my removal of the splash screen which replaced Shell.internal_new() factory and was IMHO a nice general enhancement. But giving the expressed overload of the community with to many changes, I through it away.

Rough implementation idea was:

change WorkbenchPlugin.getSplashShell / Workbench.createSplashWrapper to always create a fresh Shell from splash.location. Stop reading splash.handle. The native takedown still happens via the existing splashHandler.run() chain when the application is running, so no second cleanup mechanism is needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reduce native code for splash screen handling

3 participants