You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by ma...@apache.org on 2022/11/01 20:40:25 UTC

[logging-log4j2] 02/02: Revert "[LOG4J2-3459] - Add LoggingSystemProvider SPI"

This is an automated email from the ASF dual-hosted git repository.

mattsicker pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit e013ad764a49d67fb2c200203311a99c9807a76d
Author: Matt Sicker <ma...@apache.org>
AuthorDate: Tue Nov 1 15:39:58 2022 -0500

    Revert "[LOG4J2-3459] - Add LoggingSystemProvider SPI"
    
    This reverts commit aab23d5c05bff2915c290dff3e73a0ae03caf43a.
    
    To be broken up into smaller commits.
---
 .../junit/LogManagerLoggerContextFactoryRule.java  |  11 +-
 .../test/junit/LoggerContextFactoryExtension.java  |  12 +-
 .../{TestSystemProvider.java => TestProvider.java} |  23 +-
 .../log4j/spi/DefaultThreadContextMapTest.java     |  10 +-
 ....apache.logging.log4j.spi.LoggingSystemProvider |  17 --
 .../services/org.apache.logging.log4j.spi.Provider |   1 +
 log4j-api/src/main/java/module-info.java           |   5 +-
 .../java/org/apache/logging/log4j/LogManager.java  |  76 ++++++-
 .../org/apache/logging/log4j/ThreadContext.java    | 133 ++++++-----
 .../apache/logging/log4j/internal/BindingMap.java  |  59 -----
 .../log4j/spi/AbstractLoggingSystemProvider.java   |  60 -----
 .../CopyOnWriteSortedArrayThreadContextMap.java    |  68 ++++--
 .../log4j/spi/DefaultContextMapFactory.java        | 102 ---------
 .../log4j/spi/DefaultContextStackFactory.java      |  50 ----
 .../logging/log4j/spi/DefaultThreadContextMap.java |  36 ++-
 .../GarbageFreeSortedArrayThreadContextMap.java    |  51 +++--
 .../log4j/spi/LegacyLoggingSystemProvider.java     | 125 ----------
 .../logging/log4j/spi/LoggingSystemProvider.java   |  82 -------
 .../org/apache/logging/log4j/spi/Provider.java     | 253 +++++++++++++++++++++
 .../logging/log4j/spi/ThreadContextMapFactory.java | 134 +++++++++++
 .../{internal/Binding.java => util/LazyInt.java}   |  44 ++--
 .../org/apache/logging/log4j/util3/Activator.java  |  57 ++---
 .../org/apache/logging/log4j/util3/Constants.java  |  43 ++--
 .../apache/logging/log4j/util3/LoggingSystem.java  | 223 ------------------
 .../apache/logging/log4j/util3/PropertiesUtil.java |  31 ++-
 .../apache/logging/log4j/util3/ProviderUtil.java   | 143 ++++++++++++
 .../core/test/junit/ContextSelectorCallback.java   |   9 +-
 .../core/test/junit/LoggerContextResolver.java     |  10 +-
 .../log4j/core/test/junit/LoggerContextRule.java   |   5 +-
 ....apache.logging.log4j.spi.LoggingSystemProvider |  17 --
 .../services/org.apache.logging.log4j.spi.Provider |   1 +
 log4j-core/src/main/java/module-info.java          |   7 +-
 .../logging/log4j/core/impl/Log4jProvider.java     |  24 +-
 .../log4j/core/impl/Log4jSystemProvider.java       |  75 ------
 .../apache/logging/log4j/core/osgi/Activator.java  |   4 +-
 ....apache.logging.log4j.spi.LoggingSystemProvider |  17 --
 .../services/org.apache.logging.log4j.spi.Provider |   1 +
 .../CopyOnWriteOpenHashMapThreadContextMap.java    |   3 +-
 .../GarbageFreeOpenHashMapThreadContextMap.java    |   3 +-
 log4j-to-jul/src/main/java/module-info.java        |   7 +-
 .../{JULSystemProvider.java => JULProvider.java}   |  24 +-
 ....apache.logging.log4j.spi.LoggingSystemProvider |  17 --
 .../services/org.apache.logging.log4j.spi.Provider |  18 ++
 .../org/apache/logging/slf4j/MDCContextMap.java    |  19 +-
 ...SLF4JSystemProvider.java => SLF4JProvider.java} |  26 +--
 ....apache.logging.log4j.spi.LoggingSystemProvider |  17 --
 .../services/org.apache.logging.log4j.spi.Provider |   1 +
 src/changes/changes.xml                            |   3 -
 src/site/asciidoc/manual/extending.adoc            |  20 +-
 src/site/asciidoc/runtime-dependencies.adoc        |   4 +-
 50 files changed, 986 insertions(+), 1195 deletions(-)

diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LogManagerLoggerContextFactoryRule.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LogManagerLoggerContextFactoryRule.java
index b7a74d1f8f..6b4d0d9825 100644
--- a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LogManagerLoggerContextFactoryRule.java
+++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LogManagerLoggerContextFactoryRule.java
@@ -16,12 +16,12 @@
  */
 package org.apache.logging.log4j.test.junit;
 
+import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.spi.LoggerContextFactory;
-import org.apache.logging.log4j.util3.LoggingSystem;
 import org.junit.rules.ExternalResource;
 
 /**
- * Sets the {@link LoggingSystem}'s {@link LoggerContextFactory} to the given instance before the test and restores it to
+ * Sets the {@link LogManager}'s {@link LoggerContextFactory} to the given instance before the test and restores it to
  * the original value after the test.
  *
  * @deprecated Use {@link LoggerContextFactoryExtension} with JUnit 5
@@ -40,14 +40,13 @@ public class LogManagerLoggerContextFactoryRule extends ExternalResource {
 
     @Override
     protected void after() {
-        LoggingSystem.getInstance().setLoggerContextFactory(this.restoreLoggerContextFactory);
+        LogManager.setFactory(this.restoreLoggerContextFactory);
     }
 
     @Override
     protected void before() throws Throwable {
-        final LoggingSystem system = LoggingSystem.getInstance();
-        this.restoreLoggerContextFactory = system.getLoggerContextFactory();
-        system.setLoggerContextFactory(this.loggerContextFactory);
+        this.restoreLoggerContextFactory = LogManager.getFactory();
+        LogManager.setFactory(this.loggerContextFactory);
     }
 
 }
diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LoggerContextFactoryExtension.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LoggerContextFactoryExtension.java
index 3e9767f1f6..0b63e3ab5e 100644
--- a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LoggerContextFactoryExtension.java
+++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LoggerContextFactoryExtension.java
@@ -17,8 +17,8 @@
 
 package org.apache.logging.log4j.test.junit;
 
+import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.spi.LoggerContextFactory;
-import org.apache.logging.log4j.util3.LoggingSystem;
 import org.junit.jupiter.api.extension.AfterAllCallback;
 import org.junit.jupiter.api.extension.BeforeAllCallback;
 import org.junit.jupiter.api.extension.ExtensionContext;
@@ -30,6 +30,7 @@ import org.junit.jupiter.api.extension.ExtensionContext;
  */
 public class LoggerContextFactoryExtension implements BeforeAllCallback, AfterAllCallback {
 
+    private static final String KEY = "previousFactory";
     private final LoggerContextFactory loggerContextFactory;
 
     public LoggerContextFactoryExtension(LoggerContextFactory loggerContextFactory) {
@@ -38,11 +39,16 @@ public class LoggerContextFactoryExtension implements BeforeAllCallback, AfterAl
 
     @Override
     public void beforeAll(ExtensionContext context) throws Exception {
-        LoggingSystem.getInstance().setLoggerContextFactory(loggerContextFactory);
+        getStore(context).put(KEY, LogManager.getFactory());
+        LogManager.setFactory(loggerContextFactory);
     }
 
     @Override
     public void afterAll(ExtensionContext context) throws Exception {
-        LoggingSystem.getInstance().setLoggerContextFactory(null);
+        LogManager.setFactory(getStore(context).get(KEY, LoggerContextFactory.class));
+    }
+
+    private ExtensionContext.Store getStore(ExtensionContext context) {
+        return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestClass()));
     }
 }
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/TestSystemProvider.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java
similarity index 67%
rename from log4j-api-test/src/test/java/org/apache/logging/log4j/TestSystemProvider.java
rename to log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java
index befe990fd3..6eff8c62cf 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/TestSystemProvider.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java
@@ -14,25 +14,16 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-
 package org.apache.logging.log4j;
 
-import org.apache.logging.log4j.spi.AbstractLoggingSystemProvider;
+import org.apache.logging.log4j.spi.Provider;
 import org.apache.logging.log4j.test.TestLoggerContextFactory;
 
-public class TestSystemProvider extends AbstractLoggingSystemProvider<TestLoggerContextFactory> {
-    @Override
-    protected TestLoggerContextFactory createLoggerContextFactory() {
-        return new TestLoggerContextFactory();
-    }
-
-    @Override
-    public int getPriority() {
-        return 0;
-    }
-
-    @Override
-    public String getVersion() {
-        return "2.6.0";
+/**
+ * Binding for the Log4j API.
+ */
+public class TestProvider extends Provider {
+    public TestProvider() {
+        super(0, "2.6.0", TestLoggerContextFactory.class);
     }
 }
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java
index f25737b427..8326ac3da5 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java
@@ -223,15 +223,17 @@ public class DefaultThreadContextMapTest {
     @ClearSystemProperty(key = DefaultThreadContextMap.INHERITABLE_MAP)
     @InitializesThreadContext
     public void testThreadLocalNotInheritableByDefault() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        assertFalse(map.isInheritable());
+        ThreadContextMapFactory.init();
+        final ThreadLocal<Map<String, String>> threadLocal = DefaultThreadContextMap.createThreadLocalMap(true);
+        assertFalse(threadLocal instanceof InheritableThreadLocal<?>);
     }
     
     @Test
     @SetSystemProperty(key = DefaultThreadContextMap.INHERITABLE_MAP, value = "true")
     @InitializesThreadContext
     public void testThreadLocalInheritableIfConfigured() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        assertTrue(map.isInheritable());
+        ThreadContextMapFactory.init();
+        final ThreadLocal<Map<String, String>> threadLocal = DefaultThreadContextMap.createThreadLocalMap(true);
+        assertTrue(threadLocal instanceof InheritableThreadLocal<?>);
     }
 }
diff --git a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider
deleted file mode 100644
index c2f1e85720..0000000000
--- a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache license, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the license for the specific language governing permissions and
-# limitations under the license.
-#
-org.apache.logging.log4j.TestSystemProvider
diff --git a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
new file mode 100644
index 0000000000..5ae649a3f9
--- /dev/null
+++ b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
@@ -0,0 +1 @@
+org.apache.logging.log4j.TestProvider
\ No newline at end of file
diff --git a/log4j-api/src/main/java/module-info.java b/log4j-api/src/main/java/module-info.java
index a8ee57ba81..b294bde1c3 100644
--- a/log4j-api/src/main/java/module-info.java
+++ b/log4j-api/src/main/java/module-info.java
@@ -15,7 +15,6 @@
  * limitations under the license.
  */
 
-import org.apache.logging.log4j.spi.LoggingSystemProvider;
 import org.apache.logging.log4j.util.PropertySource;
 import org.apache.logging.log4j.util3.EnvironmentPropertySource;
 import org.apache.logging.log4j.util3.SystemPropertiesPropertySource;
@@ -23,7 +22,7 @@ import org.apache.logging.log4j.util3.SystemPropertiesPropertySource;
 /**
  * <p>Log4j public API for libraries and applications. This module is provided as a portable
  * {@linkplain org.apache.logging.log4j.Logger logging API} which supports independent
- * {@linkplain LoggingSystemProvider logging provider} backends for configuring
+ * {@linkplain org.apache.logging.log4j.spi.Provider logging provider} backends for configuring
  * the underlying logging system. The {@link org.apache.logging.log4j} package contains the main APIs for loggers,
  * markers, logging levels, thread context maps (aka MDC), and thread context stacks (aka NDC).</p>
  *
@@ -51,7 +50,7 @@ module org.apache.logging.log4j {
             org.apache.logging.log4j.script;
 
     requires static org.osgi.framework;
-    uses LoggingSystemProvider;
+    uses org.apache.logging.log4j.spi.Provider;
     uses PropertySource;
     uses org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory;
 
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
index 9ed54dc21d..498733f5ab 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
@@ -16,18 +16,27 @@
  */
 package org.apache.logging.log4j;
 
-import org.apache.logging.log4j.util3.LoggingSystem;
+import org.apache.logging.log4j.internal.LogManagerStatus;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.message.StringFormatterMessageFactory;
 import org.apache.logging.log4j.simple.SimpleLoggerContextFactory;
 import org.apache.logging.log4j.spi.LoggerContext;
 import org.apache.logging.log4j.spi.LoggerContextFactory;
+import org.apache.logging.log4j.spi.Provider;
 import org.apache.logging.log4j.spi.Terminable;
 import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.Lazy;
+import org.apache.logging.log4j.util.PropertyEnvironment;
 import org.apache.logging.log4j.util.Strings;
+import org.apache.logging.log4j.util3.LoaderUtil;
+import org.apache.logging.log4j.util3.PropertiesUtil;
+import org.apache.logging.log4j.util3.ProviderUtil;
 import org.apache.logging.log4j.util3.StackLocatorUtil;
 
 import java.net.URI;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
 
 /**
  * The anchor point for the Log4j logging system. The most common usage of this class is to obtain a named
@@ -56,6 +65,67 @@ public class LogManager {
     // for convenience
     private static final String FQCN = LogManager.class.getName();
 
+    /**
+     * Scans the classpath to find all logging implementation. Currently, only one will be used but this could be
+     * extended to allow multiple implementations to be used.
+     */
+    private static final Lazy<LoggerContextFactory> PROVIDER = Lazy.lazy(() -> {
+        // Shortcut binding to force a specific logging implementation.
+        final PropertyEnvironment managerProps = PropertiesUtil.getProperties();
+        final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME);
+        if (factoryClassName != null) {
+            try {
+                return LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class);
+            } catch (final ClassNotFoundException cnfe) {
+                LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName);
+            } catch (final Exception ex) {
+                LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, ex);
+            }
+        }
+
+        // note that the following initial call to ProviderUtil may block until a Provider has been installed when
+        // running in an OSGi environment
+        if (!ProviderUtil.hasProviders()) {
+            LOGGER.error("Log4j2 could not find a logging implementation. "
+                    + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
+            return SimpleLoggerContextFactory.INSTANCE;
+        }
+
+        final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>();
+        for (final Provider provider : ProviderUtil.getProviders()) {
+            final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory();
+            if (factoryClass != null) {
+                try {
+                    factories.put(provider.getPriority(), factoryClass.newInstance());
+                } catch (final Exception e) {
+                    LOGGER.error("Unable to create class {} specified in provider URL {}", factoryClass.getName(), provider
+                            .getUrl(), e);
+                }
+            }
+        }
+
+        if (factories.isEmpty()) {
+            LOGGER.error("Log4j2 could not find a logging implementation. "
+                    + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
+            return SimpleLoggerContextFactory.INSTANCE;
+        } else if (factories.size() == 1) {
+            return factories.get(factories.lastKey());
+        } else {
+            final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");
+            for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {
+                sb.append("Factory: ").append(entry.getValue().getClass().getName());
+                sb.append(", Weighting: ").append(entry.getKey()).append('\n');
+            }
+            final var factory = factories.get(factories.lastKey());
+            sb.append("Using factory: ").append(factory.getClass().getName());
+            LOGGER.warn(sb.toString());
+            return factory;
+        }
+    }).map(factory -> {
+        LogManagerStatus.setInitialized(true);
+        return factory;
+    });
+
     /**
      * Prevents instantiation
      */
