From 172d132d2c7eff84bcf32e9697ae42c93568d06e Mon Sep 17 00:00:00 2001 From: Ondro Mihalyi Date: Fri, 15 May 2026 18:56:04 +0200 Subject: [PATCH 1/3] Several optimizations of Embedded GF startup Reduces startup time by about 300ms, from 2.2s to 2.0s, about 10% Things done: - set multiple properties in a single command (not so significant but might help) - preload some work in background ASAP - load some non-essential things in background (unpacking RARs to the domain) - do not initialize alias store if it's not needed --- .../config/support/TranslatedConfigView.java | 9 ++-- .../embedded/AutoDisposableGlassFish.java | 15 ++++-- .../embedded/EmbeddedGlassFishRuntime.java | 51 +++++++++++++++++-- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/nucleus/admin/config-api/src/main/java/org/glassfish/config/support/TranslatedConfigView.java b/nucleus/admin/config-api/src/main/java/org/glassfish/config/support/TranslatedConfigView.java index 0978d82b85c..7636a42e18a 100644 --- a/nucleus/admin/config-api/src/main/java/org/glassfish/config/support/TranslatedConfigView.java +++ b/nucleus/admin/config-api/src/main/java/org/glassfish/config/support/TranslatedConfigView.java @@ -88,9 +88,11 @@ public static Object getTranslatedValue(Object value) { if (stringValue.indexOf('$') == -1) { return value; } - DomainScopedPasswordAliasStore dasPasswordAliasStore = domainPasswordAliasStore(); - if (dasPasswordAliasStore != null) { - if (getAlias(stringValue) != null) { + // We first search for alias in the value which is much faster than loading the store.. + // This speeds up the startup and avoids loading the store if it's not needed + if (getAlias(stringValue) != null) { + DomainScopedPasswordAliasStore dasPasswordAliasStore = domainPasswordAliasStore(); + if (dasPasswordAliasStore != null) { try { return getRealPasswordFromAlias(stringValue, dasPasswordAliasStore); } catch (Exception e) { @@ -100,7 +102,6 @@ public static Object getTranslatedValue(Object value) { } } } - // Perform property substitution in the value // The loop limit is imposed to prevent infinite looping to values // such as a=${a} or a=foo ${b} and b=bar {$a} diff --git a/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/AutoDisposableGlassFish.java b/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/AutoDisposableGlassFish.java index c26ef2b65a8..3f1bf55e3a7 100644 --- a/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/AutoDisposableGlassFish.java +++ b/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/AutoDisposableGlassFish.java @@ -24,9 +24,11 @@ import java.lang.System.Logger.Level; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.ForkJoinPool; import java.util.function.Consumer; import java.util.stream.Stream; @@ -35,7 +37,9 @@ import org.glassfish.embeddable.GlassFish; import org.glassfish.embeddable.GlassFishException; import org.glassfish.embeddable.GlassFishProperties; +import org.glassfish.hk2.api.ActiveDescriptor; import org.glassfish.hk2.api.ServiceLocator; +import org.glassfish.hk2.utilities.BuilderHelper; import org.glassfish.main.boot.impl.GlassFishImpl; import org.glassfish.main.jdke.props.SystemProperties; @@ -63,6 +67,7 @@ class AutoDisposableGlassFish extends GlassFishImpl { // If there are custom configurations like http.port, https.port, jmx.port then configure them. CommandRunner commandRunner = null; Set knownPropertyPrefixes = new HashSet<>(); + ArrayList configPropertiesToSet = new ArrayList<>(); for (String key : gfProps.getPropertyNames()) { String propertyName = key; if (key.startsWith(GENERAL_CONFIG_PROP_PREFIX)) { @@ -92,10 +97,12 @@ class AutoDisposableGlassFish extends GlassFishImpl { continue; } } - CommandResult result = commandRunner.run("set", propertyName + "=" + propertyValue); - if (result.getExitStatus() != CommandResult.ExitStatus.SUCCESS) { - throw new GlassFishException(result.getOutput(), result.getFailureCause()); - } + configPropertiesToSet.add(propertyName + "=" + propertyValue); + } + + CommandResult result = commandRunner.run("set", configPropertiesToSet.toArray(String[]::new)); + if (result.getExitStatus() != CommandResult.ExitStatus.SUCCESS) { + throw new GlassFishException(result.getOutput(), result.getFailureCause()); } } diff --git a/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/EmbeddedGlassFishRuntime.java b/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/EmbeddedGlassFishRuntime.java index 2c3f564a05e..60f7646c27c 100644 --- a/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/EmbeddedGlassFishRuntime.java +++ b/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/EmbeddedGlassFishRuntime.java @@ -35,15 +35,20 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ForkJoinPool; import java.util.function.Consumer; import java.util.logging.Logger; +import org.glassfish.embeddable.CommandRunner; import org.glassfish.embeddable.GlassFish; import org.glassfish.embeddable.GlassFishException; import org.glassfish.embeddable.GlassFishProperties; import org.glassfish.embeddable.GlassFishRuntime; +import org.glassfish.hk2.api.ActiveDescriptor; import org.glassfish.hk2.api.ServiceLocator; +import org.glassfish.hk2.utilities.BuilderHelper; import org.glassfish.hk2.utilities.DuplicatePostProcessor; import org.glassfish.main.boot.log.LogFacade; @@ -89,18 +94,25 @@ public synchronized GlassFish newGlassFish(GlassFishProperties glassFishProperti properties.putAll(glassFishProperties.getProperties()); final GlassFishProperties gfProps = new GlassFishProperties(properties); - setEnv(gfProps); + final CompletableFuture setEnvFuture = setEnv(gfProps); final StartupContext startupContext = new StartupContext(gfProps.getProperties()); final ModulesRegistry modulesRegistry = AbstractFactory.getInstance().createModulesRegistry(); + // Building the serviceLocator takes about 600ms, around 1/5 final ServiceLocator serviceLocator = main.createServiceLocator(modulesRegistry, startupContext, List.of(new EmbeddedInhabitantsParser(), new DuplicatePostProcessor()), null); + preloadEssentialServicesInBackground(serviceLocator); + final ModuleStartup gfKernel = main.findStartupService(modulesRegistry, serviceLocator, null, startupContext); final Consumer onDispose = gf -> glassFishInstances.remove(gfProps.getInstanceRoot()); - final GlassFish glassFish = new AutoDisposableGlassFish(gfKernel, serviceLocator, gfProps, onDispose); + + // We need to complete the setup before creating AutoDisposableGlassFish + setEnvFuture.join(); + + final GlassFish glassFish = new AutoDisposableGlassFish(gfKernel, serviceLocator, gfProps, onDispose); // takes 900ms glassFishInstances.put(gfProps.getInstanceRoot(), glassFish); return glassFish; @@ -111,6 +123,26 @@ public synchronized GlassFish newGlassFish(GlassFishProperties glassFishProperti } } + // Preloads some services in background. They will always be needed during startup. + // Instead of loading them lazily in the main thread, we load them immediately in background so that they are loaded sooner + private void preloadEssentialServicesInBackground(final ServiceLocator serviceLocator) { + // Preload command runner. AutoDisposableGlassFish always needs to run it + // - first load preloads all the singleton dependencies which take about 500ms to initialize. + // It's typically not loaded in time before it's needed on the main thread but it still decreases + // the time the main thread needs to wait until the initialization completes by about 2/3, to about 150ms + ForkJoinPool.commonPool().submit(() -> serviceLocator.getService(CommandRunner.class)); + + // Preload the service for the set command (name="set") in a background thread. + // AutoDisposableGlassFish always needs to run it so it can retrieve it faster + // - we load the singleton GetSetModularityHelper dependency heere eagerly because it takes about 250ms to initialize + // Again, it doesn't load completely in time before it's needed on the main thread but decreases the wait time on the main to about 80ms + ForkJoinPool.commonPool().submit(() -> { + final ActiveDescriptor setCommandDescriptor = serviceLocator.getBestDescriptor(BuilderHelper.createNameFilter("set")); + serviceLocator.getServiceHandle(setCommandDescriptor) + .getService(); + }); + } + @Override public synchronized void shutdown() throws GlassFishException { for (GlassFish glassFish : glassFishInstances.values()) { @@ -130,7 +162,15 @@ public synchronized void shutdown() throws GlassFishException { } } - private void setEnv(GlassFishProperties gfProps) throws Exception { + /* + Unpacks the domain directory and sets up the properties related to the environment + + Asynchronously completes tasks that aren't essential during startup to move them from the main thread. + The returned future should be joined before creating AutoDisposableGlassFish, which applies properties + that might depend on having the environment completely set up. + */ + private CompletableFuture setEnv(GlassFishProperties gfProps) throws Exception { + CompletableFuture asyncResult = CompletableFuture.completedFuture(null); String instanceRootValue = gfProps.getInstanceRoot(); if (instanceRootValue == null) { instanceRootValue = createTempInstanceRoot(gfProps); @@ -145,7 +185,8 @@ private void setEnv(GlassFishProperties gfProps) throws Exception { if (installRootValue == null) { installRootValue = instanceRoot.getAbsolutePath(); gfProps.setProperty("-type", "EMBEDDED"); - JarUtil.extractRars(installRootValue); + final String installRootFinalValue = installRootValue; + asyncResult = asyncResult.thenRun(() -> JarUtil.extractRars(installRootFinalValue)); } JarUtil.setEnv(installRootValue); @@ -159,6 +200,8 @@ private void setEnv(GlassFishProperties gfProps) throws Exception { // StartupContext requires the installRoot to be set in the GlassFishProperties. gfProps.setProperty(INSTALL_ROOT.getPropertyName(), installRoot.getAbsolutePath()); gfProps.setProperty(BootstrapKeys.INSTALL_ROOT_URI_PROP_NAME, installRoot.toURI().toString()); + + return asyncResult; } private String createTempInstanceRoot(GlassFishProperties gfProps) throws Exception { From d71c276a49b2a14359d586ad2be4680890aa688a Mon Sep 17 00:00:00 2001 From: Ondro Mihalyi Date: Wed, 20 May 2026 14:21:42 +0200 Subject: [PATCH 2/3] Fix Checkstyle violations - Remove unused commits --- .../glassfish/main/boot/embedded/AutoDisposableGlassFish.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/AutoDisposableGlassFish.java b/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/AutoDisposableGlassFish.java index 3f1bf55e3a7..bf8c1965efb 100644 --- a/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/AutoDisposableGlassFish.java +++ b/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/AutoDisposableGlassFish.java @@ -28,7 +28,6 @@ import java.util.Comparator; import java.util.HashSet; import java.util.Set; -import java.util.concurrent.ForkJoinPool; import java.util.function.Consumer; import java.util.stream.Stream; @@ -37,9 +36,7 @@ import org.glassfish.embeddable.GlassFish; import org.glassfish.embeddable.GlassFishException; import org.glassfish.embeddable.GlassFishProperties; -import org.glassfish.hk2.api.ActiveDescriptor; import org.glassfish.hk2.api.ServiceLocator; -import org.glassfish.hk2.utilities.BuilderHelper; import org.glassfish.main.boot.impl.GlassFishImpl; import org.glassfish.main.jdke.props.SystemProperties; From b74acdac22d41e7d00877894d1a179f334470e45 Mon Sep 17 00:00:00 2001 From: Ondro Mihalyi Date: Wed, 20 May 2026 21:40:31 +0200 Subject: [PATCH 3/3] Optimizations of Embedded GF startup - pass the context classloader Pass the context classloader to the asynchronous jobs. Without this, the service loader doesn't find some classes when Embedded GlassFish is started within the same JVM as Maven (using an old Maven plugin or the new plugin with execution configured to the same JVM) --- .../embedded/EmbeddedGlassFishRuntime.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/EmbeddedGlassFishRuntime.java b/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/EmbeddedGlassFishRuntime.java index 60f7646c27c..6ab97f58094 100644 --- a/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/EmbeddedGlassFishRuntime.java +++ b/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/EmbeddedGlassFishRuntime.java @@ -23,6 +23,7 @@ import com.sun.enterprise.module.bootstrap.ModuleStartup; import com.sun.enterprise.module.bootstrap.StartupContext; import com.sun.enterprise.module.common_impl.AbstractFactory; +import com.sun.enterprise.util.Utility; import java.io.File; import java.io.FileOutputStream; @@ -130,16 +131,25 @@ private void preloadEssentialServicesInBackground(final ServiceLocator serviceLo // - first load preloads all the singleton dependencies which take about 500ms to initialize. // It's typically not loaded in time before it's needed on the main thread but it still decreases // the time the main thread needs to wait until the initialization completes by about 2/3, to about 150ms - ForkJoinPool.commonPool().submit(() -> serviceLocator.getService(CommandRunner.class)); + runAsynchWithThreadContext(() -> serviceLocator.getService(CommandRunner.class), Thread.currentThread()); // Preload the service for the set command (name="set") in a background thread. // AutoDisposableGlassFish always needs to run it so it can retrieve it faster // - we load the singleton GetSetModularityHelper dependency heere eagerly because it takes about 250ms to initialize // Again, it doesn't load completely in time before it's needed on the main thread but decreases the wait time on the main to about 80ms + runAsynchWithThreadContext( + () -> { + final ActiveDescriptor setCommandDescriptor = serviceLocator.getBestDescriptor(BuilderHelper.createNameFilter("set")); + serviceLocator.getServiceHandle(setCommandDescriptor) + .getService(); + }, + Thread.currentThread()); + } + + private static void runAsynchWithThreadContext(Utility.RunnableWithException action, Thread spawningThread) { + final ClassLoader contextClassLoader = spawningThread.getContextClassLoader(); ForkJoinPool.commonPool().submit(() -> { - final ActiveDescriptor setCommandDescriptor = serviceLocator.getBestDescriptor(BuilderHelper.createNameFilter("set")); - serviceLocator.getServiceHandle(setCommandDescriptor) - .getService(); + Utility.runWithContextClassLoader(contextClassLoader, action); }); } @@ -186,7 +196,12 @@ private CompletableFuture setEnv(GlassFishProperties gfProps) throws Excep installRootValue = instanceRoot.getAbsolutePath(); gfProps.setProperty("-type", "EMBEDDED"); final String installRootFinalValue = installRootValue; - asyncResult = asyncResult.thenRun(() -> JarUtil.extractRars(installRootFinalValue)); + final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + asyncResult = asyncResult.thenRun( + () -> { + Utility.runWithContextClassLoader(contextClassLoader, + () -> JarUtil.extractRars(installRootFinalValue)); + }); } JarUtil.setEnv(installRootValue);