@@ -363,7 +433,7 @@ public class LogManager {
      * @return The LoggerContextFactory.
      */
     public static LoggerContextFactory getFactory() {
-        return LoggingSystem.getInstance().getLoggerContextFactory();
+        return PROVIDER.get();
     }
 
     /**
@@ -380,7 +450,7 @@ public class LogManager {
      * @param factory the LoggerContextFactory to use.
      */
     public static void setFactory(final LoggerContextFactory factory) {
-        LoggingSystem.getInstance().setLoggerContextFactory(factory);
+        PROVIDER.set(factory);
     }
 
     /**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java b/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java
index eae54bb2a8..b03a1f3d17 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java
@@ -17,13 +17,18 @@
 
 package org.apache.logging.log4j;
 
-import org.apache.logging.log4j.util3.LoggingSystem;
 import org.apache.logging.log4j.message.ParameterizedMessage;
+import org.apache.logging.log4j.spi.CleanableThreadContextMap;
 import org.apache.logging.log4j.spi.DefaultThreadContextMap;
+import org.apache.logging.log4j.spi.DefaultThreadContextStack;
+import org.apache.logging.log4j.spi.NoOpThreadContextMap;
 import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
 import org.apache.logging.log4j.spi.ThreadContextMap;
+import org.apache.logging.log4j.spi.ThreadContextMap2;
+import org.apache.logging.log4j.spi.ThreadContextMapFactory;
 import org.apache.logging.log4j.spi.ThreadContextStack;
-import org.apache.logging.log4j.util.Lazy;
+import org.apache.logging.log4j.util3.PropertiesUtil;
+import org.apache.logging.log4j.util.PropertyEnvironment;
 
 import java.io.Serializable;
 import java.util.AbstractCollection;
@@ -185,43 +190,45 @@ public final class ThreadContext {
     @SuppressWarnings("PublicStaticCollectionField")
     public static final ThreadContextStack EMPTY_STACK = new EmptyThreadContextStack();
 
-    private static final Lazy<ThreadContext> INSTANCE = Lazy.relaxed(() -> new ThreadContext(LoggingSystem.getInstance()));
+    private static final String DISABLE_MAP = "disableThreadContextMap";
+    private static final String DISABLE_STACK = "disableThreadContextStack";
+    private static final String DISABLE_ALL = "disableThreadContext";
 
-    private final ThreadContextMap contextMap;
-    private final ThreadContextStack contextStack;
-    private final ReadOnlyThreadContextMap readOnlyThreadContextMap;
+    private static boolean useStack;
+    private static ThreadContextMap contextMap;
+    private static ThreadContextStack contextStack;
+    private static ReadOnlyThreadContextMap readOnlyContextMap;
 
-    private ThreadContext(final LoggingSystem system) {
-        this(system.createContextMap(), system.createContextStack());
+    static {
+        init();
     }
 
-    private ThreadContext(final ThreadContextMap contextMap, final ThreadContextStack contextStack) {
-        this.contextMap = contextMap;
-        this.contextStack = contextStack;
-        readOnlyThreadContextMap = contextMap instanceof ReadOnlyThreadContextMap ? (ReadOnlyThreadContextMap) contextMap : null;
+    private ThreadContext() {
+        // empty
     }
 
     /**
      * <em>Consider private, used for testing.</em>
      */
     public static void init() {
-        final LoggingSystem system = LoggingSystem.getInstance();
-        // clear out cached values and allow them to be recalculated
-        system.setContextMapFactory(null);
-        system.setContextStackFactory(null);
-        INSTANCE.set(null);
-    }
-
-    private static ThreadContextMap map() {
-        return INSTANCE.value().contextMap;
-    }
-
-    private static ThreadContextStack stack() {
-        return INSTANCE.value().contextStack;
-    }
-
-    private static ReadOnlyThreadContextMap readOnlyMap() {
-        return INSTANCE.value().readOnlyThreadContextMap;
+        ThreadContextMapFactory.init();
+        contextMap = null;
+        final PropertyEnvironment managerProps = PropertiesUtil.getProperties();
+        final boolean disableAll = managerProps.getBooleanProperty(DISABLE_ALL);
+        useStack = !(managerProps.getBooleanProperty(DISABLE_STACK) || disableAll);
+        final boolean useMap = !(managerProps.getBooleanProperty(DISABLE_MAP) || disableAll);
+
+        contextStack = new DefaultThreadContextStack(useStack);
+        if (!useMap) {
+            contextMap = new NoOpThreadContextMap();
+        } else {
+            contextMap = ThreadContextMapFactory.createThreadContextMap();
+        }
+        if (contextMap instanceof ReadOnlyThreadContextMap) {
+            readOnlyContextMap = (ReadOnlyThreadContextMap) contextMap;
+        } else {
+            readOnlyContextMap = null;
+        }
     }
 
     /**
@@ -236,7 +243,7 @@ public final class ThreadContext {
      * @param value The key value.
      */
     public static void put(final String key, final String value) {
-        map().put(key, value);
+        contextMap.put(key, value);
     }
 
     /**
@@ -252,8 +259,8 @@ public final class ThreadContext {
      * @since 2.13.0
      */
     public static void putIfNull(final String key, final String value) {
-        if (!map().containsKey(key)) {
-            map().put(key, value);
+        if(!contextMap.containsKey(key)) {
+            contextMap.put(key, value);
         }
     }
 
@@ -267,7 +274,15 @@ public final class ThreadContext {
      * @since 2.7
      */
     public static void putAll(final Map<String, String> m) {
-        map().putAll(m);
+        if (contextMap instanceof ThreadContextMap2) {
+            ((ThreadContextMap2) contextMap).putAll(m);
+        } else if (contextMap instanceof DefaultThreadContextMap) {
+            ((DefaultThreadContextMap) contextMap).putAll(m);
+        } else {
+            for (final Map.Entry<String, String> entry: m.entrySet()) {
+                contextMap.put(entry.getKey(), entry.getValue());
+            }
+        }
     }
 
     /**
@@ -281,7 +296,7 @@ public final class ThreadContext {
      * @return The value associated with the key or null.
      */
     public static String get(final String key) {
-        return map().get(key);
+        return contextMap.get(key);
     }
 
     /**
@@ -290,7 +305,7 @@ public final class ThreadContext {
      * @param key The key to remove.
      */
     public static void remove(final String key) {
-        map().remove(key);
+        contextMap.remove(key);
     }
 
     /**
@@ -301,14 +316,22 @@ public final class ThreadContext {
      * @since 2.8
      */
     public static void removeAll(final Iterable<String> keys) {
-        map().removeAll(keys);
+        if (contextMap instanceof CleanableThreadContextMap) {
+            ((CleanableThreadContextMap) contextMap).removeAll(keys);
+        } else if (contextMap instanceof DefaultThreadContextMap) {
+            ((DefaultThreadContextMap) contextMap).removeAll(keys);
+        } else {
+            for (final String key : keys) {
+                contextMap.remove(key);
+            }
+        }
     }
 
     /**
      * Clears the context map.
      */
     public static void clearMap() {
-        map().clear();
+        contextMap.clear();
     }
 
     /**
@@ -326,7 +349,7 @@ public final class ThreadContext {
      * @return True if the key is in the context, false otherwise.
      */
     public static boolean containsKey(final String key) {
-        return map().containsKey(key);
+        return contextMap.containsKey(key);
     }
 
     /**
@@ -335,7 +358,7 @@ public final class ThreadContext {
      * @return a mutable copy of the context.
      */
     public static Map<String, String> getContext() {
-        return map().getCopy();
+        return contextMap.getCopy();
     }
 
     /**
@@ -344,7 +367,7 @@ public final class ThreadContext {
      * @return An immutable view of the ThreadContext Map.
      */
     public static Map<String, String> getImmutableContext() {
-        final Map<String, String> map = map().getImmutableMapOrNull();
+        final Map<String, String> map = contextMap.getImmutableMapOrNull();
         return map == null ? EMPTY_MAP : map;
     }
 
@@ -358,14 +381,14 @@ public final class ThreadContext {
      * </p>
      *
      * @return the internal data structure used to store thread context key-value pairs or {@code null}
-     * @see ThreadContextMap.Factory
+     * @see ThreadContextMapFactory
      * @see DefaultThreadContextMap
      * @see org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap
      * @see org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap
      * @since 2.8
      */
     public static ReadOnlyThreadContextMap getThreadContextMap() {
-        return readOnlyMap();
+        return readOnlyContextMap;
     }
 
     /**
@@ -374,14 +397,14 @@ public final class ThreadContext {
      * @return true if the Map is empty, false otherwise.
      */
     public static boolean isEmpty() {
-        return map().isEmpty();
+        return contextMap.isEmpty();
     }
 
     /**
      * Clears the stack for this thread.
      */
     public static void clearStack() {
-        stack().clear();
+        contextStack.clear();
     }
 
     /**
@@ -390,7 +413,7 @@ public final class ThreadContext {
      * @return A copy of this thread's stack.
      */
     public static ContextStack cloneStack() {
-        return stack().copy();
+        return contextStack.copy();
     }
 
     /**
@@ -399,7 +422,7 @@ public final class ThreadContext {
      * @return an immutable copy of the ThreadContext stack.
      */
     public static ContextStack getImmutableStack() {
-        final ContextStack result = stack().getImmutableStackOrNull();
+        final ContextStack result = contextStack.getImmutableStackOrNull();
         return result == null ? EMPTY_STACK : result;
     }
 
@@ -409,11 +432,9 @@ public final class ThreadContext {
      * @param stack The stack to use.
      */
     public static void setStack(final Collection<String> stack) {
-        final LoggingSystem system = LoggingSystem.getInstance();
-        if (stack.isEmpty() || !system.isContextStackEnabled()) {
+        if (stack.isEmpty() || !useStack) {
             return;
         }
-        final ThreadContextStack contextStack = stack();
         contextStack.clear();
         contextStack.addAll(stack);
     }
@@ -426,7 +447,7 @@ public final class ThreadContext {
      * @see #trim
      */
     public static int getDepth() {
-        return stack().getDepth();
+        return contextStack.getDepth();
     }
 
     /**
@@ -440,7 +461,7 @@ public final class ThreadContext {
      * @return String The innermost diagnostic context.
      */
     public static String pop() {
-        return stack().pop();
+        return contextStack.pop();
     }
 
     /**
@@ -454,7 +475,7 @@ public final class ThreadContext {
      * @return String The innermost diagnostic context.
      */
     public static String peek() {
-        return stack().peek();
+        return contextStack.peek();
     }
 
     /**
@@ -467,7 +488,7 @@ public final class ThreadContext {
      * @param message The new diagnostic context information.
      */
     public static void push(final String message) {
-        stack().push(message);
+        contextStack.push(message);
     }
 
     /**
@@ -483,7 +504,7 @@ public final class ThreadContext {
      * @param args Parameters for the message.
      */
     public static void push(final String message, final Object... args) {
-        stack().push(ParameterizedMessage.format(message, args));
+        contextStack.push(ParameterizedMessage.format(message, args));
     }
 
     /**
@@ -503,7 +524,7 @@ public final class ThreadContext {
      * </p>
      */
     public static void removeStack() {
-        stack().clear();
+        contextStack.clear();
     }
 
     /**
@@ -539,7 +560,7 @@ public final class ThreadContext {
      * @param depth The number of elements to keep.
      */
     public static void trim(final int depth) {
-        stack().trim(depth);
+        contextStack.trim(depth);
     }
 
     /**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/BindingMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/BindingMap.java
deleted file mode 100644
index 570a0a516a..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/BindingMap.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.internal;
-
-import org.apache.logging.log4j.util3.Cast;
-
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Supplier;
-
-/**
- * Mapping between classes and instance suppliers.
- */
-public class BindingMap {
-    private final Map<Class<?>, Binding<?>> bindings = new ConcurrentHashMap<>();
-
-    private <T> Binding<T> getBinding(final Class<T> key) {
-        return Cast.cast(bindings.get(key));
-    }
-
-    private <T> Supplier<? extends T> getSupplier(final Class<T> key) {
-        final Binding<T> binding = getBinding(key);
-        return binding == null ? null : binding.getSupplier();
-    }
-
-    public <T> Supplier<? extends T> getOrBindSupplier(final Class<T> key, final Supplier<? extends T> newSupplier) {
-        final Binding<T> binding = Cast.cast(bindings.computeIfAbsent(key, ignored -> Binding.bind(key, newSupplier)));
-        return binding.getSupplier();
-    }
-
-    public <T> T getInstance(final Class<T> key) {
-        final Supplier<? extends T> supplier = getSupplier(key);
-        if (supplier == null) {
-            throw new NoSuchElementException("No binding registered for " + key);
-        }
-        return supplier.get();
-    }
-
-    public <T> BindingMap bind(final Class<T> key, final Supplier<? extends T> supplier) {
-        bindings.put(key, Binding.bind(key, supplier));
-        return this;
-    }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLoggingSystemProvider.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLoggingSystemProvider.java
deleted file mode 100644
index ac10f9ca89..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLoggingSystemProvider.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.spi;
-
-import org.apache.logging.log4j.internal.BindingMap;
-import org.apache.logging.log4j.util.Lazy;
-import org.apache.logging.log4j.util.PropertyEnvironment;
-import org.apache.logging.log4j.util.ReflectionUtil;
-import org.apache.logging.log4j.util3.PropertiesUtil;
-
-/**
- * Abstract base class for logging system providers.
- *
- * @param <F> type of LoggerContextFactory this provider uses
- */
-public abstract class AbstractLoggingSystemProvider<F extends LoggerContextFactory> implements LoggingSystemProvider {
-    private final BindingMap bindings;
-
-    protected AbstractLoggingSystemProvider() {
-        bindings = new BindingMap();
-        bindings.bind(PropertyEnvironment.class, PropertiesUtil::getProperties)
-                .bind(LoggerContextFactory.class, Lazy.lazy(this::createLoggerContextFactory))
-                .bind(ThreadContextMap.Factory.class, this::createContextMapFactory)
-                .bind(ThreadContextStack.Factory.class, this::createContextStackFactory);
-    }
-
-    protected abstract F createLoggerContextFactory();
-
-    protected ThreadContextMap.Factory createContextMapFactory() {
-        return new DefaultContextMapFactory(getEnvironment());
-    }
-
-    protected ThreadContextStack.Factory createContextStackFactory() {
-        return new DefaultContextStackFactory(getEnvironment());
-    }
-
-    protected <T> T newInstance(final Class<T> type) {
-        return ReflectionUtil.instantiate(type);
-    }
-
-    @Override
-    public <T> T getInstance(final Class<T> clazz) {
-        return bindings.getOrBindSupplier(clazz, () -> newInstance(clazz)).get();
-    }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java
index 31705b36f1..7b9ad8a76d 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java
@@ -16,11 +16,12 @@
  */
 package org.apache.logging.log4j.spi;
 
+import org.apache.logging.log4j.util.LazyBoolean;
+import org.apache.logging.log4j.util.LazyInt;
 import org.apache.logging.log4j.util.PropertyEnvironment;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.SortedArrayStringMap;
 import org.apache.logging.log4j.util.StringMap;
-import org.apache.logging.log4j.util3.Constants;
 import org.apache.logging.log4j.util3.PropertiesUtil;
 
 import java.util.Collections;
@@ -37,25 +38,55 @@ import java.util.Objects;
  *
  * @since 2.7
  */
-class CopyOnWriteSortedArrayThreadContextMap implements ReadOnlyThreadContextMap, ThreadContextMap, CopyOnWrite {
+class CopyOnWriteSortedArrayThreadContextMap implements ReadOnlyThreadContextMap, ObjectThreadContextMap, CopyOnWrite {
+
+    /**
+     * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain
+     * {@code ThreadLocal} (value is not "true") in the implementation.
+     */
+    public static final String INHERITABLE_MAP = "isThreadContextMapInheritable";
+
+    /**
+     * The default initial capacity.
+     */
+    protected static final int DEFAULT_INITIAL_CAPACITY = 16;
+
+    /**
+     * System property name that can be used to control the data structure's initial capacity.
+     */
+    protected static final String PROPERTY_NAME_INITIAL_CAPACITY = "log4j2.ThreadContext.initial.capacity";
 
     private static final StringMap EMPTY_CONTEXT_DATA = new SortedArrayStringMap(1);
+    
+    private static final LazyInt initialCapacity = new LazyInt(() -> PropertiesUtil.getProperties()
+            .getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY));
+    private static final LazyBoolean inheritableMap = new LazyBoolean(() -> PropertiesUtil.getProperties()
+            .getBooleanProperty(INHERITABLE_MAP));
 
+    /**
+     * Initializes static variables based on system properties. Normally called when this class is initialized by the VM
+     * and when Log4j is reconfigured.
+     */
+    static void init(final PropertyEnvironment properties) {
+        initialCapacity.setAsInt(properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY));
+        inheritableMap.setAsBoolean(properties.getBooleanProperty(INHERITABLE_MAP));
+    }
+    
     static {
         EMPTY_CONTEXT_DATA.freeze();
     }
 
-    private final int initialCapacity;
     private final ThreadLocal<StringMap> localMap;
 
     public CopyOnWriteSortedArrayThreadContextMap() {
-        this(PropertiesUtil.getProperties());
+        this.localMap = createThreadLocalMap();
     }
 
-    CopyOnWriteSortedArrayThreadContextMap(final int initialCapacity, final boolean inheritableMap) {
-        this.initialCapacity = initialCapacity;
-        if (inheritableMap) {
-            this.localMap = new InheritableThreadLocal<>() {
+    // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured.
+    // (This method is package protected for JUnit tests.)
+    private ThreadLocal<StringMap> createThreadLocalMap() {
+        if (inheritableMap.getAsBoolean()) {
+            return new InheritableThreadLocal<StringMap>() {
                 @Override
                 protected StringMap childValue(final StringMap parentValue) {
                     if (parentValue == null) {
@@ -66,18 +97,9 @@ class CopyOnWriteSortedArrayThreadContextMap implements ReadOnlyThreadContextMap
                     return stringMap;
                 }
             };
-        } else {
-            this.localMap = new ThreadLocal<>();
         }
-    }
-
-    // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured.
-    // (This method is package protected for JUnit tests.)
-    CopyOnWriteSortedArrayThreadContextMap(final PropertyEnvironment properties) {
-        this(
-                properties.getIntegerProperty(Constants.PROPERTY_NAME_INITIAL_CAPACITY, Constants.DEFAULT_INITIAL_CAPACITY),
-                properties.getBooleanProperty(Constants.INHERITABLE_MAP)
-        );
+        // if not inheritable, return plain ThreadLocal with null as initial value
+        return new ThreadLocal<>();
     }
 
     /**
@@ -88,7 +110,7 @@ class CopyOnWriteSortedArrayThreadContextMap implements ReadOnlyThreadContextMap
      * @return an implementation of the {@code StringMap} used to back this thread context map
      */
     protected StringMap createStringMap() {
-        return new SortedArrayStringMap(initialCapacity);
+        return new SortedArrayStringMap(initialCapacity.getAsInt());
     }
 
     /**
@@ -148,13 +170,13 @@ class CopyOnWriteSortedArrayThreadContextMap implements ReadOnlyThreadContextMap
 
     @Override
     public String get(final String key) {
-        return getValue(key);
+        return (String) getValue(key);
     }
 
     @Override
     public <V> V getValue(final String key) {
         final StringMap map = localMap.get();
-        return map == null ? null : map.getValue(key);
+        return map == null ? null : map.<V>getValue(key);
     }
 
     @Override
@@ -195,7 +217,7 @@ class CopyOnWriteSortedArrayThreadContextMap implements ReadOnlyThreadContextMap
     @Override
     public Map<String, String> getCopy() {
         final StringMap map = localMap.get();
-        return map == null ? new HashMap<>() : map.toMap();
+        return map == null ? new HashMap<String, String>() : map.toMap();
     }
 
     /**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultContextMapFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultContextMapFactory.java
deleted file mode 100644
index 9164690863..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultContextMapFactory.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.spi;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.ThreadContext;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.PropertyEnvironment;
-import org.apache.logging.log4j.util.ReflectionUtil;
-import org.apache.logging.log4j.util3.Constants;
-import org.apache.logging.log4j.util3.LoaderUtil;
-import org.apache.logging.log4j.util3.PropertiesUtil;
-
-/**
- * Creates the ThreadContextMap instance used by the ThreadContext.
- * <p>
- * If {@linkplain Constants#isThreadLocalsEnabled() Log4j can use ThreadLocals}, a garbage-free StringMap-based context map can
- * be installed by setting system property {@code log4j2.garbagefree.threadContextMap} to {@code true}.
- * </p><p>
- * Furthermore, any custom {@code ThreadContextMap} can be installed by setting system property
- * {@code log4j2.threadContextMap} to the fully qualified class name of the class implementing the
- * {@code ThreadContextMap} interface. (Also implement the {@code ReadOnlyThreadContextMap} interface if your custom
- * {@code ThreadContextMap} implementation should be accessible to applications via the
- * {@link ThreadContext#getThreadContextMap()} method.)
- * </p><p>
- * Instead of system properties, the above can also be specified in a properties file named
- * {@code log4j2.component.properties} in the classpath.
- * </p>
- *
- * @see ThreadContextMap
- * @see ReadOnlyThreadContextMap
- * @see org.apache.logging.log4j.ThreadContext
- * @since 3.0.0
- */
-public class DefaultContextMapFactory implements ThreadContextMap.Factory {
-    private static final String THREAD_CONTEXT_KEY = "log4j2.threadContextMap";
-    private static final String GC_FREE_THREAD_CONTEXT_KEY = "log4j2.garbagefree.threadContextMap";
-    static final String DISABLE_ALL = "disableThreadContext";
-    static final String DISABLE_MAP = "disableThreadContextMap";
-
-    private final String threadContextMapName;
-    private final boolean mapEnabled;
-    private final boolean garbageFreeEnabled;
-    private final boolean inheritableMap;
-    private final boolean threadLocalsEnabled;
-    private final int initialCapacity;
-    private final Logger logger = StatusLogger.getLogger();
-
-    public DefaultContextMapFactory() {
-        this(PropertiesUtil.getProperties());
-    }
-
-    public DefaultContextMapFactory(final PropertyEnvironment properties) {
-        threadContextMapName = properties.getStringProperty(THREAD_CONTEXT_KEY);
-        garbageFreeEnabled = properties.getBooleanProperty(GC_FREE_THREAD_CONTEXT_KEY);
-        mapEnabled = !(properties.getBooleanProperty(DISABLE_ALL) || properties.getBooleanProperty(DISABLE_MAP));
-        inheritableMap = properties.getBooleanProperty(Constants.INHERITABLE_MAP);
-        threadLocalsEnabled = properties.getBooleanProperty(Constants.THREAD_LOCALS_ENABLED);
-        initialCapacity = properties.getIntegerProperty(Constants.PROPERTY_NAME_INITIAL_CAPACITY, Constants.DEFAULT_INITIAL_CAPACITY);
-    }
-
-    @Override
-    public ThreadContextMap createThreadContextMap() {
-        if (!mapEnabled) {
-            return new NoOpThreadContextMap();
-        }
-        if (threadContextMapName != null) {
-            final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
-            try {
-                final Class<? extends ThreadContextMap> threadContextMapClass = loader.loadClass(threadContextMapName)
-                        .asSubclass(ThreadContextMap.class);
-                return ReflectionUtil.instantiate(threadContextMapClass);
-            } catch (final ClassNotFoundException e) {
-                logger.error("Unable to locate configured ThreadContextMap {}", threadContextMapName);
-            } catch (final Exception e) {
-                logger.error("Unable to create configured ThreadContextMap {}", threadContextMapName, e);
-            }
-        }
-        if (threadLocalsEnabled) {
-            if (garbageFreeEnabled) {
-                return new GarbageFreeSortedArrayThreadContextMap(initialCapacity, inheritableMap);
-            }
-            return new CopyOnWriteSortedArrayThreadContextMap(initialCapacity, inheritableMap);
-        }
-        return new DefaultThreadContextMap(true, inheritableMap);
-    }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultContextStackFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultContextStackFactory.java
deleted file mode 100644
index 93784275ca..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultContextStackFactory.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.spi;
-
-import org.apache.logging.log4j.util.PropertyEnvironment;
-import org.apache.logging.log4j.util3.PropertiesUtil;
-
-import static org.apache.logging.log4j.spi.DefaultContextMapFactory.DISABLE_ALL;
-
-public class DefaultContextStackFactory implements ThreadContextStack.Factory {
-    static final String DISABLE_STACK = "disableThreadContextStack";
-    private final boolean stackEnabled;
-
-    public DefaultContextStackFactory() {
-        this(PropertiesUtil.getProperties());
-    }
-
-    public DefaultContextStackFactory(final PropertyEnvironment properties) {
-        this(!(properties.getBooleanProperty(DISABLE_ALL) || properties.getBooleanProperty(DISABLE_STACK)));
-    }
-
-    public DefaultContextStackFactory(final boolean stackEnabled) {
-        this.stackEnabled = stackEnabled;
-    }
-
-    @Override
-    public ThreadContextStack createThreadContextStack() {
-        return new DefaultThreadContextStack(stackEnabled);
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return stackEnabled;
-    }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java
index 16e0a21d56..c5bb0e2e24 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java
@@ -17,10 +17,10 @@
 package org.apache.logging.log4j.spi;
 
 import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.LazyBoolean;
 import org.apache.logging.log4j.util.PropertyEnvironment;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.TriConsumer;
-import org.apache.logging.log4j.util3.Constants;
 import org.apache.logging.log4j.util3.PropertiesUtil;
 
 import java.util.Collections;
@@ -28,9 +28,6 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 
-import static org.apache.logging.log4j.spi.DefaultContextMapFactory.DISABLE_ALL;
-import static org.apache.logging.log4j.spi.DefaultContextMapFactory.DISABLE_MAP;
-
 /**
  * The actual ThreadContext Map. A new ThreadContext Map is created each time it is updated and the Map stored is always
  * immutable. This means the Map can be passed to other threads without concern that it will be updated. Since it is
@@ -44,15 +41,18 @@ public class DefaultThreadContextMap implements ThreadContextMap, ReadOnlyString
      * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain
      * {@code ThreadLocal} (value is not "true") in the implementation.
      */
-    public static final String INHERITABLE_MAP = Constants.INHERITABLE_MAP;
+    public static final String INHERITABLE_MAP = "isThreadContextMapInheritable";
 
     private final boolean useMap;
-    private final boolean inheritableMap;
     private final ThreadLocal<Map<String, String>> localMap;
 
+    private static final LazyBoolean inheritableMap = new LazyBoolean(() -> PropertiesUtil.getProperties()
+            .getBooleanProperty(INHERITABLE_MAP));
+
     // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured.
-    static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean isMapEnabled, final boolean isInheritableMap) {
-        if (isInheritableMap) {
+    // (This method is package protected for JUnit tests.)
+    static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean isMapEnabled) {
+        if (inheritableMap.getAsBoolean()) {
             return new InheritableThreadLocal<>() {
                 @Override
                 protected Map<String, String> childValue(final Map<String, String> parentValue) {
@@ -66,23 +66,17 @@ public class DefaultThreadContextMap implements ThreadContextMap, ReadOnlyString
         return new ThreadLocal<>();
     }
 
+    static void init(final PropertyEnvironment properties) {
+        inheritableMap.setAsBoolean(properties.getBooleanProperty(INHERITABLE_MAP));
+    }
+    
     public DefaultThreadContextMap() {
         this(true);
     }
 
     public DefaultThreadContextMap(final boolean useMap) {
-        this(useMap, PropertiesUtil.getProperties().getBooleanProperty(Constants.INHERITABLE_MAP));
-    }
-
-    public DefaultThreadContextMap(final boolean useMap, final boolean inheritableMap) {
         this.useMap = useMap;
-        this.inheritableMap = inheritableMap;
-        this.localMap = createThreadLocalMap(useMap, inheritableMap);
-    }
-
-    DefaultThreadContextMap(final PropertyEnvironment properties) {
-        this(!(properties.getBooleanProperty(DISABLE_ALL) || properties.getBooleanProperty(DISABLE_MAP)),
-                properties.getBooleanProperty(Constants.INHERITABLE_MAP));
+        this.localMap = createThreadLocalMap(useMap);
     }
 
     @Override
@@ -243,8 +237,4 @@ public class DefaultThreadContextMap implements ThreadContextMap, ReadOnlyString
         final Map<String, String> otherMap = other.getImmutableMapOrNull();
         return Objects.equals(map, otherMap);
     }
-
-    boolean isInheritable() {
-        return inheritableMap;
-    }
 }
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
index 766891118d..2cba6d3ad0 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
@@ -16,11 +16,12 @@
  */
 package org.apache.logging.log4j.spi;
 
+import org.apache.logging.log4j.util.LazyBoolean;
+import org.apache.logging.log4j.util.LazyInt;
 import org.apache.logging.log4j.util.PropertyEnvironment;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.SortedArrayStringMap;
 import org.apache.logging.log4j.util.StringMap;
-import org.apache.logging.log4j.util3.Constants;
 import org.apache.logging.log4j.util3.PropertiesUtil;
 
 import java.util.Collections;
@@ -37,34 +38,48 @@ import java.util.Objects;
  * </p>
  * @since 2.7
  */
-class GarbageFreeSortedArrayThreadContextMap implements ReadOnlyThreadContextMap, ThreadContextMap  {
+class GarbageFreeSortedArrayThreadContextMap implements ReadOnlyThreadContextMap, ObjectThreadContextMap  {
+
+    /**
+     * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain
+     * {@code ThreadLocal} (value is not "true") in the implementation.
+     */
+    public static final String INHERITABLE_MAP = "isThreadContextMapInheritable";
+
+    /**
+     * The default initial capacity.
+     */
+    protected static final int DEFAULT_INITIAL_CAPACITY = 16;
+
+    /**
+     * System property name that can be used to control the data structure's initial capacity.
+     */
+    protected static final String PROPERTY_NAME_INITIAL_CAPACITY = "log4j2.ThreadContext.initial.capacity";
 
     protected final ThreadLocal<StringMap> localMap;
 
-    private final int initialCapacity;
-    private final boolean inheritableMap;
+    private static final LazyInt initialCapacity = new LazyInt(() -> PropertiesUtil.getProperties()
+            .getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY));
+    private static final LazyBoolean inheritableMap = new LazyBoolean(() -> PropertiesUtil.getProperties()
+            .getBooleanProperty(INHERITABLE_MAP));
 
-    public GarbageFreeSortedArrayThreadContextMap() {
-        this(PropertiesUtil.getProperties());
+    /**
+     * Initializes static variables based on system properties. Normally called when this class is initialized by the VM
+     * and when Log4j is reconfigured.
+     */
+    static void init(final PropertyEnvironment properties) {
+        initialCapacity.setAsInt(properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY));
+        inheritableMap.setAsBoolean(properties.getBooleanProperty(INHERITABLE_MAP));
     }
 
-    GarbageFreeSortedArrayThreadContextMap(final int initialCapacity, final boolean inheritableMap) {
-        this.initialCapacity = initialCapacity;
-        this.inheritableMap = inheritableMap;
+    public GarbageFreeSortedArrayThreadContextMap() {
         this.localMap = createThreadLocalMap();
     }
 
-    GarbageFreeSortedArrayThreadContextMap(final PropertyEnvironment properties) {
-        this(
-                properties.getIntegerProperty(Constants.PROPERTY_NAME_INITIAL_CAPACITY, Constants.DEFAULT_INITIAL_CAPACITY),
-                properties.getBooleanProperty(Constants.INHERITABLE_MAP)
-        );
-    }
-
     // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured.
     // (This method is package protected for JUnit tests.)
     private ThreadLocal<StringMap> createThreadLocalMap() {
-        if (inheritableMap) {
+        if (inheritableMap.getAsBoolean()) {
             return new InheritableThreadLocal<StringMap>() {
                 @Override
                 protected StringMap childValue(final StringMap parentValue) {
@@ -84,7 +99,7 @@ class GarbageFreeSortedArrayThreadContextMap implements ReadOnlyThreadContextMap
      * @return an implementation of the {@code StringMap} used to back this thread context map
      */
     protected StringMap createStringMap() {
-        return new SortedArrayStringMap(initialCapacity);
+        return new SortedArrayStringMap(initialCapacity.getAsInt());
     }
 
     /**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LegacyLoggingSystemProvider.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LegacyLoggingSystemProvider.java
deleted file mode 100644
index 702a8f6358..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LegacyLoggingSystemProvider.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.spi;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util3.Cast;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.net.URL;
-import java.util.Properties;
-
-/**
- * Logging system provider for wrapping legacy Log4j providers specified via properties files.
- */
-public class LegacyLoggingSystemProvider<F extends LoggerContextFactory> extends AbstractLoggingSystemProvider<F> {
-    /**
-     * Property name to set for a Log4j 2 provider to specify the priority of this implementation.
-     */
-    public static final String FACTORY_PRIORITY = "FactoryPriority";
-    /**
-     * Property name to set to the implementation of {@link org.apache.logging.log4j.spi.ThreadContextMap}.
-     */
-    public static final String THREAD_CONTEXT_MAP = "ThreadContextMap";
-    /**
-     * Property name to set to the implementation of {@link org.apache.logging.log4j.spi.LoggerContextFactory}.
-     */
-    public static final String LOGGER_CONTEXT_FACTORY = "LoggerContextFactory";
-    private static final String API_VERSION = "Log4jAPIVersion";
-
-    private final Logger logger = StatusLogger.getLogger();
-    private final URL url;
-    private final WeakReference<ClassLoader> classLoaderRef;
-    private final int priority;
-    private final String version;
-    private final String className;
-    private final String threadContextMap;
-
-    public LegacyLoggingSystemProvider(final URL url, final ClassLoader classLoader) throws IOException {
-        this(loadFromUrl(url), url, classLoader);
-    }
-
-    public LegacyLoggingSystemProvider(final Properties props, final URL url, final ClassLoader classLoader) {
-        this.url = url;
-        this.classLoaderRef = new WeakReference<>(classLoader);
-        final String weight = props.getProperty(FACTORY_PRIORITY);
-        priority = weight == null ? -1 : Integer.parseInt(weight);
-        version = props.getProperty(API_VERSION);
-        className = props.getProperty(LOGGER_CONTEXT_FACTORY);
-        threadContextMap = props.getProperty(THREAD_CONTEXT_MAP);
-    }
-
-    @Override
-    public int getPriority() {
-        return priority;
-    }
-
-    @Override
-    public String getVersion() {
-        return version;
-    }
-
-    @Override
-    protected F createLoggerContextFactory() {
-        if (className == null) {
-            logger.error("No class name defined for Log4j provider at {}", url);
-            return null;
-        }
-        final ClassLoader classLoader = classLoaderRef.get();
-        if (classLoader == null) {
-            logger.error("No ClassLoader defined for Log4j provider at {}", url);
-            return null;
-        }
-        try {
-            final Class<?> clazz = classLoader.loadClass(className);
-            final Class<F> factoryClass = Cast.cast(clazz.asSubclass(LoggerContextFactory.class));
-            return getInstance(factoryClass);
-        } catch (final Exception e) {
-            logger.error("Unable to create class {} specified in {}", className, url, e);
-        }
-        return null;
-    }
-
-    @Override
-    protected ThreadContextMap.Factory createContextMapFactory() {
-        if (threadContextMap != null) {
-            final ClassLoader classLoader = classLoaderRef.get();
-            if (classLoader != null) {
-                try {
-                    final Class<?> mapClass = classLoader.loadClass(threadContextMap);
-                    final Class<? extends ThreadContextMap> contextMapClass = mapClass.asSubclass(ThreadContextMap.class);
-                    return () -> getInstance(contextMapClass);
-                } catch (final ClassNotFoundException e) {
-                    logger.error("Unable to create class {} specified in {}", threadContextMap, url, e);
-                }
-            }
-        }
-        return super.createContextMapFactory();
-    }
-
-    private static Properties loadFromUrl(final URL url) throws IOException {
-        try (final InputStream in = url.openStream()) {
-            final Properties properties = new Properties();
-            properties.load(in);
-            return properties;
-        }
-    }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProvider.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProvider.java
deleted file mode 100644
index 4046a1a47f..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProvider.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.spi;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.util.PropertyEnvironment;
-
-/**
- * Central service provider for the logging system used by Log4j API. Implementations provide factory instances
- * for loggers, thread context maps, and thread context stacks, along with configuration properties.
- *
- * @see AbstractLoggingSystemProvider
- * @see PropertyEnvironment
- * @see LoggerContextFactory
- * @see ThreadContextMap.Factory
- * @see ThreadContextStack.Factory
- */
-public interface LoggingSystemProvider {
-
-    /**
-     * Returns the priority of this provider where larger numbers indicate higher priority.
-     */
-    int getPriority();
-
-    /**
-     * Returns the minimum valid API version this provider requires.
-     */
-    String getVersion();
-
-    /**
-     * Gets an instance of the specified class or interface. This should minimally support construction
-     * of classes with a default constructor along with knowledge of bindings of existing instances to interfaces.
-     * More sophisticated implementations may support dependency injection and configuration binding.
-     */
-    <T> T getInstance(final Class<T> clazz);
-
-    /**
-     * Returns the properties this logging system was configured with.
-     */
-    default PropertyEnvironment getEnvironment() {
-        return getInstance(PropertyEnvironment.class);
-    }
-
-    /**
-     * Returns the initialized LoggerContextFactory to use for managing {@link LoggerContext} and
-     * {@link Logger} instances.
-     */
-    default LoggerContextFactory getLoggerContextFactory() {
-        return getInstance(LoggerContextFactory.class);
-    }
-
-    /**
-     * Returns the initialized {@link ThreadContextMap.Factory} to use for creating {@link ThreadContextMap} instances.
-     */
-    default ThreadContextMap.Factory getContextMapFactory() {
-        return getInstance(ThreadContextMap.Factory.class);
-    }
-
-    /**
-     * Returns the initialized {@link ThreadContextStack.Factory} to use for creating {@link ThreadContextStack}
-     * instances.
-     */
-    default ThreadContextStack.Factory getContextStackFactory() {
-        return getInstance(ThreadContextStack.Factory.class);
-    }
-
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java
new file mode 100644
index 0000000000..591c4c27a0
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.spi;
+
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.util.Properties;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * Model class for a Log4j 2 provider. The properties in this class correspond to the properties used in a
+ * {@code META-INF/log4j-provider.properties} file. Note that this class is automatically created by Log4j and should
+ * not be used by providers.
+ */
+public class Provider {
+    /**
+     * Property name to set for a Log4j 2 provider to specify the priority of this implementation.
+     */
+    public static final String FACTORY_PRIORITY = "FactoryPriority";
+    /**
+     * Property name to set to the implementation of {@link org.apache.logging.log4j.spi.ThreadContextMap}.
+     */
+    public static final String THREAD_CONTEXT_MAP = "ThreadContextMap";
+    /**
+     * Property name to set to the implementation of {@link org.apache.logging.log4j.spi.LoggerContextFactory}.
+     */
+    public static final String LOGGER_CONTEXT_FACTORY = "LoggerContextFactory";
+
+    private static final Integer DEFAULT_PRIORITY = Integer.valueOf(-1);
+    private static final Logger LOGGER = StatusLogger.getLogger();
+
+    private final Integer priority;
+    private final String className;
+    private final Class<? extends LoggerContextFactory> loggerContextFactoryClass;
+    private final String threadContextMap;
+    private final Class<? extends ThreadContextMap> threadContextMapClass;
+    private final String versions;
+    private final URL url;
+    private final WeakReference<ClassLoader> classLoader;
+
+    public Provider(final Properties props, final URL url, final ClassLoader classLoader) {
+        this.url = url;
+        this.classLoader = new WeakReference<>(classLoader);
+        final String weight = props.getProperty(FACTORY_PRIORITY);
+        priority = weight == null ? DEFAULT_PRIORITY : Integer.valueOf(weight);
+        className = props.getProperty(LOGGER_CONTEXT_FACTORY);
+        threadContextMap = props.getProperty(THREAD_CONTEXT_MAP);
+        loggerContextFactoryClass = null;
+        threadContextMapClass = null;
+        versions = null;
+    }
+
+    public Provider(final Integer priority, final String versions,
+                    final Class<? extends LoggerContextFactory> loggerContextFactoryClass) {
+        this(priority, versions, loggerContextFactoryClass, null);
+    }
+
+
+    public Provider(final Integer priority, final String versions,
+                    final Class<? extends LoggerContextFactory> loggerContextFactoryClass,
+                    final Class<? extends ThreadContextMap> threadContextMapClass) {
+        this.url = null;
+        this.classLoader = null;
+        this.priority = priority;
+        this.loggerContextFactoryClass = loggerContextFactoryClass;
+        this.threadContextMapClass = threadContextMapClass;
+        this.className = null;
+        this.threadContextMap = null;
+        this.versions = versions;
+    }
+
+    /**
+     * Returns the Log4j API versions supported by the implementation.
+     * @return A String containing the Log4j versions supported.
+     */
+    public String getVersions() {
+        return versions;
+    }
+
+    /**
+     * Gets the priority (natural ordering) of this Provider.
+     *
+     * @return the priority of this Provider
+     */
+    public Integer getPriority() {
+        return priority;
+    }
+
+    /**
+     * Gets the class name of the {@link org.apache.logging.log4j.spi.LoggerContextFactory} implementation of this
+     * Provider.
+     *
+     * @return the class name of a LoggerContextFactory implementation
+     */
+    public String getClassName() {
+        if (loggerContextFactoryClass != null) {
+            return loggerContextFactoryClass.getName();
+        }
+        return className;
+    }
+
+    /**
+     * Loads the {@link org.apache.logging.log4j.spi.LoggerContextFactory} class specified by this Provider.
+     *
+     * @return the LoggerContextFactory implementation class or {@code null} if there was an error loading it
+     */
+    public Class<? extends LoggerContextFactory> loadLoggerContextFactory() {
+        if (loggerContextFactoryClass != null) {
+            return loggerContextFactoryClass;
+        }
+        if (className == null) {
+            return null;
+        }
+        final ClassLoader loader = classLoader.get();
+        if (loader == null) {
+            return null;
+        }
+        try {
+            final Class<?> clazz = loader.loadClass(className);
+            if (LoggerContextFactory.class.isAssignableFrom(clazz)) {
+                return clazz.asSubclass(LoggerContextFactory.class);
+            }
+        } catch (final Exception e) {
+            LOGGER.error("Unable to create class {} specified in {}", className, url.toString(), e);
+        }
+        return null;
+    }
+
+    /**
+     * Gets the class name of the {@link org.apache.logging.log4j.spi.ThreadContextMap} implementation of this Provider.
+     *
+     * @return the class name of a ThreadContextMap implementation
+     */
+    public String getThreadContextMap() {
+        if (threadContextMapClass != null) {
+            return threadContextMapClass.getName();
+        }
+        return threadContextMap;
+    }
+
+    /**
+     * Loads the {@link org.apache.logging.log4j.spi.ThreadContextMap} class specified by this Provider.
+     *
+     * @return the ThreadContextMap implementation class or {@code null} if there was an error loading it
+     */
+    public Class<? extends ThreadContextMap> loadThreadContextMap() {
+        if (threadContextMapClass != null) {
+            return threadContextMapClass;
+        }
+        if (threadContextMap == null) {
+            return null;
+        }
+        final ClassLoader loader = classLoader.get();
+        if (loader == null) {
+            return null;
+        }
+        try {
+            final Class<?> clazz = loader.loadClass(threadContextMap);
+            if (ThreadContextMap.class.isAssignableFrom(clazz)) {
+                return clazz.asSubclass(ThreadContextMap.class);
+            }
+        } catch (final Exception e) {
+            LOGGER.error("Unable to create class {} specified in {}", threadContextMap, url.toString(), e);
+        }
+        return null;
+    }
+
+    /**
+     * Gets the URL containing this Provider's Log4j details.
+     *
+     * @return the URL corresponding to the Provider {@code META-INF/log4j-provider.properties} file
+     */
+    public URL getUrl() {
+        return url;
+    }
+    
+    @Override
+    public String toString() {
+        final StringBuilder result = new StringBuilder("Provider[");
+        if (!DEFAULT_PRIORITY.equals(priority)) {
+            result.append("priority=").append(priority).append(", ");
+        }
+        if (threadContextMap != null) {
+            result.append("threadContextMap=").append(threadContextMap).append(", ");
+        } else if (threadContextMapClass != null) {
+            result.append("threadContextMapClass=").append(threadContextMapClass.getName());
+        }
+        if (className != null) {
+            result.append("className=").append(className).append(", ");
+        } else if (loggerContextFactoryClass != null) {
+            result.append("class=").append(loggerContextFactoryClass.getName());
+        }
+        if (url != null) {
+            result.append("url=").append(url);
+        }
+        final ClassLoader loader;
+        if (classLoader == null || (loader = classLoader.get()) == null) {
+            result.append(", classLoader=null(not reachable)");
+        } else {
+            result.append(", classLoader=").append(loader);
+        }
+        result.append("]");
+        return result.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final Provider provider = (Provider) o;
+
+        if (priority != null ? !priority.equals(provider.priority) : provider.priority != null) {
+            return false;
+        }
+        if (className != null ? !className.equals(provider.className) : provider.className != null) {
+            return false;
+        }
+        if (loggerContextFactoryClass != null ? !loggerContextFactoryClass.equals(provider.loggerContextFactoryClass) : provider.loggerContextFactoryClass != null) {
+            return false;
+        }
+        return versions != null ? versions.equals(provider.versions) : provider.versions == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = priority != null ? priority.hashCode() : 0;
+        result = 31 * result + (className != null ? className.hashCode() : 0);
+        result = 31 * result + (loggerContextFactoryClass != null ? loggerContextFactoryClass.hashCode() : 0);
+        result = 31 * result + (versions != null ? versions.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java
new file mode 100644
index 0000000000..cb341640b9
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.spi;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util3.Constants;
+import org.apache.logging.log4j.util3.PropertiesUtil;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+import org.apache.logging.log4j.util3.ProviderUtil;
+import org.apache.logging.log4j.util.ReflectionUtil;
+
+/**
+ * Creates the ThreadContextMap instance used by the ThreadContext.
+ * <p>
+ * If {@linkplain Constants#isThreadLocalsEnabled() Log4j can use ThreadLocals}, a garbage-free StringMap-based context map can
+ * be installed by setting system property {@code log4j2.garbagefree.threadContextMap} to {@code true}.
+ * </p><p>
+ * Furthermore, any custom {@code ThreadContextMap} can be installed by setting system property
+ * {@code log4j2.threadContextMap} to the fully qualified class name of the class implementing the
+ * {@code ThreadContextMap} interface. (Also implement the {@code ReadOnlyThreadContextMap} interface if your custom
+ * {@code ThreadContextMap} implementation should be accessible to applications via the
+ * {@link ThreadContext#getThreadContextMap()} method.)
+ * </p><p>
+ * Instead of system properties, the above can also be specified in a properties file named
+ * {@code log4j2.component.properties} in the classpath.
+ * </p>
+ *
+ * @see ThreadContextMap
+ * @see ReadOnlyThreadContextMap
+ * @see org.apache.logging.log4j.ThreadContext
+ * @since 2.7
+ */
+public final class ThreadContextMapFactory {
+    private static final Logger LOGGER = StatusLogger.getLogger();
+    private static final String THREAD_CONTEXT_KEY = "log4j2.threadContextMap";
+    private static final String GC_FREE_THREAD_CONTEXT_KEY = "log4j2.garbagefree.threadContextMap";
+    
+    private static boolean GcFreeThreadContextKey;
+    private static String ThreadContextMapName;
+
+    static {
+        initPrivate(PropertiesUtil.getProperties());
+    }
+    
+    /**
+     * Initializes static variables based on system properties. Normally called when this class is initialized by the VM
+     * and when Log4j is reconfigured.
+     */
+    public static void init() {
+        final PropertyEnvironment properties = PropertiesUtil.getProperties();
+        CopyOnWriteSortedArrayThreadContextMap.init(properties);
+        GarbageFreeSortedArrayThreadContextMap.init(properties);
+        DefaultThreadContextMap.init(properties);
+        initPrivate(properties);
+    }
+
+    /**
+     * Initializes static variables based on system properties. Normally called when this class is initialized by the VM
+     * and when Log4j is reconfigured.
+     */
+    private static void initPrivate(final PropertyEnvironment properties) {
+        ThreadContextMapName = properties.getStringProperty(THREAD_CONTEXT_KEY);
+        GcFreeThreadContextKey = properties.getBooleanProperty(GC_FREE_THREAD_CONTEXT_KEY);
+    }
+    
+    private ThreadContextMapFactory() {
+    }
+
+    public static ThreadContextMap createThreadContextMap() {
+        final ClassLoader cl = ProviderUtil.findClassLoader();
+        ThreadContextMap result = null;
+        if (ThreadContextMapName != null) {
+            try {
+                final Class<?> clazz = cl.loadClass(ThreadContextMapName);
+                if (ThreadContextMap.class.isAssignableFrom(clazz)) {
+                    result = ReflectionUtil.instantiate(clazz.asSubclass(ThreadContextMap.class));
+                }
+            } catch (final ClassNotFoundException cnfe) {
+                LOGGER.error("Unable to locate configured ThreadContextMap {}", ThreadContextMapName);
+            } catch (final Exception ex) {
+                LOGGER.error("Unable to create configured ThreadContextMap {}", ThreadContextMapName, ex);
+            }
+        }
+        if (result == null && ProviderUtil.hasProviders() && LogManager.getFactory() != null) { //LOG4J2-1658
+            final String factoryClassName = LogManager.getFactory().getClass().getName();
+            for (final Provider provider : ProviderUtil.getProviders()) {
+                if (factoryClassName.equals(provider.getClassName())) {
+                    final Class<? extends ThreadContextMap> clazz = provider.loadThreadContextMap();
+                    if (clazz != null) {
+                        try {
+                            result = ReflectionUtil.instantiate(clazz);
+                            break;
+                        } catch (final Exception e) {
+                            LOGGER.error("Unable to locate or load configured ThreadContextMap {}",
+                                    provider.getThreadContextMap(), e);
+                            result = createDefaultThreadContextMap();
+                        }
+                    }
+                }
+            }
+        }
+        if (result == null) {
+            result = createDefaultThreadContextMap();
+        }
+        return result;
+    }
+
+    private static ThreadContextMap createDefaultThreadContextMap() {
+        if (Constants.isThreadLocalsEnabled()) {
+            if (GcFreeThreadContextKey) {
+                return new GarbageFreeSortedArrayThreadContextMap();
+            }
+            return new CopyOnWriteSortedArrayThreadContextMap();
+        }
+        return new DefaultThreadContextMap(true);
+    }
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/Binding.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/LazyInt.java
similarity index 50%
rename from log4j-api/src/main/java/org/apache/logging/log4j/internal/Binding.java
rename to log4j-api/src/main/java/org/apache/logging/log4j/util/LazyInt.java
index 28e4cd70f6..5b4d2fe6ca 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/Binding.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/LazyInt.java
@@ -15,32 +15,38 @@
  * limitations under the license.
  */
 
-package org.apache.logging.log4j.internal;
+package org.apache.logging.log4j.util;
 
-import java.util.function.Supplier;
+import java.util.function.IntSupplier;
 
-/**
- * Combination of a class key and a supplier function to get a bound value for the key.
- * @param <T> type of key
- */
-public class Binding<T> {
-    private final Class<T> key;
-    private final Supplier<? extends T> supplier;
+public class LazyInt implements IntSupplier {
+    private final IntSupplier supplier;
+    private volatile boolean initialized;
+    private volatile int value;
 
-    private Binding(final Class<T> key, final Supplier<? extends T> supplier) {
-        this.key = key;
+    public LazyInt(final IntSupplier supplier) {
         this.supplier = supplier;
     }
 
-    public Class<T> getKey() {
-        return key;
-    }
-
-    public Supplier<? extends T> getSupplier() {
-        return supplier;
+    @Override
+    public int getAsInt() {
+        boolean uninitialized = !initialized;
+        int value = this.value;
+        if (uninitialized) {
+            synchronized (this) {
+                uninitialized = !initialized;
+                if (uninitialized) {
+                    this.value = value = supplier.getAsInt();
+                    initialized = true;
+                }
+            }
+        }
+        return value;
     }
 
-    public static <T> Binding<T> bind(final Class<T> key, final Supplier<? extends T> supplier) {
-        return new Binding<>(key, supplier);
+    public synchronized void setAsInt(final int i) {
+        initialized = false;
+        value = i;
+        initialized = true;
     }
 }
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util3/Activator.java b/log4j-api/src/main/java/org/apache/logging/log4j/util3/Activator.java
index 8c05d7d32b..1c2f5e5dfe 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util3/Activator.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util3/Activator.java
@@ -17,9 +17,8 @@
 package org.apache.logging.log4j.util3;
 
 import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.spi.LegacyLoggingSystemProvider;
 import org.apache.logging.log4j.spi.LoggerContextFactory;
-import org.apache.logging.log4j.spi.LoggingSystemProvider;
+import org.apache.logging.log4j.spi.Provider;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.osgi.framework.AdaptPermission;
 import org.osgi.framework.AdminPermission;
@@ -27,13 +26,15 @@ import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
 import org.osgi.framework.SynchronousBundleListener;
 import org.osgi.framework.wiring.BundleWire;
 import org.osgi.framework.wiring.BundleWiring;
 
-import java.io.IOException;
 import java.net.URL;
 import java.security.Permission;
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -49,12 +50,9 @@ public class Activator implements BundleActivator, SynchronousBundleListener {
 
     private static final Logger LOGGER = StatusLogger.getLogger();
 
-    // until we have at least one LoggingSystemProvider, we'll acquire an initialization lock on LoggingSystem
-    // which in turn locks LogManager and ThreadContext internally
+    // until we have at least one Provider, we'll lock ProviderUtil which locks LogManager.<clinit> by extension.
     // this variable needs to be reset once the lock has been released
-    private boolean acquiredLoggingSystemInitializationLock;
-    private LoggingSystem loggingSystem;
-    private ServiceRegistry serviceRegistry;
+    private boolean lockingProviderUtil;
 
     private static void checkPermission(final Permission permission) {
         if (SECURITY_MANAGER != null) {
@@ -102,25 +100,32 @@ public class Activator implements BundleActivator, SynchronousBundleListener {
     }
 
     private void loadProvider(final BundleContext bundleContext, final BundleWiring bundleWiring) {
-        final ClassLoader classLoader = bundleWiring.getClassLoader();
-        final long bundleId = bundleContext.getBundle().getBundleId();
-        serviceRegistry.loadServicesFromBundle(LoggingSystemProvider.class, bundleId, classLoader);
-        for (final URL url : bundleWiring.findEntries("META-INF", "log4j-provider.properties", 0)) {
-            try {
-                serviceRegistry.registerBundleServices(LoggingSystemProvider.class, bundleId,
-                        List.of(new LegacyLoggingSystemProvider<>(url, classLoader)));
-            } catch (final IOException e) {
-                LOGGER.error("Unable to load {}", url, e);
+        final String filter = "(APIVersion>=2.6.0)";
+        try {
+            final Collection<ServiceReference<Provider>> serviceReferences = bundleContext.getServiceReferences(Provider.class, filter);
+            Provider maxProvider = null;
+            for (final ServiceReference<Provider> serviceReference : serviceReferences) {
+                final Provider provider = bundleContext.getService(serviceReference);
+                if (maxProvider == null || provider.getPriority() > maxProvider.getPriority()) {
+                    maxProvider = provider;
+                }
+            }
+            if (maxProvider != null) {
+                ProviderUtil.addProvider(maxProvider);
             }
+        } catch (final InvalidSyntaxException ex) {
+            LOGGER.error("Invalid service filter: " + filter, ex);
+        }
+        final List<URL> urls = bundleWiring.findEntries("META-INF", "log4j-provider.properties", 0);
+        for (final URL url : urls) {
+            ProviderUtil.loadProvider(url, bundleWiring.getClassLoader());
         }
     }
 
     @Override
     public void start(final BundleContext bundleContext) throws Exception {
-        serviceRegistry = ServiceRegistry.getInstance();
-        loggingSystem = LoggingSystem.getInstance();
-        loggingSystem.acquireInitializationLock();
-        acquiredLoggingSystemInitializationLock = true;
+        ProviderUtil.STARTUP_LOCK.lock();
+        lockingProviderUtil = true;
         final BundleWiring self = bundleContext.getBundle().adapt(BundleWiring.class);
         final List<BundleWire> required = self.getRequiredWires(LoggerContextFactory.class.getName());
         for (final BundleWire wire : required) {
@@ -135,9 +140,9 @@ public class Activator implements BundleActivator, SynchronousBundleListener {
     }
 
     private void unlockIfReady() {
-        if (acquiredLoggingSystemInitializationLock && loggingSystem.hasProvider()) {
-            loggingSystem.releaseInitializationLock();
-            acquiredLoggingSystemInitializationLock = false;
+        if (lockingProviderUtil && !ProviderUtil.PROVIDERS.isEmpty()) {
+            ProviderUtil.STARTUP_LOCK.unlock();
+            lockingProviderUtil = false;
         }
     }
 
@@ -155,10 +160,6 @@ public class Activator implements BundleActivator, SynchronousBundleListener {
                 unlockIfReady();
                 break;
 
-            case BundleEvent.STOPPED:
-                serviceRegistry.unregisterBundleServices(event.getBundle().getBundleId());
-                break;
-
             default:
                 break;
         }
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util3/Constants.java b/log4j-api/src/main/java/org/apache/logging/log4j/util3/Constants.java
index cb1b60e65f..da5aca343e 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util3/Constants.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util3/Constants.java
@@ -25,21 +25,9 @@ import org.apache.logging.log4j.util.LazyBoolean;
  */
 public final class Constants {
 
-    /**
-     * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain
-     * {@code ThreadLocal} (value is not "true") in the implementation.
-     */
-    public static final String INHERITABLE_MAP = "isThreadContextMapInheritable";
-
-    /**
-     * The default initial capacity.
-     */
-    public static final int DEFAULT_INITIAL_CAPACITY = 16;
-
-    /**
-     * System property name that can be used to control the data structure's initial capacity.
-     */
-    public static final String PROPERTY_NAME_INITIAL_CAPACITY = "log4j2.ThreadContext.initial.capacity";
+    private static final LazyBoolean isWebApp = new LazyBoolean(() -> PropertiesUtil.getProperties()
+            .getBooleanProperty("log4j2.is.webapp",
+                    isClassAvailable("javax.servlet.Servlet") || isClassAvailable("jakarta.servlet.Servlet")));
 
     /**
      * {@code true} if we think we are running in a web container, based on the boolean value of system property
@@ -47,16 +35,25 @@ public final class Constants {
      * is present in the classpath.
      */
     public static boolean isWebApp() {
-        return PropertiesUtil.getProperties().getBooleanProperty("log4j2.is.webapp", hasServletClass());
+        return isWebApp.getAsBoolean();
     }
 
-    private static boolean hasServletClass() {
-        return isClassAvailable("javax.servlet.Servlet") || isClassAvailable("jakarta.servlet.Servlet");
+    public static void setWebApp(final boolean webApp) {
+        isWebApp.setAsBoolean(webApp);
     }
 
-    public static final String THREAD_LOCALS_ENABLED = "log4j2.enable.threadlocals";
+    public static void resetWebApp() {
+        isWebApp.reset();
+    }
+
+    /**
+     * @deprecated use {@link #isWebApp()}
+     */
+    @Deprecated(since = "3.0.0", forRemoval = true)
+    public static final boolean IS_WEB_APP = isWebApp();
+
     private static final LazyBoolean threadLocalsEnabled = new LazyBoolean(
-            () -> !isWebApp() && PropertiesUtil.getProperties().getBooleanProperty(THREAD_LOCALS_ENABLED, true));
+            () -> !isWebApp() && PropertiesUtil.getProperties().getBooleanProperty("log4j2.enable.threadlocals", true));
 
     /**
      * Kill switch for object pooling in ThreadLocals that enables much of the LOG4J2-1270 no-GC behaviour.
@@ -77,6 +74,12 @@ public final class Constants {
         threadLocalsEnabled.reset();
     }
 
+    /**
+     * @deprecated use {@link #isThreadLocalsEnabled()}
+     */
+    @Deprecated(since = "3.0.0", forRemoval = true)
+    public static final boolean ENABLE_THREADLOCALS = isThreadLocalsEnabled();
+
     public static final int JAVA_MAJOR_VERSION = getMajorVersion();
 
     /**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util3/LoggingSystem.java b/log4j-api/src/main/java/org/apache/logging/log4j/util3/LoggingSystem.java
deleted file mode 100644
index 9fedc79b62..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util3/LoggingSystem.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.util3;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.simple.SimpleLoggingSystemProvider;
-import org.apache.logging.log4j.spi.LegacyLoggingSystemProvider;
-import org.apache.logging.log4j.spi.LoggerContextFactory;
-import org.apache.logging.log4j.spi.LoggingSystemProvider;
-import org.apache.logging.log4j.spi.ThreadContextMap;
-import org.apache.logging.log4j.spi.ThreadContextStack;
-import org.apache.logging.log4j.util.Lazy;
-import org.apache.logging.log4j.util.PropertyEnvironment;
-
-import java.io.IOException;
-import java.lang.invoke.MethodHandles;
-import java.lang.reflect.InvocationTargetException;
-import java.net.URL;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
-
-/**
- * Central tracking for how to obtain the system's {@link LoggerContextFactory} along with which
- * {@link LoggingSystemProvider} is installed in general.
- */
-public class LoggingSystem {
-    /**
-     * Resource name for a Log4j 2 provider properties file.
-     */
-    private static final String PROVIDER_RESOURCE = "META-INF/log4j-provider.properties";
-    // these API versions correspond to the general LoggerContextFactory/LoggerContext/Logger/Message/Marker API
-    // this does not correspond to the version of the provider
-    // (whether it be a v2-style properties file or a v3-style service provider)
-    private static final String[] COMPATIBLE_API_VERSIONS = {"2.6.0"};
-    private static final Lazy<LoggingSystem> INSTANCE = Lazy.relaxed(LoggingSystem::new);
-
-    private final Lock initializationLock = new ReentrantLock();
-    private volatile LoggingSystemProvider provider;
-    private final Lazy<LoggerContextFactory> loggerContextFactory =
-            Lazy.relaxed(() -> {
-                final PropertyEnvironment environment = PropertiesUtil.getProperties();
-                final String factoryClassName = environment.getStringProperty(LogManager.FACTORY_PROPERTY_NAME);
-                if (factoryClassName != null) {
-                    try {
-                        return LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class);
-                    } catch (final ClassNotFoundException e) {
-                        LowLevelLogUtil.logException(String.format("Unable to locate configured LoggerContextFactory '%s'", factoryClassName), e);
-                    } catch (final InvocationTargetException | InstantiationException | IllegalAccessException | LinkageError e) {
-                        LowLevelLogUtil.logException(String.format("Unable to create configured LoggerContextFactory '%s'", factoryClassName), e);
-                    }
-                }
-                return getProvider().getLoggerContextFactory();
-            });
-    private final Lazy<ThreadContextMap.Factory> contextMapFactory =
-            Lazy.relaxed(() -> getProvider().getContextMapFactory());
-    private final Lazy<ThreadContextStack.Factory> contextStackFactory =
-            Lazy.relaxed(() -> getProvider().getContextStackFactory());
-
-    /**
-     * Indicates if a LoggingSystemProvider has been selected.
-     */
-    public boolean hasProvider() {
-        return provider != null;
-    }
-
-    /**
-     * Acquires a lock on the initialization of locating a logging system provider. This lock should be
-     * {@linkplain #releaseInitializationLock() released} once the logging system provider is loaded. This lock is
-     * provided to allow for lazy initialization via frameworks like OSGi to wait for a provider to be installed
-     * before allowing initialization to continue.
-     *
-     * @see <a href="https://issues.apache.org/jira/browse/LOG4J2-373">LOG4J2-373</a>
-     */
-    public void acquireInitializationLock() {
-        initializationLock.lock();
-    }
-
-    /**
-     * Releases a lock on the initialization phase of this logging system.
-     */
-    public void releaseInitializationLock() {
-        initializationLock.unlock();
-    }
-
-    /**
-     * Gets or initializes the LoggingSystemProvider.
-     */
-    public LoggingSystemProvider getProvider() {
-        var provider = this.provider;
-        if (provider == null) {
-            try {
-                initializationLock.lockInterruptibly();
-                provider = this.provider;
-                if (provider == null) {
-                    this.provider = provider = findProvider();
-                }
-            } catch (final InterruptedException e) {
-                LowLevelLogUtil.logException("ERROR: Interrupted before Log4j Providers could be loaded.", e);
-                provider = createFallbackProvider();
-                Thread.currentThread().interrupt();
-            } finally {
-                releaseInitializationLock();
-            }
-        }
-        return provider;
-    }
-
-    private LoggingSystemProvider findProvider() {
-        final SortedMap<Integer, LoggingSystemProvider> providers = new TreeMap<>();
-        loadDefaultProviders().forEach(provider -> providers.put(provider.getPriority(), provider));
-        loadLegacyProviders().forEach(provider -> providers.put(provider.getPriority(), provider));
-        if (providers.size() == 1) {
-            return providers.get(providers.lastKey());
-        } else if (providers.size() > 1) {
-            final StringBuilder sb = new StringBuilder("WARN: Multiple logging implementations found: \n");
-            for (final Map.Entry<Integer, LoggingSystemProvider> entry : providers.entrySet()) {
-                sb.append("Provider: ").append(entry.getValue().getClass().getName());
-                sb.append(", Weighting: ").append(entry.getKey()).append('\n');
-            }
-            final LoggingSystemProvider provider = providers.get(providers.lastKey());
-            sb.append("Using provider: ").append(provider.getClass().getName());
-            LowLevelLogUtil.log(sb.toString());
-            return provider;
-        }
-        return createFallbackProvider();
-    }
-
-    public LoggerContextFactory getLoggerContextFactory() {
-        return loggerContextFactory.value();
-    }
-
-    public void setLoggerContextFactory(final LoggerContextFactory factory) {
-        loggerContextFactory.set(factory);
-    }
-
-    public ThreadContextMap createContextMap() {
-        return contextMapFactory.value().createThreadContextMap();
-    }
-
-    public void setContextMapFactory(final ThreadContextMap.Factory factory) {
-        contextMapFactory.set(factory);
-    }
-
-    public ThreadContextStack createContextStack() {
-        return contextStackFactory.value().createThreadContextStack();
-    }
-
-    public boolean isContextStackEnabled() {
-        return contextStackFactory.value().isEnabled();
-    }
-
-    public void setContextStackFactory(final ThreadContextStack.Factory factory) {
-        contextStackFactory.set(factory);
-    }
-
-    private static LoggingSystemProvider createFallbackProvider() {
-        LowLevelLogUtil.log("ERROR: Log4j could not find a logging implementation. "
-                + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
-        return new SimpleLoggingSystemProvider();
-    }
-
-    /**
-     * Returns the LoggingSystem instance.
-     */
-    public static LoggingSystem getInstance() {
-        return INSTANCE.value();
-    }
-
-    private static List<LoggingSystemProvider> loadDefaultProviders() {
-        return ServiceRegistry.getInstance()
-                .getServices(LoggingSystemProvider.class, MethodHandles.lookup(), LoggingSystem::validProvider);
-    }
-
-    private static List<LoggingSystemProvider> loadLegacyProviders() {
-        return LoaderUtil.findUrlResources(PROVIDER_RESOURCE, false)
-                .stream()
-                .map(resource -> loadProvider(resource.getUrl(), resource.getClassLoader()))
-                .filter(LoggingSystem::validProvider)
-                .collect(Collectors.toList());
-    }
-
-    private static LoggingSystemProvider loadProvider(final URL url, final ClassLoader cl) {
-        try {
-            return new LegacyLoggingSystemProvider<>(url, cl);
-        } catch (final IOException e) {
-            LowLevelLogUtil.logException("Unable to load " + url, e);
-            return null;
-        }
-    }
-
-    private static boolean validProvider(final LoggingSystemProvider provider) {
-        return provider != null && validVersion(provider.getVersion());
-    }
-
-    private static boolean validVersion(final String version) {
-        for (final String v : COMPATIBLE_API_VERSIONS) {
-            if (version.startsWith(v)) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util3/PropertiesUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util3/PropertiesUtil.java
index 0760624143..0eea506a84 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util3/PropertiesUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util3/PropertiesUtil.java
@@ -22,6 +22,8 @@ import org.apache.logging.log4j.util.PropertyEnvironment;
 import org.apache.logging.log4j.util.PropertyFilePropertySource;
 import org.apache.logging.log4j.util.PropertySource;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -36,14 +38,16 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentSkipListSet;
 
 /**
+ * <em>Consider this class private.</em>
+ * <p>
  * Provides utility methods for managing {@link Properties} instances as well as access to the global configuration
  * system. Properties by default are loaded from the system properties, system environment, and a classpath resource
  * file named {@value #LOG4J_PROPERTIES_FILE_NAME}. Additional properties can be loaded by implementing a custom
  * {@link PropertySource} service and specifying it via a {@link ServiceLoader} file called
  * {@code META-INF/services/org.apache.logging.log4j.util.PropertySource} with a list of fully qualified class names
  * implementing that interface.
+ * </p>
  *
- * @see PropertyEnvironment
  * @see PropertySource
  */
 public class PropertiesUtil implements PropertyEnvironment {
@@ -86,6 +90,31 @@ public class PropertiesUtil implements PropertyEnvironment {
         this.environment = new Environment(source);
     }
 
+    /**
+     * Loads and closes the given property input stream. If an error occurs, log to the status logger.
+     *
+     * @param in     a property input stream.
+     * @param source a source object describing the source, like a resource string or a URL.
+     * @return a new Properties object
+     */
+    static Properties loadClose(final InputStream in, final Object source) {
+        final Properties props = new Properties();
+        if (null != in) {
+            try {
+                props.load(in);
+            } catch (final IOException e) {
+                LowLevelLogUtil.logException("Unable to read " + source, e);
+            } finally {
+                try {
+                    in.close();
+                } catch (final IOException e) {
+                    LowLevelLogUtil.logException("Unable to close " + source, e);
+                }
+            }
+        }
+        return props;
+    }
+
     /**
      * Returns the PropertiesUtil used by Log4j.
      *
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util3/ProviderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util3/ProviderUtil.java
new file mode 100644
index 0000000000..9d560406b0
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util3/ProviderUtil.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.util3;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.spi.Provider;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.net.URL;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * <em>Consider this class private.</em> Utility class for Log4j {@link Provider}s. When integrating with an application
+ * container framework, any Log4j Providers not accessible through standard classpath scanning should
+ * {@link #loadProvider(java.net.URL, ClassLoader)} a classpath accordingly.
+ */
+public final class ProviderUtil {
+
+    /**
+     * Resource name for a Log4j 2 provider properties file.
+     */
+    private static final String PROVIDER_RESOURCE = "META-INF/log4j-provider.properties";
+
+    /**
+     * Loaded providers.
+     */
+    static final Collection<Provider> PROVIDERS = new HashSet<>();
+
+    /**
+     * Guards the ProviderUtil singleton instance from lazy initialization. This is primarily used for OSGi support.
+     *
+     * @since 2.1
+     */
+    static final Lock STARTUP_LOCK = new ReentrantLock();
+
+    private static final String API_VERSION = "Log4jAPIVersion";
+    private static final String[] COMPATIBLE_API_VERSIONS = {"2.6.0"};
+    private static final Logger LOGGER = StatusLogger.getLogger();
+
+    // STARTUP_LOCK guards INSTANCE for lazy initialization; this allows the OSGi Activator to pause the startup and
+    // wait for a Provider to be installed. See LOG4J2-373
+    private static volatile ProviderUtil instance;
+
+    private ProviderUtil() {
+        PROVIDERS.addAll(ServiceRegistry.getInstance()
+                .getServices(Provider.class, MethodHandles.lookup(), provider -> validVersion(provider.getVersions())));
+        for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE, false)) {
+            loadProvider(resource.getUrl(), resource.getClassLoader());
+        }
+    }
+
+    static void addProvider(final Provider provider) {
+        PROVIDERS.add(provider);
+        LOGGER.debug("Loaded Provider {}", provider);
+    }
+
+    /**
+     * Loads an individual Provider implementation. This method is really only useful for the OSGi bundle activator and
+     * this class itself.
+     *
+     * @param url the URL to the provider properties file
+     * @param cl the ClassLoader to load the provider classes with
+     */
+    static void loadProvider(final URL url, final ClassLoader cl) {
+        try {
+            final Properties props = PropertiesUtil.loadClose(url.openStream(), url);
+            if (validVersion(props.getProperty(API_VERSION))) {
+                final Provider provider = new Provider(props, url, cl);
+                PROVIDERS.add(provider);
+                LOGGER.debug("Loaded Provider {}", provider);
+            }
+        } catch (final IOException e) {
+            LOGGER.error("Unable to open {}", url, e);
+        }
+    }
+
+    public static Iterable<Provider> getProviders() {
+        lazyInit();
+        return PROVIDERS;
+    }
+
+    public static boolean hasProviders() {
+        lazyInit();
+        return !PROVIDERS.isEmpty();
+    }
+
+    /**
+     * Lazily initializes the ProviderUtil singleton.
+     *
+     * @since 2.1
+     */
+    private static void lazyInit() {
+        if (instance == null) {
+            try {
+                STARTUP_LOCK.lockInterruptibly();
+                try {
+                    if (instance == null) {
+                        //noinspection InstantiationOfUtilityClass
+                        instance = new ProviderUtil();
+                    }
+                } finally {
+                    STARTUP_LOCK.unlock();
+                }
+            } catch (final InterruptedException e) {
+                LOGGER.fatal("Interrupted before Log4j Providers could be loaded.", e);
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    public static ClassLoader findClassLoader() {
+        return LoaderUtil.getThreadContextClassLoader();
+    }
+
+    private static boolean validVersion(final String version) {
+        for (final String v : COMPATIBLE_API_VERSIONS) {
+            if (version.startsWith(v)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorCallback.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorCallback.java
index d189869655..75f339e981 100644
--- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorCallback.java
+++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorCallback.java
@@ -17,11 +17,11 @@
 
 package org.apache.logging.log4j.core.test.junit;
 
+import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.core.impl.Log4jContextFactory;
 import org.apache.logging.log4j.core.selector.ContextSelector;
 import org.apache.logging.log4j.plugins.di.DI;
 import org.apache.logging.log4j.plugins.di.Injector;
-import org.apache.logging.log4j.util3.LoggingSystem;
 import org.junit.jupiter.api.extension.AfterAllCallback;
 import org.junit.jupiter.api.extension.BeforeAllCallback;
 import org.junit.jupiter.api.extension.ExtensionContext;
@@ -30,21 +30,20 @@ import org.junit.platform.commons.support.AnnotationSupport;
 public class ContextSelectorCallback implements BeforeAllCallback, AfterAllCallback {
     @Override
     public void beforeAll(final ExtensionContext context) throws Exception {
-        final Class<?> testClass = context.getRequiredTestClass();
-        AnnotationSupport.findAnnotation(testClass, ContextSelectorType.class)
+        AnnotationSupport.findAnnotation(context.getTestClass(), ContextSelectorType.class)
                 .map(ContextSelectorType::value)
                 .ifPresent(contextSelectorClass -> {
                     final Injector injector = DI.createInjector();
                     injector.registerBinding(ContextSelector.KEY, injector.getFactory(contextSelectorClass));
                     injector.init();
                     final Log4jContextFactory factory = injector.getInstance(Log4jContextFactory.class);
-                    LoggingSystem.getInstance().setLoggerContextFactory(factory);
+                    LogManager.setFactory(factory);
                 });
     }
 
     @Override
     public void afterAll(final ExtensionContext context) throws Exception {
         AnnotationSupport.findAnnotation(context.getTestClass(), ContextSelectorType.class)
-                .ifPresent(ignored -> LoggingSystem.getInstance().setLoggerContextFactory(null));
+                .ifPresent(ignored -> LogManager.setFactory(null));
     }
 }
diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextResolver.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextResolver.java
index b1b7a6af1f..e2b01200fa 100644
--- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextResolver.java
+++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextResolver.java
@@ -17,6 +17,7 @@
 
 package org.apache.logging.log4j.core.test.junit;
 
+import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.LoggerContextAccessor;
 import org.apache.logging.log4j.core.config.ConfigurationFactory;
@@ -24,9 +25,7 @@ import org.apache.logging.log4j.core.impl.Log4jContextFactory;
 import org.apache.logging.log4j.core.util.NetUtils;
 import org.apache.logging.log4j.plugins.di.DI;
 import org.apache.logging.log4j.plugins.di.Injector;
-import org.apache.logging.log4j.spi.LoggerContextFactory;
 import org.apache.logging.log4j.test.junit.TypeBasedParameterResolver;
-import org.apache.logging.log4j.util3.LoggingSystem;
 import org.junit.jupiter.api.extension.AfterEachCallback;
 import org.junit.jupiter.api.extension.BeforeAllCallback;
 import org.junit.jupiter.api.extension.BeforeEachCallback;
@@ -115,15 +114,12 @@ class LoggerContextResolver extends TypeBasedParameterResolver<LoggerContext> im
         final String displayName = extensionContext.getDisplayName();
         final Injector injector = extensionContext.getTestInstance().map(DI::createInjector).orElseGet(DI::createInjector);
         injector.init();
-        final LoggingSystem system = LoggingSystem.getInstance();
         final Log4jContextFactory loggerContextFactory;
         if (source.bootstrap()) {
             loggerContextFactory = new Log4jContextFactory(injector);
-            system.setLoggerContextFactory(loggerContextFactory);
-            getTestStore(extensionContext).put(LoggerContextFactory.class,
-                    (Store.CloseableResource) () -> system.setLoggerContextFactory(null));
+            LogManager.setFactory(loggerContextFactory);
         } else {
-            loggerContextFactory = (Log4jContextFactory) system.getLoggerContextFactory();
+            loggerContextFactory = (Log4jContextFactory) LogManager.getFactory();
         }
         final Class<?> testClass = extensionContext.getRequiredTestClass();
         final ClassLoader classLoader = testClass.getClassLoader();
diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java
index 7a41bf1302..9bcb833c3b 100644
--- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java
+++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java
@@ -17,6 +17,7 @@
 package org.apache.logging.log4j.core.test.junit;
 
 import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.core.AbstractLifeCycle;
 import org.apache.logging.log4j.core.Appender;
 import org.apache.logging.log4j.core.Logger;
@@ -34,7 +35,6 @@ import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.test.junit.CleanFiles;
 import org.apache.logging.log4j.test.junit.CleanFolders;
 import org.apache.logging.log4j.util.Strings;
-import org.apache.logging.log4j.util3.LoggingSystem;
 import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
@@ -129,7 +129,7 @@ public class LoggerContextRule implements TestRule, LoggerContextAccessor {
                 }
                 injector.init();
                 final Log4jContextFactory factory = new Log4jContextFactory(injector);
-                LoggingSystem.getInstance().setLoggerContextFactory(factory);
+                LogManager.setFactory(factory);
                 final String fqcn = getClass().getName();
                 final ClassLoader classLoader = description.getTestClass().getClassLoader();
 
@@ -153,7 +153,6 @@ public class LoggerContextRule implements TestRule, LoggerContextAccessor {
                     loggerContext = null;
                     contextSelectorClass = null;
                     StatusLogger.getLogger().reset();
-                    LoggingSystem.getInstance().setLoggerContextFactory(null);
                     System.clearProperty(SYS_PROP_KEY_CLASS_NAME);
                     System.clearProperty(SYS_PROP_KEY_DISPLAY_NAME);
                 }
diff --git a/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider b/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider
deleted file mode 100644
index 2d5fe4b36f..0000000000
--- a/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache license, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the license for the specific language governing permissions and
-# limitations under the license.
-#
-org.apache.logging.log4j.core.impl.Log4jSystemProvider
diff --git a/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider b/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
new file mode 100644
index 0000000000..c4c6c6ca91
--- /dev/null
+++ b/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
@@ -0,0 +1 @@
+org.apache.logging.log4j.core.impl.Log4jProvider
\ No newline at end of file
diff --git a/log4j-core/src/main/java/module-info.java b/log4j-core/src/main/java/module-info.java
index 358eceaad9..6c29146711 100644
--- a/log4j-core/src/main/java/module-info.java
+++ b/log4j-core/src/main/java/module-info.java
@@ -14,9 +14,8 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-
 import org.apache.logging.log4j.core.impl.DefaultCallback;
-import org.apache.logging.log4j.core.impl.Log4jSystemProvider;
+import org.apache.logging.log4j.core.impl.Log4jProvider;
 import org.apache.logging.log4j.core.impl.ThreadContextDataProvider;
 import org.apache.logging.log4j.core.message.ExtendedThreadInfoFactory;
 import org.apache.logging.log4j.core.plugins.Log4jPlugins;
@@ -26,7 +25,7 @@ import org.apache.logging.log4j.core.util.WatchEventService;
 import org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory;
 import org.apache.logging.log4j.plugins.di.InjectorCallback;
 import org.apache.logging.log4j.plugins.model.PluginService;
-import org.apache.logging.log4j.spi.LoggingSystemProvider;
+import org.apache.logging.log4j.spi.Provider;
 
 module org.apache.logging.log4j.core {
     exports org.apache.logging.log4j.core;
@@ -99,7 +98,7 @@ module org.apache.logging.log4j.core {
     uses ScriptManagerFactory;
     provides ThreadInfoFactory with ExtendedThreadInfoFactory;
     provides ContextDataProvider with ThreadContextDataProvider;
-    provides LoggingSystemProvider with Log4jSystemProvider;
+    provides Provider with Log4jProvider;
     provides PluginService with Log4jPlugins;
     provides InjectorCallback with DefaultCallback;
 }
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggingSystemProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java
similarity index 58%
rename from log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggingSystemProvider.java
rename to log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java
index aeeeb748cd..499fb4534e 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggingSystemProvider.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java
@@ -14,27 +14,15 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
+package org.apache.logging.log4j.core.impl;
 
-package org.apache.logging.log4j.simple;
-
-import org.apache.logging.log4j.spi.AbstractLoggingSystemProvider;
+import org.apache.logging.log4j.spi.Provider;
 
 /**
- * Provides a simple fallback logging system implementation.
+ * Binding for the Log4j API.
  */
-public class SimpleLoggingSystemProvider extends AbstractLoggingSystemProvider<SimpleLoggerContextFactory> {
-    @Override
-    protected SimpleLoggerContextFactory createLoggerContextFactory() {
-        return SimpleLoggerContextFactory.INSTANCE;
-    }
-
-    @Override
-    public int getPriority() {
-        return Integer.MIN_VALUE;
-    }
-
-    @Override
-    public String getVersion() {
-        return "2.6.0";
+public class Log4jProvider extends Provider {
+    public Log4jProvider() {
+        super(10, "2.6.0", Log4jContextFactory.class);
     }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jSystemProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jSystemProvider.java
deleted file mode 100644
index fd11c9a641..0000000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jSystemProvider.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.impl;
-
-import org.apache.logging.log4j.plugins.Factory;
-import org.apache.logging.log4j.plugins.di.DI;
-import org.apache.logging.log4j.plugins.di.Injector;
-import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.spi.DefaultContextMapFactory;
-import org.apache.logging.log4j.spi.DefaultContextStackFactory;
-import org.apache.logging.log4j.spi.LoggerContextFactory;
-import org.apache.logging.log4j.spi.LoggingSystemProvider;
-import org.apache.logging.log4j.spi.ThreadContextMap;
-import org.apache.logging.log4j.spi.ThreadContextStack;
-import org.apache.logging.log4j.util.PropertyEnvironment;
-import org.apache.logging.log4j.util3.PropertiesUtil;
-
-/**
- * Binding for the Log4j API.
- */
-public class Log4jSystemProvider implements LoggingSystemProvider {
-    private final Injector injector;
-
-    public Log4jSystemProvider() {
-        injector = DI.createInjector();
-        injector.init();
-        injector.registerBundle(new Object() {
-            @Factory
-            PropertyEnvironment environment() {
-                return PropertiesUtil.getProperties();
-            }
-
-            @Factory
-            ThreadContextMap.Factory contextMapFactory(PropertyEnvironment environment) {
-                return new DefaultContextMapFactory(environment);
-            }
-
-            @Factory
-            ThreadContextStack.Factory contextStackFactory(PropertyEnvironment environment) {
-                return new DefaultContextStackFactory(environment);
-            }
-        });
-        injector.registerBinding(Key.forClass(LoggerContextFactory.class), injector.getFactory(Log4jContextFactory.class));
-    }
-
-    @Override
-    public int getPriority() {
-        return 10;
-    }
-
-    @Override
-    public String getVersion() {
-        return "2.6.0";
-    }
-
-    @Override
-    public <T> T getInstance(final Class<T> clazz) {
-        return injector.getInstance(clazz);
-    }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java
index d5457d3cdd..9d2720b114 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java
@@ -25,6 +25,7 @@ import org.apache.logging.log4j.plugins.di.InjectorCallback;
 import org.apache.logging.log4j.plugins.di.Key;
 import org.apache.logging.log4j.plugins.model.PluginRegistry;
 import org.apache.logging.log4j.plugins.model.PluginService;
+import org.apache.logging.log4j.spi.Provider;
 import org.apache.logging.log4j.util3.PropertiesUtil;
 import org.apache.logging.log4j.util3.ServiceRegistry;
 import org.osgi.framework.Bundle;
@@ -67,10 +68,10 @@ public final class Activator implements BundleActivator {
         final ServiceRegistry registry = ServiceRegistry.getInstance();
         final Bundle bundle = context.getBundle();
         final long bundleId = bundle.getBundleId();
-
         final ClassLoader classLoader = bundle.adapt(BundleWiring.class).getClassLoader();
         registry.registerBundleServices(InjectorCallback.class, bundleId, List.of(injectorCallback));
         registry.loadServicesFromBundle(PluginService.class, bundleId, classLoader);
+        registry.loadServicesFromBundle(Provider.class, bundleId, classLoader);
         registry.loadServicesFromBundle(ContextDataProvider.class, bundleId, classLoader);
         registry.loadServicesFromBundle(InjectorCallback.class, bundleId, classLoader);
         // allow the user to override the default ContextSelector (e.g., by using BasicContextSelector for a global cfg)
@@ -82,6 +83,7 @@ public final class Activator implements BundleActivator {
 
     @Override
     public void stop(final BundleContext context) throws Exception {
+        ServiceRegistry.getInstance().unregisterBundleServices(context.getBundle().getBundleId());
         if (injectorCallback != null) {
             injectorCallback = null;
             injectorCallbackServiceRegistration.unregister();
diff --git a/log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider b/log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider
deleted file mode 100644
index 2d5fe4b36f..0000000000
--- a/log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache license, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the license for the specific language governing permissions and
-# limitations under the license.
-#
-org.apache.logging.log4j.core.impl.Log4jSystemProvider
diff --git a/log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider b/log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
new file mode 100644
index 0000000000..c4c6c6ca91
--- /dev/null
+++ b/log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
@@ -0,0 +1 @@
+org.apache.logging.log4j.core.impl.Log4jProvider
\ No newline at end of file
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteOpenHashMapThreadContextMap.java b/log4j-perf/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteOpenHashMapThreadContextMap.java
index 3d02d4daad..f8c6c1fba5 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteOpenHashMapThreadContextMap.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteOpenHashMapThreadContextMap.java
@@ -19,7 +19,6 @@ package org.apache.logging.log4j.spi;
 import org.apache.logging.log4j.perf.nogc.OpenHashStringMap;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.StringMap;
-import org.apache.logging.log4j.util3.Constants;
 import org.apache.logging.log4j.util3.PropertiesUtil;
 
 /**
@@ -37,7 +36,7 @@ public class CopyOnWriteOpenHashMapThreadContextMap extends CopyOnWriteSortedArr
     @Override
     protected StringMap createStringMap() {
         return new OpenHashStringMap<>(PropertiesUtil.getProperties().getIntegerProperty(
-                Constants.PROPERTY_NAME_INITIAL_CAPACITY, Constants.DEFAULT_INITIAL_CAPACITY));
+                PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY));
     }
 
     @Override
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/spi/GarbageFreeOpenHashMapThreadContextMap.java b/log4j-perf/src/main/java/org/apache/logging/log4j/spi/GarbageFreeOpenHashMapThreadContextMap.java
index 79a5bb653a..fbd62d5d91 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/spi/GarbageFreeOpenHashMapThreadContextMap.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/spi/GarbageFreeOpenHashMapThreadContextMap.java
@@ -19,7 +19,6 @@ package org.apache.logging.log4j.spi;
 import org.apache.logging.log4j.perf.nogc.OpenHashStringMap;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.StringMap;
-import org.apache.logging.log4j.util3.Constants;
 import org.apache.logging.log4j.util3.PropertiesUtil;
 
 /**
@@ -40,7 +39,7 @@ public class GarbageFreeOpenHashMapThreadContextMap extends GarbageFreeSortedArr
     @Override
     protected StringMap createStringMap() {
         return new OpenHashStringMap<>(PropertiesUtil.getProperties().getIntegerProperty(
-                Constants.PROPERTY_NAME_INITIAL_CAPACITY, Constants.DEFAULT_INITIAL_CAPACITY));
+                PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY));
     }
 
     @Override
diff --git a/log4j-to-jul/src/main/java/module-info.java b/log4j-to-jul/src/main/java/module-info.java
index 0689c71b1c..6ad9397a86 100644
--- a/log4j-to-jul/src/main/java/module-info.java
+++ b/log4j-to-jul/src/main/java/module-info.java
@@ -1,6 +1,3 @@
-import org.apache.logging.log4j.spi.LoggingSystemProvider;
-import org.apache.logging.log4j.tojul.JULSystemProvider;
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements. See the NOTICE file distributed with
@@ -23,5 +20,5 @@ module org.apache.logging.log4j.tojul {
     requires org.apache.logging.log4j;
     requires java.logging;
 
-    provides LoggingSystemProvider with JULSystemProvider;
-}
+    provides org.apache.logging.log4j.spi.Provider with org.apache.logging.log4j.tojul.JULProvider;
+}
\ No newline at end of file
diff --git a/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULSystemProvider.java b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULProvider.java
similarity index 66%
rename from log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULSystemProvider.java
rename to log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULProvider.java
index 1d010c9b7e..b4600155a6 100644
--- a/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULSystemProvider.java
+++ b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULProvider.java
@@ -14,33 +14,17 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-
 package org.apache.logging.log4j.tojul;
 
-import org.apache.logging.log4j.spi.AbstractLoggingSystemProvider;
+import org.apache.logging.log4j.spi.Provider;
 
 /**
  * Bind the Log4j API to JUL.
  *
  * @author <a href="http://www.vorburger.ch">Michael Vorburger.ch</a> for Google
  */
-public class JULSystemProvider extends AbstractLoggingSystemProvider<JULLoggerContextFactory> {
-    public JULSystemProvider() {
-        super();
-    }
-
-    @Override
-    protected JULLoggerContextFactory createLoggerContextFactory() {
-        return new JULLoggerContextFactory();
-    }
-
-    @Override
-    public int getPriority() {
-        return 20;
-    }
-
-    @Override
-    public String getVersion() {
-        return "2.6.0";
+public class JULProvider extends Provider {
+    public JULProvider() {
+        super(20, "2.6.0", JULLoggerContextFactory.class, null);
     }
 }
diff --git a/log4j-to-jul/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider b/log4j-to-jul/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider
deleted file mode 100644
index 28c6070cb5..0000000000
--- a/log4j-to-jul/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache license, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the license for the specific language governing permissions and
-# limitations under the license.
-#
-org.apache.logging.log4j.tojul.JULSystemProvider
diff --git a/log4j-to-jul/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider b/log4j-to-jul/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
new file mode 100644
index 0000000000..2ac36b5b8b
--- /dev/null
+++ b/log4j-to-jul/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+org.apache.logging.log4j.tojul.JULProvider
diff --git a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/MDCContextMap.java b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/MDCContextMap.java
index 7ecb530c80..b04d358236 100644
--- a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/MDCContextMap.java
+++ b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/MDCContextMap.java
@@ -19,7 +19,7 @@ package org.apache.logging.slf4j;
 import java.util.Map;
 import java.util.Map.Entry;
 
-import org.apache.logging.log4j.spi.ThreadContextMap;
+import org.apache.logging.log4j.spi.CleanableThreadContextMap;
 import org.apache.logging.log4j.util.SortedArrayStringMap;
 import org.apache.logging.log4j.util.StringMap;
 import org.slf4j.MDC;
@@ -27,7 +27,7 @@ import org.slf4j.MDC;
 /**
  * Bind the ThreadContextMap to the SLF4J MDC.
  */
-public class MDCContextMap implements ThreadContextMap {
+public class MDCContextMap implements CleanableThreadContextMap {
 
     private static final StringMap EMPTY_CONTEXT_DATA = new SortedArrayStringMap(1);
     static {
@@ -56,6 +56,14 @@ public class MDCContextMap implements ThreadContextMap {
         MDC.remove(key);
     }
 
+
+    @Override
+    public void removeAll(final Iterable<String> keys) {
+        for (final String key : keys) {
+            MDC.remove(key);
+        }
+    }
+
     @Override
     public void clear() {
         MDC.clear();
@@ -97,11 +105,4 @@ public class MDCContextMap implements ThreadContextMap {
         }
         return result;
     }
-
-    public static class Factory implements ThreadContextMap.Factory {
-        @Override
-        public ThreadContextMap createThreadContextMap() {
-            return new MDCContextMap();
-        }
-    }
 }
diff --git a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JSystemProvider.java b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JProvider.java
similarity index 58%
rename from log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JSystemProvider.java
rename to log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JProvider.java
index 942927ed46..979d5e9c7a 100644
--- a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JSystemProvider.java
+++ b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JProvider.java
@@ -14,33 +14,15 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-
 package org.apache.logging.slf4j;
 
-import org.apache.logging.log4j.spi.AbstractLoggingSystemProvider;
-import org.apache.logging.log4j.spi.ThreadContextMap;
+import org.apache.logging.log4j.spi.Provider;
 
 /**
  * Bind the Log4j API to SLF4J.
  */
-public class SLF4JSystemProvider extends AbstractLoggingSystemProvider<SLF4JLoggerContextFactory> {
-    @Override
-    protected SLF4JLoggerContextFactory createLoggerContextFactory() {
-        return new SLF4JLoggerContextFactory();
-    }
-
-    @Override
-    protected ThreadContextMap.Factory createContextMapFactory() {
-        return new MDCContextMap.Factory();
-    }
-
-    @Override
-    public int getPriority() {
-        return 15;
-    }
-
-    @Override
-    public String getVersion() {
-        return "2.6.0";
+public class SLF4JProvider extends Provider {
+    public SLF4JProvider() {
+        super(15, "2.6.0", SLF4JLoggerContextFactory.class, MDCContextMap.class);
     }
 }
diff --git a/log4j-to-slf4j/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider b/log4j-to-slf4j/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider
deleted file mode 100644
index 09b12e2e93..0000000000
--- a/log4j-to-slf4j/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.LoggingSystemProvider
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache license, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the license for the specific language governing permissions and
-# limitations under the license.
-#
-org.apache.logging.slf4j.SLF4JSystemProvider
diff --git a/log4j-to-slf4j/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider b/log4j-to-slf4j/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
new file mode 100644
index 0000000000..c66b5c946a
--- /dev/null
+++ b/log4j-to-slf4j/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
@@ -0,0 +1 @@
+org.apache.logging.slf4j.SLF4JProvider
\ No newline at end of file
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index cd8363de69..d3f790c843 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -31,9 +31,6 @@
          - "remove" - Removed
     -->
     <release version="3.0.0" date="2022-MM-DD" description="GA Release 3.0.0">
-      <action issue="LOG4J2-3459" dev="mattsicker" type="add">
-        Reduce static initialization and state by introducing `LoggingSystemProvider` service provider class.
-      </action>
       <action issue="LOG4J2-3300" dev="mattsicker" type="add">
         Add conditional annotations to support more declarative binding factory bundle classes.
       </action>
diff --git a/src/site/asciidoc/manual/extending.adoc b/src/site/asciidoc/manual/extending.adoc
index 8badf7c19a..70704130b3 100644
--- a/src/site/asciidoc/manual/extending.adoc
+++ b/src/site/asciidoc/manual/extending.adoc
@@ -27,9 +27,12 @@ supported by the Log4j 3 implementation.
 The `LoggerContextFactory` binds the Log4j API to its implementation.
 The Log4j `LogManager` locates a `LoggerContextFactory` by using
 `java.util.ServiceLoader` to locate all instances of
-`org.apache.logging.log4j.spi.LoggingSystemProvider`. Each implementation must
-provide a class that implements `org.apache.logging.log4j.spi.LoggingSystemProvider` and
-should have a no-arg constructor. Log4j will compare
+`org.apache.logging.log4j.spi.Provider`. Each implementation must
+provide a class that extends `org.apache.logging.log4j.spi.Provider` and
+should have a no-arg constructor that delegates to Provider's
+constructor passing the Priority, the API versions it is compatible
+with, and the class that implements
+`org.apache.logging.log4j.spi.LoggerContextFactory`. Log4j will compare
 the current API version and if it is compatible the implementation 
 will be added to the list of providers. The API version in
 `org.apache.logging.log4j.LogManager` is only changed when a feature is
@@ -45,12 +48,13 @@ Applications may change the LoggerContextFactory that will be used by
 
 1.  Create a binding to the logging implementation.
 ..  Implement a new link:../log4j-core/apidocs/org/apache/logging/log4j/core/impl/Log4jContextFactory.html[`LoggerContextFactory`].
-..  Implement a class that implements link:../log4j-core/apidocs/org/apache/logging/spi/LoggingSystemProvider.html[`org.apache.logging.spi.LoggingSystemProvider`]
-with a no-arg constructor.
-..  Create a `META-INF/services/org.apache.logging.spi.LoggingSystemProvider` file
+..  Implement a class that extends link:../log4j-core/apidocs/org/apache/logging/spi/Provider.html[`org.apache.logging.spi.Provider`] 
+with a no-arg constructor that calls super-class's constructor with the
+Priority, the API version(s), `LoggerContextFactory` class, and
+optionally, a link:../log4j-core/apidocs/org/apache/logging/log4j/spi/ThreadContextMap.html[`ThreadContextMap`] implementation class.
+..  Create a `META-INF/services/org.apache.logging.spi.Provider` file
 that contains the name of the class that implements
-`org.apache.logging.spi.LoggingSystemProvider`.
-..  Add `provides org.apache.logging.spi.LoggingSystemProvider with com.example.MyLoggingSystemProvider;` to `module-info.java`.
+`org.apache.logging.spi.Provider`.
 2.  Setting the system property "log4j2.loggerContextFactory" to the name
 of the `LoggerContextFactory` class to use.
 3.  Setting the property "log4j2.loggerContextFactory" in a properties
diff --git a/src/site/asciidoc/runtime-dependencies.adoc b/src/site/asciidoc/runtime-dependencies.adoc
index 719d65c209..870b258fbb 100644
--- a/src/site/asciidoc/runtime-dependencies.adoc
+++ b/src/site/asciidoc/runtime-dependencies.adoc
@@ -48,7 +48,7 @@ characteristics of the modules are:
 !Some classes in this package are used by the logging implementation and should be considered private.
 The module info definition may be modified in the future to export these only to the logging implementation.
 
-!uses org.apache.logging.log4j.spi.LoggingSystemProvider
+!uses org.apache.logging.log4j.spi.Provider
 !Service that must be provided by the logging implementation.
 !===
 
@@ -310,4 +310,4 @@ API. See the link:log4j-web/dependencies.html#Dependency_Tree[Dependency
 Tree] for the exact list of JAR files needed. Note that this works with
 the Servlet 2.5 API as well as the Servlet 3.x API.
 
-|===
+|===
\ No newline at end of file