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

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

commit b37dcc14c3d6b674508c1f856f686818031f03d5
Author: Volkan Yazıcı <[email protected]>
AuthorDate: Fri Dec 15 19:15:38 2023 +0100

    Create `log4j-jctools` module with a JCTools-based recycler
---
 .../log4j/internal/RecyclerFactoriesTest.java      | 102 ------
 .../log4j/internal/StringParameterParserTest.java  | 373 ---------------------
 .../internal/{ => recycler}/ArrayQueueTest.java    |   2 +-
 .../recycler/RecyclerFactoriesTestUtil.java        |  46 +++
 .../recycler/RecyclerFactoryRegistryTest.java      |  85 +++++
 .../ThreadLocalRecyclerFactoryProviderTest.java}   |  46 ++-
 .../logging/log4j/internal/DefaultLogBuilder.java  |   4 +-
 .../logging/log4j/internal/QueueFactories.java     | 143 --------
 .../log4j/internal/QueueingRecyclerFactory.java    |  92 -----
 .../logging/log4j/internal/RecyclerFactories.java  | 173 ----------
 .../log4j/internal/StringParameterParser.java      | 304 -----------------
 .../log4j/internal/ThreadLocalRecyclerFactory.java | 101 ------
 .../log4j/internal/{ => recycler}/ArrayQueue.java  |   2 +-
 .../recycler/DummyRecyclerFactoryProvider.java     |  81 +++++
 .../recycler/QueueingRecyclerFactoryProvider.java  | 106 ++++++
 .../ThreadLocalRecyclerFactoryProvider.java        | 125 +++++++
 .../logging/log4j/message/MessageFactory.java      |   2 +-
 .../log4j/message/ParameterizedMessage.java        |   2 +-
 .../log4j/message/ReusableMessageFactory.java      |   4 +-
 .../message/ReusableParameterizedMessage.java      |   4 +-
 .../apache/logging/log4j/spi/AbstractLogger.java   |   2 +
 .../logging/log4j/spi/DummyRecyclerFactory.java    |  59 ----
 .../apache/logging/log4j/spi/LoggingSystem.java    |   6 +-
 .../logging/log4j/spi/LoggingSystemProperty.java   |  13 +-
 .../logging/log4j/spi/PropertyComponent.java       |   2 +
 .../org/apache/logging/log4j/spi/QueueFactory.java |  32 --
 .../apache/logging/log4j/spi/RecyclerFactory.java  |  58 ----
 .../log4j/spi/{ => recycler}/AbstractRecycler.java |  10 +-
 .../logging/log4j/spi/{ => recycler}/Recycler.java |  10 +-
 .../log4j/spi/{ => recycler}/RecyclerAware.java    |   3 +-
 .../log4j/spi/recycler/RecyclerFactory.java        |  58 ++++
 .../spi/recycler/RecyclerFactoryProvider.java      |  59 ++++
 .../spi/recycler/RecyclerFactoryRegistry.java      | 119 +++++++
 .../logging/log4j/spi/recycler/package-info.java   |  26 ++
 .../apache/logging/log4j/status/StatusLogger.java  |   5 +-
 .../logging/log4j/util/ServiceLoaderUtil.java      |  11 +
 .../logging/log4j/core/config/Configuration.java   |   2 +-
 .../log4j/core/filter/StructuredDataFilter.java    |   4 +-
 .../logging/log4j/core/impl/DefaultBundle.java     |   1 +
 .../logging/log4j/core/impl/MutableLogEvent.java   |   2 +-
 .../log4j/core/impl/ReusableLogEventFactory.java   |   4 +-
 .../log4j/core/layout/AbstractStringLayout.java    |   4 +-
 .../logging/log4j/core/layout/PatternLayout.java   |   2 +-
 .../log4j/core/layout/StringBuilderEncoder.java    |   2 +-
 .../apache/logging/log4j/core/util/JsonUtils.java  |   2 +-
 {log4j-api-queue-jctools => log4j-jctools}/pom.xml |  21 +-
 .../JCToolsMpmcRecyclerFactoryProvider.java        | 106 ++++++
 .../JCToolsMpmcRecyclerFactoryProviderTest.java    |  32 +-
 ...ging.log4j.spi.recycler.RecyclerFactoryProvider |   6 +
 .../layout/template/json/JsonTemplateLayout.java   |   2 +-
 .../template/json/resolver/CounterResolver.java    |   2 +-
 .../json/resolver/MessageParameterResolver.java    |   2 +-
 .../json/resolver/ReadOnlyStringMapResolver.java   |   4 +-
 .../json/resolver/StackTraceStringResolver.java    |   4 +-
 .../plugins/convert/TypeConverterFactory.java      |   3 -
 .../java/org/apache/logging/slf4j/SLF4JLogger.java |   2 +-
 pom.xml                                            |   2 +-
 57 files changed, 961 insertions(+), 1518 deletions(-)

diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/RecyclerFactoriesTest.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/RecyclerFactoriesTest.java
deleted file mode 100644
index 9673abe14f..0000000000
--- 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/RecyclerFactoriesTest.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.internal;
-
-import java.util.ArrayDeque;
-import java.util.concurrent.ArrayBlockingQueue;
-import org.apache.logging.log4j.spi.DummyRecyclerFactory;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
-import org.assertj.core.api.Assertions;
-import org.assertj.core.api.InstanceOfAssertFactories;
-import org.junit.jupiter.api.Test;
-
-public class RecyclerFactoriesTest {
-
-    @Test
-    void DummyRecyclerFactory_should_work() {
-        final RecyclerFactory actualDummyRecyclerFactory = 
RecyclerFactories.ofSpec("dummy");
-        
Assertions.assertThat(actualDummyRecyclerFactory).isSameAs(DummyRecyclerFactory.getInstance());
-    }
-
-    @Test
-    void ThreadLocalRecyclerFactory_should_work() {
-        final RecyclerFactory actualThreadLocalRecyclerFactory = 
RecyclerFactories.ofSpec("threadLocal");
-        Assertions.assertThat(actualThreadLocalRecyclerFactory)
-                
.asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class))
-                .extracting(ThreadLocalRecyclerFactory::getCapacity)
-                .isEqualTo(RecyclerFactories.CAPACITY);
-    }
-
-    @Test
-    void ThreadLocalRecyclerFactory_should_work_with_capacity() {
-        final int capacity = 13;
-        final RecyclerFactory actualThreadLocalRecyclerFactory =
-                RecyclerFactories.ofSpec("threadLocal:capacity=" + capacity);
-        Assertions.assertThat(actualThreadLocalRecyclerFactory)
-                
.asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class))
-                .extracting(ThreadLocalRecyclerFactory::getCapacity)
-                .isEqualTo(capacity);
-    }
-
-    @Test
-    void QueueingRecyclerFactory_should_work() {
-        final RecyclerFactory actualQueueingRecyclerFactory = 
RecyclerFactories.ofSpec("queue");
-        Assertions.assertThat(actualQueueingRecyclerFactory)
-                
.asInstanceOf(InstanceOfAssertFactories.type(QueueingRecyclerFactory.class))
-                .extracting(QueueingRecyclerFactory::getCapacity)
-                .isEqualTo(RecyclerFactories.CAPACITY);
-    }
-
-    @Test
-    void QueueingRecyclerFactory_should_work_with_supplier() {
-        final RecyclerFactory recyclerFactory = 
RecyclerFactories.ofSpec("queue:supplier=java.util.ArrayDeque.new");
-        
Assertions.assertThat(recyclerFactory).isInstanceOf(QueueingRecyclerFactory.class);
-        final QueueingRecyclerFactory queueingRecyclerFactory = 
(QueueingRecyclerFactory) recyclerFactory;
-        final Recycler<Object> recycler = 
queueingRecyclerFactory.create(Object::new);
-        
Assertions.assertThat(recycler).isInstanceOf(QueueingRecyclerFactory.QueueingRecycler.class);
-        final QueueingRecyclerFactory.QueueingRecycler<Object> 
queueingRecycler =
-                (QueueingRecyclerFactory.QueueingRecycler<Object>) recycler;
-        
Assertions.assertThat(queueingRecycler.getQueue()).isInstanceOf(ArrayDeque.class);
-    }
-
-    @Test
-    void QueueingRecyclerFactory_should_work_with_capacity() {
-        final int capacity = 100;
-        final RecyclerFactory actualQueueingRecyclerFactory = 
RecyclerFactories.ofSpec("queue:capacity=" + capacity);
-        Assertions.assertThat(actualQueueingRecyclerFactory)
-                
.asInstanceOf(InstanceOfAssertFactories.type(QueueingRecyclerFactory.class))
-                .extracting(QueueingRecyclerFactory::getCapacity)
-                .isEqualTo(capacity);
-    }
-
-    @Test
-    void QueueingRecyclerFactory_should_work_with_supplier_and_capacity() {
-        final int capacity = 100;
-        final RecyclerFactory recyclerFactory = RecyclerFactories.ofSpec(
-                
"queue:supplier=java.util.concurrent.ArrayBlockingQueue.new,capacity=" + 
capacity);
-        
Assertions.assertThat(recyclerFactory).isInstanceOf(QueueingRecyclerFactory.class);
-        final QueueingRecyclerFactory queueingRecyclerFactory = 
(QueueingRecyclerFactory) recyclerFactory;
-        final Recycler<Object> recycler = 
queueingRecyclerFactory.create(Object::new);
-        
Assertions.assertThat(recycler).isInstanceOf(QueueingRecyclerFactory.QueueingRecycler.class);
-        final QueueingRecyclerFactory.QueueingRecycler<Object> 
queueingRecycler =
-                (QueueingRecyclerFactory.QueueingRecycler<Object>) recycler;
-        
Assertions.assertThat(queueingRecycler.getQueue()).isInstanceOf(ArrayBlockingQueue.class);
-        final ArrayBlockingQueue<Object> queue = (ArrayBlockingQueue<Object>) 
queueingRecycler.getQueue();
-        Assertions.assertThat(queue.remainingCapacity()).isEqualTo(capacity);
-    }
-}
diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/StringParameterParserTest.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/StringParameterParserTest.java
deleted file mode 100644
index 512cc708c6..0000000000
--- 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/StringParameterParserTest.java
+++ /dev/null
@@ -1,373 +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 java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-import 
org.apache.logging.log4j.internal.StringParameterParser.DoubleQuotedStringValue;
-import org.apache.logging.log4j.internal.StringParameterParser.NullValue;
-import org.apache.logging.log4j.internal.StringParameterParser.StringValue;
-import org.apache.logging.log4j.internal.StringParameterParser.Value;
-import org.apache.logging.log4j.internal.StringParameterParser.Values;
-import org.assertj.core.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-@SuppressWarnings("DoubleBraceInitialization")
-class StringParameterParserTest {
-
-    @Test
-    void test_empty_string() {
-        testSuccess("", Collections.emptyMap());
-    }
-
-    @Test
-    void test_blank_string() {
-        testSuccess("\t", Collections.emptyMap());
-    }
-
-    @Test
-    void test_simple_pair() {
-        testSuccess("a=b", Collections.singletonMap("a", 
Values.stringValue("b")));
-    }
-
-    @Test
-    void test_simple_pair_with_whitespace_1() {
-        testSuccess(" a=b", Collections.singletonMap("a", 
Values.stringValue("b")));
-    }
-
-    @Test
-    void test_simple_pair_with_whitespace_2() {
-        testSuccess(" a =b", Collections.singletonMap("a", 
Values.stringValue("b")));
-    }
-
-    @Test
-    void test_simple_pair_with_whitespace_3() {
-        testSuccess(" a = b", Collections.singletonMap("a", 
Values.stringValue("b")));
-    }
-
-    @Test
-    void test_simple_pair_with_whitespace_4() {
-        testSuccess(" a = b ", Collections.singletonMap("a", 
Values.stringValue("b")));
-    }
-
-    @Test
-    void test_null_value_1() {
-        testSuccess("a", Collections.singletonMap("a", Values.nullValue()));
-    }
-
-    @Test
-    void test_null_value_2() {
-        testSuccess("a,b=c,d=", new LinkedHashMap<>() {
-            {
-                put("a", Values.nullValue());
-                put("b", Values.stringValue("c"));
-                put("d", Values.nullValue());
-            }
-        });
-    }
-
-    @Test
-    void test_null_value_3() {
-        testSuccess("a,b=c,d", new LinkedHashMap<>() {
-            {
-                put("a", Values.nullValue());
-                put("b", Values.stringValue("c"));
-                put("d", Values.nullValue());
-            }
-        });
-    }
-
-    @Test
-    void test_null_value_4() {
-        testSuccess("a,b=\"c,=\\\"\",d=,e=f", new LinkedHashMap<>() {
-            {
-                put("a", Values.nullValue());
-                put("b", Values.doubleQuotedStringValue("c,=\""));
-                put("d", Values.nullValue());
-                put("e", Values.stringValue("f"));
-            }
-        });
-    }
-
-    @Test
-    void test_two_pairs() {
-        testSuccess("a=b,c=d", new LinkedHashMap<>() {
-            {
-                put("a", Values.stringValue("b"));
-                put("c", Values.stringValue("d"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_01() {
-        testSuccess("a=\"b\"", Collections.singletonMap("a", 
Values.doubleQuotedStringValue("b")));
-    }
-
-    @Test
-    void test_quoted_string_02() {
-        testSuccess("a=\"b\",c=d", new LinkedHashMap<>() {
-            {
-                put("a", Values.doubleQuotedStringValue("b"));
-                put("c", Values.stringValue("d"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_03() {
-        testSuccess("a=b,c=\"d\"", new LinkedHashMap<>() {
-            {
-                put("a", Values.stringValue("b"));
-                put("c", Values.doubleQuotedStringValue("d"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_04() {
-        testSuccess("a=\"b\",c=\"d\"", new LinkedHashMap<>() {
-            {
-                put("a", Values.doubleQuotedStringValue("b"));
-                put("c", Values.doubleQuotedStringValue("d"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_05() {
-        testSuccess("a=\"\\\"b\"", Collections.singletonMap("a", 
Values.doubleQuotedStringValue("\"b")));
-    }
-
-    @Test
-    void test_quoted_string_06() {
-        testSuccess("a=\"\\\"b\\\"\"", Collections.singletonMap("a", 
Values.doubleQuotedStringValue("\"b\"")));
-    }
-
-    @Test
-    void test_quoted_string_07() {
-        testSuccess("a=\"\\\"b\",c=d", new LinkedHashMap<>() {
-            {
-                put("a", Values.doubleQuotedStringValue("\"b"));
-                put("c", Values.stringValue("d"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_08() {
-        testSuccess("a=\"\\\"b\\\"\",c=d", new LinkedHashMap<String, Value>() {
-            {
-                put("a", Values.doubleQuotedStringValue("\"b\""));
-                put("c", Values.stringValue("d"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_09() {
-        testSuccess("a=\"\\\"b,\",c=d", new LinkedHashMap<>() {
-            {
-                put("a", Values.doubleQuotedStringValue("\"b,"));
-                put("c", Values.stringValue("d"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_10() {
-        testSuccess("a=\"\\\"b\\\",\",c=d", new LinkedHashMap<>() {
-            {
-                put("a", Values.doubleQuotedStringValue("\"b\","));
-                put("c", Values.stringValue("d"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_11() {
-        testSuccess("a=\"\\\"b\",c=\"d\"", new LinkedHashMap<>() {
-            {
-                put("a", Values.doubleQuotedStringValue("\"b"));
-                put("c", Values.doubleQuotedStringValue("d"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_12() {
-        testSuccess("a=\"\\\"b\\\"\",c=\"d\"", new LinkedHashMap<>() {
-            {
-                put("a", Values.doubleQuotedStringValue("\"b\""));
-                put("c", Values.doubleQuotedStringValue("d"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_13() {
-        testSuccess("a=\"\\\"b,\",c=\"\\\"d\"", new LinkedHashMap<String, 
Value>() {
-            {
-                put("a", Values.doubleQuotedStringValue("\"b,"));
-                put("c", Values.doubleQuotedStringValue("\"d"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_14() {
-        testSuccess("a=\"\\\"b\\\",\",c=\"\\\"d\\\"\"", new LinkedHashMap<>() {
-            {
-                put("a", Values.doubleQuotedStringValue("\"b\","));
-                put("c", Values.doubleQuotedStringValue("\"d\""));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_15() {
-        testSuccess("a=\"\\\"b\",c=\",d\"", new LinkedHashMap<>() {
-            {
-                put("a", Values.doubleQuotedStringValue("\"b"));
-                put("c", Values.doubleQuotedStringValue(",d"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_16() {
-        testSuccess("a=\"\\\"b\\\"\",c=\",d\"", new LinkedHashMap<>() {
-            {
-                put("a", Values.doubleQuotedStringValue("\"b\""));
-                put("c", Values.doubleQuotedStringValue(",d"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_17() {
-        testSuccess("a=\"\\\"b,\",c=\"\\\"d,\"", new LinkedHashMap<>() {
-            {
-                put("a", Values.doubleQuotedStringValue("\"b,"));
-                put("c", Values.doubleQuotedStringValue("\"d,"));
-            }
-        });
-    }
-
-    @Test
-    void test_quoted_string_18() {
-        testSuccess("a=\"\\\"b\\\",\",c=\"\\\"d\\\",\"", new LinkedHashMap<>() 
{
-            {
-                put("a", Values.doubleQuotedStringValue("\"b\","));
-                put("c", Values.doubleQuotedStringValue("\"d\","));
-            }
-        });
-    }
-
-    private static void testSuccess(final String input, final Map<String, 
Value> expectedMap) {
-        final Map<String, Value> actualMap = 
StringParameterParser.parse(input);
-        Assertions.assertThat(actualMap).as("input: %s", 
input).isEqualTo(expectedMap);
-    }
-
-    @Test
-    void test_missing_key() {
-        Assertions.assertThatThrownBy(() -> {
-                    final String input = ",a=b";
-                    StringParameterParser.parse(input);
-                })
-                .hasMessageStartingWith("failed to locate key at index 0");
-    }
-
-    @Test
-    void test_conflicting_key() {
-        Assertions.assertThatThrownBy(() -> {
-                    final String input = "a,a";
-                    StringParameterParser.parse(input);
-                })
-                .hasMessageStartingWith("conflicting key at index 2");
-    }
-
-    @Test
-    void test_prematurely_ending_quoted_string_01() {
-        Assertions.assertThatThrownBy(() -> {
-                    final String input = "a,b=\"";
-                    StringParameterParser.parse(input);
-                })
-                .hasMessageStartingWith("failed to locate the end of 
double-quoted content starting at index 4");
-    }
-
-    @Test
-    void test_prematurely_ending_quoted_string_02() {
-        Assertions.assertThatThrownBy(() -> {
-                    final String input = "a,b=\"c";
-                    StringParameterParser.parse(input);
-                })
-                .hasMessageStartingWith("failed to locate the end of 
double-quoted content starting at index 4");
-    }
-
-    @Test
-    void test_prematurely_ending_quoted_string_03() {
-        Assertions.assertThatThrownBy(() -> {
-                    final String input = "a,b=\",c";
-                    StringParameterParser.parse(input);
-                })
-                .hasMessageStartingWith("failed to locate the end of 
double-quoted content starting at index 4");
-    }
-
-    @Test
-    void test_prematurely_ending_quoted_string_04() {
-        Assertions.assertThatThrownBy(() -> {
-                    final String input = "a,b=\",c\" x";
-                    StringParameterParser.parse(input);
-                })
-                .hasMessageStartingWith("was expecting comma at index 9");
-    }
-
-    @Test
-    void test_NullValue_toString() {
-        final Map<String, Value> map = StringParameterParser.parse("a");
-        final NullValue value = (NullValue) map.get("a");
-        Assertions.assertThat(value.toString()).isEqualTo("null");
-    }
-
-    @Test
-    void test_StringValue_toString() {
-        final Map<String, Value> map = StringParameterParser.parse("a=b");
-        final StringValue value = (StringValue) map.get("a");
-        Assertions.assertThat(value.toString()).isEqualTo("b");
-    }
-
-    @Test
-    void test_DoubleQuotedStringValue_toString() {
-        final Map<String, Value> map = 
StringParameterParser.parse("a=\"\\\"b\"");
-        final DoubleQuotedStringValue value = (DoubleQuotedStringValue) 
map.get("a");
-        Assertions.assertThat(value.toString()).isEqualTo("\"b");
-    }
-
-    @Test
-    void test_allowedKeys() {
-        Assertions.assertThatThrownBy(() -> {
-                    final String input = "a,b";
-                    final Set<String> allowedKeys = new 
LinkedHashSet<>(Collections.singletonList("a"));
-                    StringParameterParser.parse(input, allowedKeys);
-                })
-                .hasMessageStartingWith("unknown key \"b\" is found in input: 
a,b");
-    }
-}
diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/ArrayQueueTest.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ArrayQueueTest.java
similarity index 98%
rename from 
log4j-api-test/src/test/java/org/apache/logging/log4j/internal/ArrayQueueTest.java
rename to 
log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ArrayQueueTest.java
index e063e7f81c..2ded8ff7a8 100644
--- 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/ArrayQueueTest.java
+++ 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ArrayQueueTest.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.internal;
+package org.apache.logging.log4j.internal.recycler;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoriesTestUtil.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoriesTestUtil.java
new file mode 100644
index 0000000000..44422a3319
--- /dev/null
+++ 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoriesTestUtil.java
@@ -0,0 +1,46 @@
+/*
+ * 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.recycler;
+
+import edu.umd.cs.findbugs.annotations.Nullable;
+import java.util.Properties;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryRegistry;
+import org.apache.logging.log4j.util.PropertiesUtil;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+
+final class RecyclerFactoriesTestUtil {
+
+    private RecyclerFactoriesTestUtil() {}
+
+    @Nullable
+    static RecyclerFactory createForEnvironment(
+            @Nullable Boolean threadLocalsEnabled, @Nullable final String 
factory, @Nullable final Integer capacity) {
+        final Properties properties = new Properties();
+        if (threadLocalsEnabled != null) {
+            properties.setProperty("log4j2.*.ThreadLocals.enable", "" + 
threadLocalsEnabled);
+        }
+        if (factory != null) {
+            properties.setProperty("log4j2.*.Recycler.factory", factory);
+        }
+        if (capacity != null) {
+            properties.setProperty("log4j2.*.Recycler.capacity", "" + 
capacity);
+        }
+        final PropertyEnvironment env = new PropertiesUtil(properties);
+        return RecyclerFactoryRegistry.findRecyclerFactory(env);
+    }
+}
diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryRegistryTest.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryRegistryTest.java
new file mode 100644
index 0000000000..8793c1244e
--- /dev/null
+++ 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryRegistryTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.recycler;
+
+import static 
org.apache.logging.log4j.internal.recycler.RecyclerFactoriesTestUtil.createForEnvironment;
+import static org.apache.logging.log4j.spi.recycler.Recycler.DEFAULT_CAPACITY;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import 
org.apache.logging.log4j.internal.recycler.DummyRecyclerFactoryProvider.DummyRecyclerFactory;
+import 
org.apache.logging.log4j.internal.recycler.QueueingRecyclerFactoryProvider.QueueingRecyclerFactory;
+import 
org.apache.logging.log4j.internal.recycler.ThreadLocalRecyclerFactoryProvider.ThreadLocalRecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.jupiter.api.Test;
+
+public class RecyclerFactoryRegistryTest {
+
+    @Test
+    void DummyRecyclerFactory_should_work() {
+        final RecyclerFactory factory = createForEnvironment(null, "dummy", 
null);
+        assertThat(factory).isInstanceOf(DummyRecyclerFactory.class);
+    }
+
+    @Test
+    void ThreadLocalRecyclerFactory_should_work() {
+        final RecyclerFactory factory = createForEnvironment(null, 
"threadLocal", null);
+        assertThat(factory)
+                
.asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class))
+                .extracting(factory_ -> factory_.capacity)
+                .isEqualTo(DEFAULT_CAPACITY);
+    }
+
+    @Test
+    void ThreadLocalRecyclerFactory_should_work_with_capacity() {
+        final int capacity = 13;
+        final RecyclerFactory factory = createForEnvironment(null, 
"threadLocal", capacity);
+        assertThat(factory)
+                
.asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class))
+                .extracting(factory_ -> factory_.capacity)
+                .isEqualTo(capacity);
+    }
+
+    @Test
+    void QueueingRecyclerFactory_should_work() {
+        final RecyclerFactory factory = createForEnvironment(null, "queue", 
null);
+        assertThat(factory)
+                
.asInstanceOf(InstanceOfAssertFactories.type(QueueingRecyclerFactory.class))
+                .extracting(factory_ -> factory_.capacity)
+                .isEqualTo(DEFAULT_CAPACITY);
+    }
+
+    @Test
+    void QueueingRecyclerFactory_should_work_with_capacity() {
+        final int capacity = 100;
+        final RecyclerFactory factory = createForEnvironment(null, "queue", 
capacity);
+        assertThat(factory)
+                
.asInstanceOf(InstanceOfAssertFactories.type(QueueingRecyclerFactory.class))
+                .extracting(factory_ -> factory_.capacity)
+                .isEqualTo(capacity);
+    }
+
+    @Test
+    void verify_order() {
+        final RecyclerFactoryProvider dummyProvider = new 
DummyRecyclerFactoryProvider();
+        final RecyclerFactoryProvider threadLocalProvider = new 
ThreadLocalRecyclerFactoryProvider();
+        final RecyclerFactoryProvider queueProvider = new 
QueueingRecyclerFactoryProvider();
+        
assertThat(dummyProvider.getOrder()).isGreaterThan(queueProvider.getOrder());
+        
assertThat(queueProvider.getOrder()).isGreaterThan(threadLocalProvider.getOrder());
+    }
+}
diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/ThreadLocalRecyclerFactoryTest.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProviderTest.java
similarity index 60%
rename from 
log4j-api-test/src/test/java/org/apache/logging/log4j/internal/ThreadLocalRecyclerFactoryTest.java
rename to 
log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProviderTest.java
index c822e2218f..7ccde99823 100644
--- 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/ThreadLocalRecyclerFactoryTest.java
+++ 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProviderTest.java
@@ -14,34 +14,48 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.internal;
+package org.apache.logging.log4j.internal.recycler;
 
+import static 
org.apache.logging.log4j.internal.recycler.RecyclerFactoriesTestUtil.createForEnvironment;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.util.List;
 import java.util.Queue;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
-import org.apache.logging.log4j.spi.Recycler;
+import 
org.apache.logging.log4j.internal.recycler.ThreadLocalRecyclerFactoryProvider.ThreadLocalRecyclerFactory;
+import 
org.apache.logging.log4j.internal.recycler.ThreadLocalRecyclerFactoryProvider.ThreadLocalRecyclerFactory.ThreadLocalRecycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junitpioneer.jupiter.params.IntRangeSource;
 
-class ThreadLocalRecyclerFactoryTest {
+class ThreadLocalRecyclerFactoryProviderTest {
 
-    private static final int CAPACITY = 8;
+    private static final int CAPACITY = 13;
 
     private static class RecyclableObject {}
 
-    private Recycler<RecyclableObject> recycler;
+    private ThreadLocalRecycler<RecyclableObject> recycler;
 
     private Queue<RecyclableObject> recyclerQueue;
 
     @BeforeEach
     void setUp() {
-        recycler = new 
ThreadLocalRecyclerFactory(CAPACITY).create(RecyclableObject::new);
-        recyclerQueue = 
((ThreadLocalRecyclerFactory.ThreadLocalRecycler<RecyclableObject>) 
recycler).getQueue();
+        final RecyclerFactory recyclerFactory = createForEnvironment(null, 
"threadLocal", CAPACITY);
+        
assertThat(recyclerFactory).isInstanceOf(ThreadLocalRecyclerFactory.class);
+        assert recyclerFactory != null;
+        recycler = (ThreadLocalRecycler<RecyclableObject>) 
recyclerFactory.create(RecyclableObject::new);
+        recyclerQueue = recycler.queueRef.get();
+    }
+
+    @Test
+    void should_not_be_configured_when_TLs_are_disabled() {
+        assertThatThrownBy(() -> createForEnvironment(false, "threadLocal", 
null))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageStartingWith("failed to configure recycler");
     }
 
     @ParameterizedTest
@@ -79,18 +93,22 @@ class ThreadLocalRecyclerFactoryTest {
 
         assertThat(recyclerQueue).isEmpty();
 
-        // simulate a massively callstack with tons of logging
-        final List<RecyclableObject> acquiredObjects =
-                IntStream.range(0, 1024).mapToObj(i -> 
recycler.acquire()).collect(Collectors.toList());
+        // Simulate a callstack with excessive logging
+        final int acquisitionCount = Math.addExact(CAPACITY, 1024);
+        final List<RecyclableObject> acquiredObjects = IntStream.range(0, 
acquisitionCount)
+                .mapToObj(i -> recycler.acquire())
+                .toList();
 
-        // still nothing returned to pool
+        // Verify collected instances are all new
+        assertThat(acquiredObjects).doesNotHaveDuplicates();
+
+        // Verify the pool is still empty
         assertThat(recyclerQueue).isEmpty();
 
-        // don't want any duplicate instances
-        
assertThat(acquiredObjects).containsOnlyOnceElementsOf(acquiredObjects);
+        // Release all acquired instances
         acquiredObjects.forEach(recycler::release);
 
-        // upon return, we should only have `CAPACITY` retained for future use
+        // Verify the queue size is capped
         assertThat(recyclerQueue).hasSize(CAPACITY);
     }
 }
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
index e53fd1f589..cffd2ce56f 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
@@ -24,8 +24,8 @@ import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.spi.ExtendedLogger;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerAware;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerAware;
 import org.apache.logging.log4j.util.*;
 
 /**
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/QueueFactories.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/QueueFactories.java
deleted file mode 100644
index b0275c4506..0000000000
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/QueueFactories.java
+++ /dev/null
@@ -1,143 +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 static org.apache.logging.log4j.util.LowLevelLogUtil.log;
-
-import aQute.bnd.annotation.spi.ServiceConsumer;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Queue;
-import java.util.ServiceLoader;
-import java.util.concurrent.ArrayBlockingQueue;
-import org.apache.logging.log4j.spi.QueueFactory;
-import org.apache.logging.log4j.util.Cast;
-import org.apache.logging.log4j.util.InternalApi;
-import org.apache.logging.log4j.util.LoaderUtil;
-import org.apache.logging.log4j.util.ServiceLoaderUtil;
-
-/**
- * Provides the default {@link QueueFactory} instance.
- *
- * @since 3.0.0
- */
-@InternalApi
-@ServiceConsumer(QueueFactory.class)
-public final class QueueFactories {
-
-    /**
-     * The default {@link QueueFactory} instance.
-     */
-    public static final QueueFactory INSTANCE = findInstance();
-
-    private static QueueFactory findInstance() {
-        final ServiceLoader<QueueFactory> serviceLoader =
-                ServiceLoader.load(QueueFactory.class, 
QueueFactory.class.getClassLoader());
-        final List<QueueFactory> factories =
-                ServiceLoaderUtil.safeStream(serviceLoader).toList();
-        if (factories.isEmpty()) {
-            return ArrayBlockingQueue::new;
-        } else {
-            final int factoryCount = factories.size();
-            if (factoryCount > 1) {
-                log("Log4j was expecting a single `QueueFactory` provider, 
found:");
-                for (int factoryIndex = 0; factoryIndex < factoryCount; 
factoryIndex++) {
-                    log((factoryIndex + 1) + ". `" + 
factories.get(factoryIndex) + "`");
-                }
-                log("Log4j will use the first `QueueFactory` provider as the 
default.");
-            }
-            return factories.get(0);
-        }
-    }
-
-    private QueueFactories() {}
-
-    /**
-     * Creates a {@link QueueFactory} using the provided supplier.
-     * <p>
-     * A supplier path must be formatted as follows:
-     * <ul>
-     * <li>{@code <fully-qualified-class-name>.new} – the class constructor 
accepting a single {@code int} argument (denoting the capacity) will be used 
(e.g., {@code org.jctools.queues.MpmcArrayQueue.new})</li>
-     * <li>{@code <fully-qualified-class-name>.<static-factory-method>} – the 
static factory method accepting a single {@code int} argument (denoting the 
capacity) will be used (e.g., {@code com.acme.Queues.createBoundedQueue})</li>
-     * </ul>
-     * </p>
-     *
-     * @param supplierPath a queue supplier path (e.g., {@code 
org.jctools.queues.MpmcArrayQueue.new}, {@code 
com.acme.Queues.createBoundedQueue})
-     * @return a new {@link QueueFactory} instance
-     */
-    public static QueueFactory ofSupplier(final String supplierPath) {
-        final int supplierPathSplitterIndex = supplierPath.lastIndexOf('.');
-        if (supplierPathSplitterIndex < 0) {
-            final String message = String.format("invalid queue factory 
supplier path: `%s`", supplierPath);
-            throw new IllegalArgumentException(message);
-        }
-        final String supplierClassName = supplierPath.substring(0, 
supplierPathSplitterIndex);
-        final String supplierMethodName = 
supplierPath.substring(supplierPathSplitterIndex + 1);
-        try {
-            final Class<?> supplierClass = 
LoaderUtil.loadClass(supplierClassName);
-            if ("new".equals(supplierMethodName)) {
-                final Constructor<?> supplierCtor = 
supplierClass.getDeclaredConstructor(int.class);
-                return new ConstructorProvidedQueueFactory(supplierCtor);
-            } else {
-                final Method supplierMethod = 
supplierClass.getMethod(supplierMethodName, int.class);
-                return new StaticMethodProvidedQueueFactory(supplierMethod);
-            }
-        } catch (final ReflectiveOperationException | LinkageError | 
SecurityException error) {
-            final String message =
-                    String.format("failed to create the queue factory using 
the supplier path `%s`", supplierPath);
-            throw new RuntimeException(message, error);
-        }
-    }
-
-    private static final class ConstructorProvidedQueueFactory implements 
QueueFactory {
-
-        private final Constructor<?> constructor;
-
-        private ConstructorProvidedQueueFactory(final Constructor<?> 
constructor) {
-            this.constructor = constructor;
-        }
-
-        @Override
-        public <E> Queue<E> create(final int capacity) {
-            final Constructor<Queue<E>> typedConstructor = 
Cast.cast(constructor);
-            try {
-                return typedConstructor.newInstance(capacity);
-            } catch (final ReflectiveOperationException error) {
-                throw new RuntimeException("queue construction failure", 
error);
-            }
-        }
-    }
-
-    private static final class StaticMethodProvidedQueueFactory implements 
QueueFactory {
-
-        private final Method method;
-
-        private StaticMethodProvidedQueueFactory(final Method method) {
-            this.method = method;
-        }
-
-        @Override
-        public <E> Queue<E> create(final int capacity) {
-            try {
-                return Cast.cast(method.invoke(null, capacity));
-            } catch (final ReflectiveOperationException error) {
-                throw new RuntimeException("queue construction failure", 
error);
-            }
-        }
-    }
-}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/QueueingRecyclerFactory.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/QueueingRecyclerFactory.java
deleted file mode 100644
index b5b6c70f72..0000000000
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/QueueingRecyclerFactory.java
+++ /dev/null
@@ -1,92 +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 static java.util.Objects.requireNonNull;
-
-import java.util.Queue;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-import org.apache.logging.log4j.spi.AbstractRecycler;
-import org.apache.logging.log4j.spi.QueueFactory;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
-
-/**
- * A {@link RecyclerFactory} pooling objects in a queue created using the 
provided {@link QueueFactory}.
- */
-final class QueueingRecyclerFactory implements RecyclerFactory {
-
-    private final QueueFactory queueFactory;
-
-    private final int capacity;
-
-    QueueingRecyclerFactory(final QueueFactory queueFactory, final int 
capacity) {
-        if (capacity < 1) {
-            throw new IllegalArgumentException("was expecting `capacity > 0`, 
found: " + capacity);
-        }
-        this.queueFactory = requireNonNull(queueFactory, "queueFactory");
-        this.capacity = capacity;
-    }
-
-    /**
-     * @return the maximum number of objects retained per thread in recyclers 
created
-     */
-    int getCapacity() {
-        return capacity;
-    }
-
-    @Override
-    public <V> Recycler<V> create(final Supplier<V> supplier, final 
Consumer<V> cleaner) {
-        requireNonNull(supplier, "supplier");
-        requireNonNull(cleaner, "cleaner");
-        final Queue<V> queue = queueFactory.create(capacity);
-        return new QueueingRecycler<>(supplier, cleaner, queue);
-    }
-
-    // Visible for tests
-    static class QueueingRecycler<V> extends AbstractRecycler<V> {
-
-        private final Consumer<V> cleaner;
-
-        private final Queue<V> queue;
-
-        private QueueingRecycler(final Supplier<V> supplier, final Consumer<V> 
cleaner, final Queue<V> queue) {
-            super(supplier);
-            this.cleaner = cleaner;
-            this.queue = queue;
-        }
-
-        // Visible for tests
-        Queue<V> getQueue() {
-            return queue;
-        }
-
-        @Override
-        public V acquire() {
-            final V value = queue.poll();
-            return value != null ? value : createInstance();
-        }
-
-        @Override
-        public void release(final V value) {
-            requireNonNull(value, "value");
-            cleaner.accept(value);
-            queue.offer(value);
-        }
-    }
-}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/RecyclerFactories.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/RecyclerFactories.java
deleted file mode 100644
index c7ec83a87a..0000000000
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/RecyclerFactories.java
+++ /dev/null
@@ -1,173 +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 static java.util.Objects.requireNonNull;
-import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled;
-
-import java.util.Map;
-import java.util.Set;
-import org.apache.logging.log4j.spi.DummyRecyclerFactory;
-import org.apache.logging.log4j.spi.QueueFactory;
-import org.apache.logging.log4j.spi.RecyclerFactory;
-import org.apache.logging.log4j.util.InternalApi;
-
-/**
- * Stores the default {@link RecyclerFactory} instance.
- */
-@InternalApi
-public final class RecyclerFactories {
-
-    /**
-     * The default recycler capacity.
-     */
-    public static final int CAPACITY = Math.max(2 * 
Runtime.getRuntime().availableProcessors() + 1, 8);
-
-    /**
-     * The default recycler instance.
-     */
-    public static final RecyclerFactory INSTANCE = isThreadLocalsEnabled()
-            ? new ThreadLocalRecyclerFactory(CAPACITY)
-            : new QueueingRecyclerFactory(QueueFactories.INSTANCE, CAPACITY);
-
-    private RecyclerFactories() {}
-
-    /**
-     * Creates a {@link RecyclerFactory} instance using the provided 
specification.
-     * <p>
-     * The recycler factory specification string must be formatted as follows:
-     * </p>
-     * <pre>{@code
-     * recyclerFactorySpec            = dummySpec
-     *                                | threadLocalRecyclerFactorySpec
-     *                                | queueingRecyclerFactorySpec
-     *
-     * dummySpec                      = "dummy"
-     *
-     * threadLocalRecyclerFactorySpec = "threadLocal" , [ ":" , capacityArg ]
-     * capacityArg                    = "capacity=" , integer
-     *
-     * queueingRecyclerFactorySpec    = "queue" , [ ":" , 
queueingRecyclerFactoryArgs ]
-     * queueingRecyclerFactoryArgs    = queueingRecyclerFactoryArg , [ "," , 
queueingRecyclerFactoryArg ]*
-     * queueingRecyclerFactoryArg     = capacityArg
-     *                                | queueSupplierArg
-     * queueSupplierArg               = ( classPath , ".new" )
-     *                                | ( classPath , "." , methodName )
-     * }</pre>
-     * <p>
-     * If not specified, {@code capacity} will be set to {@code max(8, 
2*C+1)}, where {@code C} denotes the value returned by {@link 
Runtime#availableProcessors()}.
-     * </p>
-     * <p>
-     * You can find some examples below.
-     * </p>
-     * <ul>
-     * <li><code>{@code dummy}</code></li>
-     * <li><code>{@code threadLocal}</code></li>
-     * <li><code>{@code threadLocal:capacity=13}</code></li>
-     * <li><code>{@code queue}</code></li>
-     * <li><code>{@code queue:supplier=java.util.ArrayDeque.new}</code></li>
-     * <li><code>{@code queue:capacity=100}</code></li>
-     * <li><code>{@code 
queue:supplier=com.acme.AwesomeQueue.create,capacity=42}</code></li>
-     * </ul>
-     * @param recyclerFactorySpec the recycler factory specification string
-     * @return a recycler factory instance
-     */
-    public static RecyclerFactory ofSpec(final String recyclerFactorySpec) {
-
-        // Check arguments
-        requireNonNull(recyclerFactorySpec, "recyclerFactorySpec");
-
-        // Is a dummy factory requested?
-        if (recyclerFactorySpec.equals("dummy")) {
-            return DummyRecyclerFactory.getInstance();
-        }
-
-        // Is a TLA factory requested?
-        else if (recyclerFactorySpec.startsWith("threadLocal")) {
-            return readThreadLocalRecyclerFactory(recyclerFactorySpec);
-        }
-
-        // Is a queueing factory requested?
-        else if (recyclerFactorySpec.startsWith("queue")) {
-            return readQueueingRecyclerFactory(recyclerFactorySpec);
-        }
-
-        // Bogus input, bail out.
-        else {
-            throw new IllegalArgumentException("invalid recycler factory: " + 
recyclerFactorySpec);
-        }
-    }
-
-    private static RecyclerFactory readThreadLocalRecyclerFactory(final String 
recyclerFactorySpec) {
-
-        // Parse the spec
-        final String queueFactorySpec = recyclerFactorySpec.substring(
-                "threadLocal".length() + 
(recyclerFactorySpec.startsWith("threadLocal:") ? 1 : 0));
-        final Map<String, StringParameterParser.Value> parsedValues =
-                StringParameterParser.parse(queueFactorySpec, 
Set.of("capacity"));
-
-        // Read the capacity
-        final int capacity = readQueueCapacity(queueFactorySpec, parsedValues);
-
-        // Execute the read spec
-        return new ThreadLocalRecyclerFactory(capacity);
-    }
-
-    private static RecyclerFactory readQueueingRecyclerFactory(final String 
recyclerFactorySpec) {
-
-        // Parse the spec
-        final String queueFactorySpec =
-                recyclerFactorySpec.substring("queue".length() + 
(recyclerFactorySpec.startsWith("queue:") ? 1 : 0));
-        final Map<String, StringParameterParser.Value> parsedValues =
-                StringParameterParser.parse(queueFactorySpec, 
Set.of("supplier", "capacity"));
-
-        // Read the capacity
-        final int capacity = readQueueCapacity(queueFactorySpec, parsedValues);
-
-        // Read the supplier path
-        final StringParameterParser.Value supplierValue = 
parsedValues.get("supplier");
-        final String supplierPath = supplierValue == null || supplierValue 
instanceof StringParameterParser.NullValue
-                ? null
-                : supplierValue.toString();
-
-        // Execute the read spec
-        final QueueFactory queueFactory =
-                supplierPath != null ? QueueFactories.ofSupplier(supplierPath) 
: QueueFactories.INSTANCE;
-        return new QueueingRecyclerFactory(queueFactory, capacity);
-    }
-
-    private static int readQueueCapacity(
-            final String factorySpec, final Map<String, 
StringParameterParser.Value> parsedValues) {
-        final StringParameterParser.Value capacityValue = 
parsedValues.get("capacity");
-        if (capacityValue == null || capacityValue instanceof 
StringParameterParser.NullValue) {
-            return CAPACITY;
-        } else {
-            final int capacity;
-            try {
-                capacity = Integer.parseInt(capacityValue.toString());
-            } catch (final NumberFormatException error) {
-                throw new IllegalArgumentException(
-                        "failed reading `capacity` in recycler factory: " + 
factorySpec, error);
-            }
-            if (capacity < 1) {
-                throw new IllegalArgumentException(
-                        "was expecting `capacity > 0` in the recycler factory: 
" + factorySpec);
-            }
-            return capacity;
-        }
-    }
-}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/StringParameterParser.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/StringParameterParser.java
deleted file mode 100644
index 9d1127ae28..0000000000
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/StringParameterParser.java
+++ /dev/null
@@ -1,304 +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 java.util.*;
-import java.util.concurrent.Callable;
-import org.apache.logging.log4j.util.InternalApi;
-import org.apache.logging.log4j.util.Strings;
-
-/**
- * Utility class for parsing string-formatted parameters, e.g., {@code 
queue:supplier=com.acme.FastestQueue.new,capacity=42}.
- * <p>
- * See the associated test class for possible combinations and double-, 
single-quote handling.
- * </p>
- */
-@InternalApi
-public final class StringParameterParser {
-
-    private StringParameterParser() {}
-
-    public static final class Values {
-
-        private Values() {}
-
-        static NullValue nullValue() {
-            return NullValue.INSTANCE;
-        }
-
-        static StringValue stringValue(final String string) {
-            return new StringValue(string);
-        }
-
-        static DoubleQuotedStringValue doubleQuotedStringValue(final String 
doubleQuotedString) {
-            return new DoubleQuotedStringValue(doubleQuotedString);
-        }
-    }
-
-    public interface Value {}
-
-    public static final class NullValue implements Value {
-
-        private static final NullValue INSTANCE = new NullValue();
-
-        private NullValue() {}
-
-        @Override
-        public String toString() {
-            return "null";
-        }
-    }
-
-    public static final class StringValue implements Value {
-
-        private final String string;
-
-        private StringValue(final String string) {
-            this.string = string;
-        }
-
-        public String getString() {
-            return string;
-        }
-
-        @Override
-        public boolean equals(final Object object) {
-            if (this == object) {
-                return true;
-            }
-            if (object == null || getClass() != object.getClass()) {
-                return false;
-            }
-            final StringValue that = (StringValue) object;
-            return string.equals(that.string);
-        }
-
-        @Override
-        public int hashCode() {
-            return 31 + Objects.hashCode(string);
-        }
-
-        @Override
-        public String toString() {
-            return string;
-        }
-    }
-
-    public static final class DoubleQuotedStringValue implements Value {
-
-        private final String doubleQuotedString;
-
-        private DoubleQuotedStringValue(final String doubleQuotedString) {
-            this.doubleQuotedString = doubleQuotedString;
-        }
-
-        public String getDoubleQuotedString() {
-            return doubleQuotedString;
-        }
-
-        @Override
-        public boolean equals(final Object object) {
-            if (this == object) {
-                return true;
-            }
-            if (object == null || getClass() != object.getClass()) {
-                return false;
-            }
-            final DoubleQuotedStringValue that = (DoubleQuotedStringValue) 
object;
-            return doubleQuotedString.equals(that.doubleQuotedString);
-        }
-
-        @Override
-        public int hashCode() {
-            return 31 + Objects.hashCode(doubleQuotedString);
-        }
-
-        @Override
-        public String toString() {
-            return doubleQuotedString.replaceAll("\\\\\"", "\"");
-        }
-    }
-
-    private enum State {
-        READING_KEY,
-        READING_VALUE
-    }
-
-    private static final class Parser implements Callable<Map<String, Value>> {
-
-        private final String input;
-
-        private final Map<String, Value> map;
-
-        private State state;
-
-        private int i;
-
-        private String key;
-
-        private Parser(final String input) {
-            this.input = Objects.requireNonNull(input, "input");
-            this.map = new LinkedHashMap<>();
-            this.state = State.READING_KEY;
-            this.i = 0;
-            this.key = null;
-        }
-
-        @Override
-        public Map<String, Value> call() {
-            while (true) {
-                skipWhitespace();
-                if (i >= input.length()) {
-                    break;
-                }
-                switch (state) {
-                    case READING_KEY:
-                        readKey();
-                        break;
-                    case READING_VALUE:
-                        readValue();
-                        break;
-                    default:
-                        throw new IllegalStateException("unknown state: " + 
state);
-                }
-            }
-            if (state == State.READING_VALUE) {
-                map.put(key, Values.nullValue());
-            }
-            return map;
-        }
-
-        private void readKey() {
-            final int eq = input.indexOf('=', i);
-            final int co = input.indexOf(',', i);
-            final int j;
-            final int nextI;
-            if (eq < 0 && co < 0) {
-                // Neither '=', nor ',' was found.
-                j = nextI = input.length();
-            } else if (eq < 0) {
-                // Found ','.
-                j = nextI = co;
-            } else if (co < 0) {
-                // Found '='.
-                j = eq;
-                nextI = eq + 1;
-            } else if (eq < co) {
-                // Found '=...,'.
-                j = eq;
-                nextI = eq + 1;
-            } else {
-                // Found ',...='.
-                j = co;
-                nextI = co;
-            }
-            key = input.substring(i, j).trim();
-            if (Strings.isEmpty(key)) {
-                final String message = String.format("failed to locate key at 
index %d: %s", i, input);
-                throw new IllegalArgumentException(message);
-            }
-            if (map.containsKey(key)) {
-                final String message = String.format("conflicting key at index 
%d: %s", i, input);
-                throw new IllegalArgumentException(message);
-            }
-            state = State.READING_VALUE;
-            i = nextI;
-        }
-
-        private void readValue() {
-            final boolean doubleQuoted = input.charAt(i) == '"';
-            if (doubleQuoted) {
-                readDoubleQuotedStringValue();
-            } else {
-                readStringValue();
-            }
-            key = null;
-            state = State.READING_KEY;
-        }
-
-        private void readDoubleQuotedStringValue() {
-            int j = i + 1;
-            while (j < input.length()) {
-                if (input.charAt(j) == '"' && input.charAt(j - 1) != '\\') {
-                    break;
-                } else {
-                    j++;
-                }
-            }
-            if (j >= input.length()) {
-                final String message = String.format(
-                        "failed to locate the end of double-quoted content 
starting at index %d: %s", i, input);
-                throw new IllegalArgumentException(message);
-            }
-            final String content = input.substring(i + 1, 
j).replaceAll("\\\\\"", "\"");
-            final Value value = Values.doubleQuotedStringValue(content);
-            map.put(key, value);
-            i = j + 1;
-            skipWhitespace();
-            if (i < input.length()) {
-                if (input.charAt(i) != ',') {
-                    final String message = String.format("was expecting comma 
at index %d: %s", i, input);
-                    throw new IllegalArgumentException(message);
-                }
-                i++;
-            }
-        }
-
-        private void skipWhitespace() {
-            while (i < input.length()) {
-                final char c = input.charAt(i);
-                if (!Character.isWhitespace(c)) {
-                    break;
-                } else {
-                    i++;
-                }
-            }
-        }
-
-        private void readStringValue() {
-            int j = input.indexOf(',', i /* + 1*/);
-            if (j < 0) {
-                j = input.length();
-            }
-            final String content = input.substring(i, j);
-            final String trimmedContent = content.trim();
-            final Value value = trimmedContent.isEmpty() ? Values.nullValue() 
: Values.stringValue(trimmedContent);
-            map.put(key, value);
-            i += content.length() + 1;
-        }
-    }
-
-    public static Map<String, Value> parse(final String input) {
-        return parse(input, null);
-    }
-
-    public static Map<String, Value> parse(final String input, final 
Set<String> allowedKeys) {
-        if (Strings.isBlank(input)) {
-            return Collections.emptyMap();
-        }
-        final Map<String, Value> map = new Parser(input).call();
-        final Set<String> actualKeys = map.keySet();
-        for (final String actualKey : actualKeys) {
-            final boolean allowed = allowedKeys == null || 
allowedKeys.contains(actualKey);
-            if (!allowed) {
-                final String message = String.format("unknown key \"%s\" is 
found in input: %s", actualKey, input);
-                throw new IllegalArgumentException(message);
-            }
-        }
-        return map;
-    }
-}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/ThreadLocalRecyclerFactory.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/ThreadLocalRecyclerFactory.java
deleted file mode 100644
index a05e9f3166..0000000000
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/ThreadLocalRecyclerFactory.java
+++ /dev/null
@@ -1,101 +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 static java.util.Objects.requireNonNull;
-
-import java.util.Queue;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-import org.apache.logging.log4j.spi.AbstractRecycler;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
-
-/**
- * A {@link RecyclerFactory} pooling objects in a queue stored in a {@link 
ThreadLocal}.
- * <p>
- * This strategy may not be appropriate in workloads where units of work are 
independent of operating system threads such as reactive streams, coroutines, 
or virtual threads.
- * For such use cases, see {@link QueueingRecyclerFactory}.
- * </p>
- *
- * @since 3.0.0
- */
-final class ThreadLocalRecyclerFactory implements RecyclerFactory {
-
-    /**
-     * Maximum number of objects retained per thread.
-     * <p>
-     * This allows to acquire objects in recursive method calls and maintain 
minimal overhead in the scenarios where the active instance count goes far 
beyond this for a brief moment.
-     * </p>
-     */
-    private final int capacity;
-
-    ThreadLocalRecyclerFactory(int capacity) {
-        if (capacity < 1) {
-            throw new IllegalArgumentException("was expecting a `capacity` 
greater than 1, found: " + capacity);
-        }
-        this.capacity = capacity;
-    }
-
-    /**
-     * @return maximum number of objects retained per thread in recyclers 
created
-     */
-    int getCapacity() {
-        return capacity;
-    }
-
-    @Override
-    public <V> Recycler<V> create(final Supplier<V> supplier, final 
Consumer<V> cleaner) {
-        requireNonNull(supplier, "supplier");
-        requireNonNull(cleaner, "cleaner");
-        return new ThreadLocalRecycler<>(supplier, cleaner, capacity);
-    }
-
-    // Visible for testing
-    static class ThreadLocalRecycler<V> extends AbstractRecycler<V> {
-
-        private final Consumer<V> cleaner;
-
-        private final ThreadLocal<Queue<V>> queueRef;
-
-        private ThreadLocalRecycler(final Supplier<V> supplier, final 
Consumer<V> cleaner, final int capacity) {
-            super(supplier);
-            this.queueRef = ThreadLocal.withInitial(() -> new 
ArrayQueue<>(capacity));
-            this.cleaner = cleaner;
-        }
-
-        @Override
-        public V acquire() {
-            final Queue<V> queue = queueRef.get();
-            final V value = queue.poll();
-            return value != null ? value : createInstance();
-        }
-
-        @Override
-        public void release(final V value) {
-            requireNonNull(value, "value");
-            cleaner.accept(value);
-            final Queue<V> queue = queueRef.get();
-            queue.offer(value);
-        }
-
-        // Visible for testing
-        Queue<V> getQueue() {
-            return queueRef.get();
-        }
-    }
-}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/ArrayQueue.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ArrayQueue.java
similarity index 98%
rename from 
log4j-api/src/main/java/org/apache/logging/log4j/internal/ArrayQueue.java
rename to 
log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ArrayQueue.java
index 238efa8796..90c573b364 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/ArrayQueue.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ArrayQueue.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.internal;
+package org.apache.logging.log4j.internal.recycler;
 
 import java.util.AbstractQueue;
 import java.util.Iterator;
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/DummyRecyclerFactoryProvider.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/DummyRecyclerFactoryProvider.java
new file mode 100644
index 0000000000..f0a531b657
--- /dev/null
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/DummyRecyclerFactoryProvider.java
@@ -0,0 +1,81 @@
+/*
+ * 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.recycler;
+
+import static java.util.Objects.requireNonNull;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.spi.recycler.AbstractRecycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+
+/**
+ * A {@link Recycler} factory provider such that the recycler does not recycle 
anything; all instances are freshly created.
+ *
+ * @since 3.0.0
+ */
+@ServiceProvider(RecyclerFactoryProvider.class)
+public final class DummyRecyclerFactoryProvider implements 
RecyclerFactoryProvider {
+
+    @Override
+    public int getOrder() {
+        return 900;
+    }
+
+    @Override
+    public String getName() {
+        return "dummy";
+    }
+
+    @Override
+    public RecyclerFactory createForEnvironment(final PropertyEnvironment 
environment) {
+        return DummyRecyclerFactory.INSTANCE;
+    }
+
+    // Visible for testing
+    static final class DummyRecyclerFactory implements RecyclerFactory {
+
+        private static final DummyRecyclerFactory INSTANCE = new 
DummyRecyclerFactory();
+
+        private DummyRecyclerFactory() {}
+
+        @Override
+        public <V> Recycler<V> create(final Supplier<V> supplier, final 
Consumer<V> cleaner) {
+            requireNonNull(supplier, "supplier");
+            return new DummyRecycler<>(supplier);
+        }
+
+        private static final class DummyRecycler<V> extends 
AbstractRecycler<V> {
+
+            private DummyRecycler(final Supplier<V> supplier) {
+                super(supplier);
+            }
+
+            @Override
+            public V acquire() {
+                return createInstance();
+            }
+
+            @Override
+            public void release(final V value) {}
+        }
+    }
+}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/QueueingRecyclerFactoryProvider.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/QueueingRecyclerFactoryProvider.java
new file mode 100644
index 0000000000..655e9e4a9e
--- /dev/null
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/QueueingRecyclerFactoryProvider.java
@@ -0,0 +1,106 @@
+/*
+ * 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.recycler;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.logging.log4j.spi.recycler.Recycler.DEFAULT_CAPACITY;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.spi.LoggingSystemProperty;
+import org.apache.logging.log4j.spi.recycler.AbstractRecycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+
+/**
+ * A {@link Recycler} factory provider such that the recycler pools objects in 
a fixed-size queue.
+ */
+@ServiceProvider(RecyclerFactoryProvider.class)
+public final class QueueingRecyclerFactoryProvider implements 
RecyclerFactoryProvider {
+
+    @Override
+    public int getOrder() {
+        return 800;
+    }
+
+    @Override
+    public String getName() {
+        return "queue";
+    }
+
+    @Override
+    public RecyclerFactory createForEnvironment(final PropertyEnvironment 
environment) {
+        requireNonNull(environment, "environment");
+        final int capacity = 
environment.getIntegerProperty(LoggingSystemProperty.RECYCLER_CAPACITY, 
DEFAULT_CAPACITY);
+        if (capacity < 1) {
+            throw new IllegalArgumentException("was expecting a `capacity` 
greater than 1, found: " + capacity);
+        }
+        return new QueueingRecyclerFactory(capacity);
+    }
+
+    // Visible for testing
+    static final class QueueingRecyclerFactory implements RecyclerFactory {
+
+        // Visible for testing
+        final int capacity;
+
+        private QueueingRecyclerFactory(int capacity) {
+            this.capacity = capacity;
+        }
+
+        @Override
+        public <V> Recycler<V> create(final Supplier<V> supplier, final 
Consumer<V> cleaner) {
+            requireNonNull(supplier, "supplier");
+            requireNonNull(cleaner, "cleaner");
+            final Queue<V> queue = new ArrayBlockingQueue<>(capacity);
+            return new QueueingRecycler<>(supplier, cleaner, queue);
+        }
+
+        // Visible for testing
+        static final class QueueingRecycler<V> extends AbstractRecycler<V> {
+
+            private final Consumer<V> cleaner;
+
+            // Visible for testing
+            final Queue<V> queue;
+
+            private QueueingRecycler(final Supplier<V> supplier, final 
Consumer<V> cleaner, final Queue<V> queue) {
+                super(supplier);
+                this.cleaner = cleaner;
+                this.queue = queue;
+            }
+
+            @Override
+            public V acquire() {
+                final V value = queue.poll();
+                return value != null ? value : createInstance();
+            }
+
+            @Override
+            public void release(final V value) {
+                requireNonNull(value, "value");
+                cleaner.accept(value);
+                queue.offer(value);
+            }
+        }
+    }
+}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProvider.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProvider.java
new file mode 100644
index 0000000000..6cdbba0863
--- /dev/null
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProvider.java
@@ -0,0 +1,125 @@
+/*
+ * 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.recycler;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.logging.log4j.spi.recycler.Recycler.DEFAULT_CAPACITY;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import edu.umd.cs.findbugs.annotations.Nullable;
+import java.util.Queue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.spi.LoggingSystemProperty;
+import org.apache.logging.log4j.spi.recycler.AbstractRecycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+
+/**
+ * A {@link Recycler} factory provider such that the recycler pools objects in 
a fixed-size queue stored in a {@link ThreadLocal}.
+ * <p>
+ * This strategy may not be appropriate in workloads where units of work are 
independent of operating system threads such as reactive streams, coroutines, 
or virtual threads.
+ * For such use cases, see {@link QueueingRecyclerFactoryProvider}.
+ * </p>
+ *
+ * @since 3.0.0
+ */
+@ServiceProvider(RecyclerFactoryProvider.class)
+public final class ThreadLocalRecyclerFactoryProvider implements 
RecyclerFactoryProvider {
+
+    @Override
+    public int getOrder() {
+        return 700;
+    }
+
+    @Override
+    public String getName() {
+        return "threadLocal";
+    }
+
+    @Nullable
+    @Override
+    public RecyclerFactory createForEnvironment(PropertyEnvironment 
environment) {
+        requireNonNull(environment, "environment");
+        final boolean threadLocalEnabled =
+                
environment.getBooleanProperty(LoggingSystemProperty.THREAD_LOCALS_ENABLE, 
true);
+        if (!threadLocalEnabled) {
+            return null;
+        }
+        final int capacity = 
environment.getIntegerProperty(LoggingSystemProperty.RECYCLER_CAPACITY, 
DEFAULT_CAPACITY);
+        if (capacity < 1) {
+            throw new IllegalArgumentException("was expecting a `capacity` 
greater than 1, found: " + capacity);
+        }
+        return new ThreadLocalRecyclerFactory(capacity);
+    }
+
+    // Visible for testing
+    static final class ThreadLocalRecyclerFactory implements RecyclerFactory {
+
+        /**
+         * Maximum number of objects retained per thread.
+         * <p>
+         * This allows to acquire objects in recursive method calls and 
maintain minimal overhead in the scenarios where the active instance count goes 
far beyond this for a brief moment.
+         * </p>
+         */
+        // Visible for testing
+        final int capacity;
+
+        private ThreadLocalRecyclerFactory(int capacity) {
+            this.capacity = capacity;
+        }
+
+        @Override
+        public <V> Recycler<V> create(final Supplier<V> supplier, final 
Consumer<V> cleaner) {
+            requireNonNull(supplier, "supplier");
+            requireNonNull(cleaner, "cleaner");
+            return new ThreadLocalRecycler<>(supplier, cleaner, capacity);
+        }
+
+        // Visible for testing
+        static final class ThreadLocalRecycler<V> extends AbstractRecycler<V> {
+
+            private final Consumer<V> cleaner;
+
+            // Visible for testing
+            final ThreadLocal<Queue<V>> queueRef;
+
+            private ThreadLocalRecycler(final Supplier<V> supplier, final 
Consumer<V> cleaner, final int capacity) {
+                super(supplier);
+                this.queueRef = ThreadLocal.withInitial(() -> new 
ArrayQueue<>(capacity));
+                this.cleaner = cleaner;
+            }
+
+            @Override
+            public V acquire() {
+                final Queue<V> queue = queueRef.get();
+                final V value = queue.poll();
+                return value != null ? value : createInstance();
+            }
+
+            @Override
+            public void release(final V value) {
+                requireNonNull(value, "value");
+                cleaner.accept(value);
+                final Queue<V> queue = queueRef.get();
+                queue.offer(value);
+            }
+        }
+    }
+}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
index 50f457140e..21176cd0c5 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
@@ -16,7 +16,7 @@
  */
 package org.apache.logging.log4j.message;
 
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
 
 /**
  * Creates messages. Implementations can provide different message format 
syntaxes.
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
index f980631224..aae12e6f37 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
@@ -19,7 +19,7 @@ package org.apache.logging.log4j.message;
 import java.util.Arrays;
 import java.util.Objects;
 import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
 import org.apache.logging.log4j.util.StringBuilderFormattable;
 import org.apache.logging.log4j.util.StringBuilders;
 
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
index a2523c2ae4..7abd8ac064 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
@@ -17,8 +17,8 @@
 package org.apache.logging.log4j.message;
 
 import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.util.PerformanceSensitive;
 
 /**
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
index 326da8dabe..928e201f1b 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
@@ -18,8 +18,8 @@ package org.apache.logging.log4j.message;
 
 import java.util.Arrays;
 import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.util.Constants;
 import org.apache.logging.log4j.util.PerformanceSensitive;
 import org.apache.logging.log4j.util.StringBuilders;
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
index 2b5606714c..5dd788c06b 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
@@ -27,6 +27,8 @@ import org.apache.logging.log4j.message.FlowMessageFactory;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.message.StringFormattedMessage;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.Cast;
 import org.apache.logging.log4j.util.LambdaUtil;
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java
deleted file mode 100644
index 0076cd49e6..0000000000
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.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.spi;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/**
- * Recycler strategy which doesn't recycle anything; all instances are freshly 
created.
- *
- * @since 3.0.0
- */
-public final class DummyRecyclerFactory implements RecyclerFactory {
-
-    private static final DummyRecyclerFactory INSTANCE = new 
DummyRecyclerFactory();
-
-    private DummyRecyclerFactory() {}
-
-    public static DummyRecyclerFactory getInstance() {
-        return INSTANCE;
-    }
-
-    @Override
-    public <V> Recycler<V> create(final Supplier<V> supplier, final 
Consumer<V> cleaner) {
-        requireNonNull(supplier, "supplier");
-        return new DummyRecycler<>(supplier);
-    }
-
-    private static class DummyRecycler<V> extends AbstractRecycler<V> {
-
-        private DummyRecycler(final Supplier<V> supplier) {
-            super(supplier);
-        }
-
-        @Override
-        public V acquire() {
-            return createInstance();
-        }
-
-        @Override
-        public void release(final V value) {}
-    }
-}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java
index 08daa2e05e..46cf2ae91f 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java
@@ -41,13 +41,14 @@ import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.ThreadContext;
-import org.apache.logging.log4j.internal.RecyclerFactories;
 import org.apache.logging.log4j.message.DefaultFlowMessageFactory;
 import org.apache.logging.log4j.message.FlowMessageFactory;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.message.ParameterizedMessageFactory;
 import org.apache.logging.log4j.message.ReusableMessageFactory;
 import org.apache.logging.log4j.simple.SimpleLoggerContextFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryRegistry;
 import org.apache.logging.log4j.util.Constants;
 import org.apache.logging.log4j.util.Lazy;
 import org.apache.logging.log4j.util.LoaderUtil;
@@ -105,7 +106,8 @@ public class LoggingSystem {
             environmentLazy.map(environment -> () -> 
getProvider().createContextMap(environment));
     private final Lazy<Supplier<ThreadContextStack>> 
threadContextStackFactoryLazy =
             environmentLazy.map(environment -> () -> 
getProvider().createContextStack(environment));
-    private final Lazy<RecyclerFactory> recyclerFactoryLazy = Lazy.lazy(() -> 
RecyclerFactories.INSTANCE);
+    private final Lazy<RecyclerFactory> recyclerFactoryLazy =
+            environmentLazy.map(RecyclerFactoryRegistry::findRecyclerFactory);
 
     public LoggingSystem() {}
 
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperty.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperty.java
index b9d814cb15..63183ae763 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperty.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperty.java
@@ -190,7 +190,15 @@ public enum LoggingSystemProperty implements PropertyKey {
      * (from either {@code javax} or {@code jakarta}) is checked to see if 
this is a webapp.
      */
     // Web.isWebApp : calculate | true | false
-    IS_WEBAPP(PropertyComponent.WEB, Constant.IS_WEB_APP);
+    IS_WEBAPP(PropertyComponent.WEB, Constant.IS_WEB_APP),
+    /**
+     * Property to override the default recycler factory.
+     */
+    RECYCLER_FACTORY(PropertyComponent.RECYCLER, Constant.RECYCLER_FACTORY),
+    /**
+     * Property to override the default recycler capacity.
+     */
+    RECYCLER_CAPACITY(PropertyComponent.RECYCLER, Constant.RECYCLER_CAPACITY);
 
     public static final String SIMPLE_LOGGER_LOG_LEVEL = 
"SimpleLogger.%s.level";
     public static final String SYSTEM_PROPERTY_PREFIX = "log4j2.*.";
@@ -476,5 +484,8 @@ public enum LoggingSystemProperty implements PropertyKey {
         static final String IS_WEB_APP = "isWebApp";
         public static final String WEB =
                 LoggingSystemProperty.SYSTEM_PROPERTY_PREFIX + 
PropertyComponent.Constant.WEB + DELIM + IS_WEB_APP;
+
+        static final String RECYCLER_FACTORY = "factory";
+        static final String RECYCLER_CAPACITY = "capacity";
     }
 }
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/PropertyComponent.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/PropertyComponent.java
index 8a748bc84a..5dd99ef77a 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/PropertyComponent.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/PropertyComponent.java
@@ -35,6 +35,7 @@ public enum PropertyComponent {
     LOGGER(Constant.LOGGER),
     LOGGER_CONTEXT(Constant.LOGGER_CONTEXT),
     MESSAGE(Constant.MESSAGE),
+    RECYCLER(Constant.RECYCLER),
     SCRIPT(Constant.SCRIPT),
     SIMPLE_LOGGER(Constant.SIMPLE_LOGGER),
     STATUS_LOGGER(Constant.STATUS_LOGGER),
@@ -74,6 +75,7 @@ public enum PropertyComponent {
         public static final String LOGGER = "Logger";
         public static final String LOGGER_CONTEXT = "LoggerContext";
         public static final String MESSAGE = "Message";
+        public static final String RECYCLER = "Recycler";
         public static final String SCRIPT = "Script";
         public static final String SIMPLE_LOGGER = "SimpleLogger";
         public static final String STATUS_LOGGER = "StatusLogger";
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueFactory.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueFactory.java
deleted file mode 100644
index 6c41f637fd..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueFactory.java
+++ /dev/null
@@ -1,32 +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 java.util.Queue;
-import org.apache.logging.log4j.internal.QueueFactories;
-
-/**
- * A {@link Queue} factory contract.
- *
- * @see QueueFactories
- * @since 3.0.0
- */
-@FunctionalInterface
-public interface QueueFactory {
-
-    <E> Queue<E> create(final int capacity);
-}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java
deleted file mode 100644
index 96c9f010e5..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java
+++ /dev/null
@@ -1,58 +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 java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/**
- * Factory for {@link Recycler} strategies. Depending on workloads, different 
instance recycling strategies may be
- * most performant. For example, traditional multithreaded workloads may 
benefit from using thread-local instance
- * recycling while different models of concurrency or versions of the JVM may 
benefit from using an object pooling
- * strategy instead.
- *
- * @since 3.0.0
- */
-@FunctionalInterface
-public interface RecyclerFactory {
-
-    /**
-     * Creates a new recycler using the given supplier function for initial 
instances. These instances have
-     * no cleaner function and are assumed to always be reusable.
-     *
-     * @param supplier function to provide new instances of a recyclable object
-     * @param <V> the recyclable type
-     * @return a new recycler for V-type instances
-     */
-    default <V> Recycler<V> create(final Supplier<V> supplier) {
-        return create(supplier, ignored -> {});
-    }
-
-    /**
-     * Creates a new recycler using the given functions for providing fresh 
instances and for cleaning up
-     * existing instances for reuse. For example, a StringBuilder recycler 
would provide two functions:
-     * a supplier function for constructing a new StringBuilder with a 
preselected initial capacity and
-     * another function for trimming the StringBuilder to some preselected 
maximum capacity and setting
-     * its length back to 0 as if it were a fresh StringBuilder.
-     *
-     * @param supplier function to provide new instances of a recyclable object
-     * @param cleaner function to reset a recyclable object to a fresh state
-     * @param <V> the recyclable type
-     * @return a new recycler for V-type instances
-     */
-    <V> Recycler<V> create(Supplier<V> supplier, Consumer<V> cleaner);
-}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/AbstractRecycler.java
similarity index 80%
rename from 
log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java
rename to 
log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/AbstractRecycler.java
index 6b74698a9e..84b5924f25 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/AbstractRecycler.java
@@ -14,7 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.spi;
+package org.apache.logging.log4j.spi.recycler;
+
+import static java.util.Objects.requireNonNull;
 
 import java.util.function.Supplier;
 
@@ -22,15 +24,15 @@ public abstract class AbstractRecycler<V> implements 
Recycler<V> {
 
     private final Supplier<V> supplier;
 
-    public AbstractRecycler(final Supplier<V> supplier) {
-        this.supplier = supplier;
+    protected AbstractRecycler(final Supplier<V> supplier) {
+        this.supplier = requireNonNull(supplier, "supplier");
     }
 
     protected final V createInstance() {
         final V instance = supplier.get();
         if (instance instanceof RecyclerAware) {
             @SuppressWarnings("unchecked")
-            RecyclerAware<V> recyclerAware = (RecyclerAware<V>) instance;
+            final RecyclerAware<V> recyclerAware = (RecyclerAware<V>) instance;
             recyclerAware.setRecycler(this);
         }
         return instance;
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/Recycler.java
similarity index 77%
rename from log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java
rename to 
log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/Recycler.java
index 50cd53a471..4a9db5b0b0 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/Recycler.java
@@ -14,16 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.spi;
+package org.apache.logging.log4j.spi.recycler;
 
 /**
- * Strategy for recycling objects. This is primarily useful for heavyweight 
objects and buffers.
+ * Contract for recycling strategies.
+ * This is the primary building block for logging components striving for 
garbage-free operation.
  *
  * @param <V> the recyclable type
  * @since 3.0.0
  */
 public interface Recycler<V> {
 
+    /**
+     * The default recycler capacity: {@code max(2C+1, 8)}, {@code C} denoting 
the number of available processors
+     */
+    int DEFAULT_CAPACITY = Math.max(2 * 
Runtime.getRuntime().availableProcessors() + 1, 8);
+
     /**
      * Acquires an instance of V. This may either be a fresh instance of V or 
a recycled instance of V.
      * Recycled instances will be modified by their cleanup function before 
being returned.
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerAware.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerAware.java
similarity index 95%
rename from 
log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerAware.java
rename to 
log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerAware.java
index 42e4d72163..4483ed3a02 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerAware.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerAware.java
@@ -14,7 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.spi;
+package org.apache.logging.log4j.spi.recycler;
+
 /**
  * Interface implemented by classes that need to interact with the {@link 
Recycler} that created them.
  */
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactory.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactory.java
new file mode 100644
index 0000000000..bb935b1958
--- /dev/null
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactory.java
@@ -0,0 +1,58 @@
+/*
+ * 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.recycler;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Contract for {@link Recycler} factories.
+ *
+ * @since 3.0.0
+ */
+public interface RecyclerFactory {
+
+    /**
+     * Creates a new recycler using the given supplier function for initial 
instances.
+     *
+     * @param supplier a function to provide initial instances
+     * @param <V> the recyclable type
+     * @return a new recycler
+     */
+    default <V> Recycler<V> create(final Supplier<V> supplier) {
+        return create(supplier, ignored -> {});
+    }
+
+    /**
+     * Creates a new recycler using the given supplier and cleaner functions.
+     * <p>
+     * The provided supplier needs to make sure that generated instances are 
always clean.
+     * </p>
+     * <p>
+     * Recycled instances are always guaranteed to be clean.
+     * The cleaning of an instance can take place either just before 
acquisition or prior to admitting it back into the reusable instances pool.
+     * The moment when the cleaning will be carried out is implementation 
dependent.
+     * Though a released instance should ideally be cleaned immediately to 
avoid keeping references to unused objects.
+     * </p>
+     *
+     * @param supplier a function to provide initial (and clean!) instances
+     * @param cleaner function to reset an instance before reuse
+     * @param <V> the recyclable type
+     * @return a new recycler
+     */
+    <V> Recycler<V> create(Supplier<V> supplier, Consumer<V> cleaner);
+}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactoryProvider.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactoryProvider.java
new file mode 100644
index 0000000000..d049e2d01e
--- /dev/null
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactoryProvider.java
@@ -0,0 +1,59 @@
+/*
+ * 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.recycler;
+
+import edu.umd.cs.findbugs.annotations.Nullable;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+
+/**
+ * Contract for providing {@link RecyclerFactory} instances.
+ *
+ * @since 3.0.0
+ */
+public interface RecyclerFactoryProvider {
+
+    /**
+     * Denotes the value to be used while sorting recycler factory providers 
to determine the precedence order.
+     * Values will be sorted naturally, that is, lower values will imply 
higher precedence.
+     *
+     * @return the value to be used while sorting
+     */
+    default int getOrder() {
+        return 100;
+    }
+
+    /**
+     * The name of this recycler factory provider.
+     * Recycler factory providers are required to have unique names.
+     *
+     * @return the name of this recycler factory provider
+     */
+    String getName();
+
+    /**
+     * Creates a recycler factory for the provided environment.
+     * <p>
+     * The return value can be null indicating that the recycler factory is 
not available for the provided environment.
+     * For instance, the provider of a {@link ThreadLocal}-based recycler 
factory can return null if the environment is of a web application.
+     * </p>
+     *
+     * @param environment an environment
+     * @return either a recycler factory instance, or null, if the associated 
recycler factory is not available for the given environment
+     */
+    @Nullable
+    RecyclerFactory createForEnvironment(PropertyEnvironment environment);
+}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactoryRegistry.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactoryRegistry.java
new file mode 100644
index 0000000000..ba17fc98e9
--- /dev/null
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactoryRegistry.java
@@ -0,0 +1,119 @@
+/*
+ * 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.recycler;
+
+import static java.util.Objects.requireNonNull;
+
+import aQute.bnd.annotation.spi.ServiceConsumer;
+import edu.umd.cs.findbugs.annotations.Nullable;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.logging.log4j.spi.LoggingSystemProperty;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
+
+/**
+ * Registry of available {@link RecyclerFactoryProvider}s.
+ */
+@ServiceConsumer(RecyclerFactoryProvider.class)
+public final class RecyclerFactoryRegistry {
+
+    private static final Map<String, RecyclerFactoryProvider> PROVIDER_BY_NAME 
= loadProviders();
+
+    private static Map<String, RecyclerFactoryProvider> loadProviders() {
+        return ServiceLoaderUtil.safeStream(RecyclerFactoryProvider.class, 
null)
+                
.sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder))
+                .collect(Collectors.toMap(
+                        RecyclerFactoryProvider::getName,
+                        Function.identity(),
+                        (provider1, provider2) -> {
+                            final String message = String.format(
+                                    "recycler factory providers `%s` 
(order=%d) and `%s` (order=%d) have conflicting names: `%s`",
+                                    provider1.getClass().getCanonicalName(),
+                                    provider1.getOrder(),
+                                    provider2.getClass().getCanonicalName(),
+                                    provider2.getOrder(),
+                                    provider2.getName());
+                            throw new IllegalStateException(message);
+                        },
+                        LinkedHashMap::new));
+    }
+
+    private RecyclerFactoryRegistry() {}
+
+    public static Collection<RecyclerFactoryProvider> 
getRecyclerFactoryProviders() {
+        return PROVIDER_BY_NAME.values();
+    }
+
+    public static RecyclerFactory findRecyclerFactory(final 
PropertyEnvironment environment) {
+
+        // Check arguments
+        requireNonNull(environment, "environment");
+
+        // Short-circuit if there are no recycler factories available
+        if (PROVIDER_BY_NAME.isEmpty()) {
+            throw new IllegalStateException("couldn't find any recycler 
factories");
+        }
+
+        // If there is a particular recycler factory requested, get that done.
+        @Nullable final String name = 
environment.getStringProperty(LoggingSystemProperty.RECYCLER_FACTORY);
+        if (name != null) {
+            return findRecyclerFactoryByName(environment, name);
+        }
+
+        // Otherwise, find one available
+        return PROVIDER_BY_NAME.values().stream()
+                
.sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder))
+                .map(provider -> provider.createForEnvironment(environment))
+                .filter(Objects::nonNull)
+                .findFirst()
+                .orElseThrow(() -> {
+                    final String names = PROVIDER_BY_NAME.keySet().stream()
+                            .map(name_ -> '`' + name_ + '`')
+                            .collect(Collectors.joining(", "));
+                    final String message = String.format(
+                            "Couldn't find a recycler factory provider 
available for the provided environment. Names of the available recycler factory 
providers: %s",
+                            names);
+                    return new IllegalArgumentException(message);
+                });
+    }
+
+    private static RecyclerFactory findRecyclerFactoryByName(final 
PropertyEnvironment environment, final String name) {
+        @Nullable final RecyclerFactoryProvider provider = 
PROVIDER_BY_NAME.get(name);
+        if (provider == null) {
+            final String names = PROVIDER_BY_NAME.keySet().stream()
+                    .map(name_ -> '`' + name_ + '`')
+                    .collect(Collectors.joining(", "));
+            final String message = String.format(
+                    "Couldn't find a recycler factory provider of name `%s`. 
Names of the available recycler factory providers: %s",
+                    name, names);
+            throw new IllegalArgumentException(message);
+        }
+        @Nullable final RecyclerFactory factory = 
provider.createForEnvironment(environment);
+        if (factory == null) {
+            final String message = String.format(
+                    "failed to configure recycler factory of name `%s` for the 
provided environment", name);
+            throw new IllegalArgumentException(message);
+        }
+        return factory;
+    }
+}
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/package-info.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/package-info.java
new file mode 100644
index 0000000000..094ac880cd
--- /dev/null
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+/**
+ * Internal interfaces and classes to be used by authors of logging 
implementations or for internal use by
+ * API classes.
+ */
+@Export
+@Version("2.20.1")
+package org.apache.logging.log4j.spi.recycler;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
index d5134593ef..f198bbee3e 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
@@ -21,12 +21,12 @@ import java.io.IOException;
 import java.util.Collection;
 import java.util.List;
 import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Marker;
-import org.apache.logging.log4j.internal.QueueFactories;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory;
 import org.apache.logging.log4j.simple.SimpleLogger;
@@ -63,7 +63,7 @@ public final class StatusLogger extends AbstractLogger {
 
     private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
 
-    private final Queue<StatusData> messages;
+    private final Queue<StatusData> messages = new ConcurrentLinkedQueue<>();
 
     private int listenersLevel;
 
@@ -97,7 +97,6 @@ public final class StatusLogger extends AbstractLogger {
         this.logger = logger;
         this.configuration = configuration;
         this.listenersLevel = configuration.getDefaultLevel().intLevel();
-        messages = 
QueueFactories.INSTANCE.create(configuration.getMaxEntries());
     }
 
     /**
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
index b9be78fcd1..7fe952cbd0 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
@@ -16,6 +16,9 @@
  */
 package org.apache.logging.log4j.util;
 
+import static java.util.Objects.requireNonNull;
+
+import edu.umd.cs.findbugs.annotations.Nullable;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.ServiceConfigurationError;
@@ -36,7 +39,15 @@ public final class ServiceLoaderUtil {
 
     private ServiceLoaderUtil() {}
 
+    public static <S> Stream<S> safeStream(final Class<S> type, @Nullable 
final ClassLoader classLoader) {
+        requireNonNull(type, "type");
+        final ClassLoader effectiveLoader = classLoader != null ? classLoader 
: type.getClassLoader();
+        final ServiceLoader<S> serviceLoader = ServiceLoader.load(type, 
effectiveLoader);
+        return safeStream(serviceLoader);
+    }
+
     public static <S> Stream<S> safeStream(final ServiceLoader<S> 
serviceLoader) {
+        requireNonNull(serviceLoader, "serviceLoader");
         final Set<Class<?>> classes = new HashSet<>();
         return StreamSupport.stream(new 
ServiceLoaderSpliterator<>(serviceLoader), false)
                 // only the first occurrence of a class
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
index d5a2c6f168..0d93461512 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
@@ -39,7 +39,7 @@ import org.apache.logging.log4j.core.util.NetUtils;
 import org.apache.logging.log4j.core.util.WatchManager;
 import org.apache.logging.log4j.plugins.Node;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.util.PropertyEnvironment;
 
 /**
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
index 408d0845d3..2a21b56b22 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
@@ -35,8 +35,8 @@ import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.PluginAttribute;
 import org.apache.logging.log4j.plugins.PluginElement;
 import org.apache.logging.log4j.plugins.PluginFactory;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
 import org.apache.logging.log4j.util.PerformanceSensitive;
 import org.apache.logging.log4j.util.StringBuilders;
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java
index d4ae74c892..7d9091071b 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java
@@ -45,6 +45,7 @@ import org.apache.logging.log4j.plugins.SingletonFactory;
 import org.apache.logging.log4j.plugins.condition.ConditionalOnMissingBinding;
 import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory;
 import org.apache.logging.log4j.spi.*;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 
 /**
  * Provides instance binding defaults.
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
index 4e64b8c2cc..44e442eb52 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
@@ -34,7 +34,7 @@ import org.apache.logging.log4j.message.ParameterVisitable;
 import org.apache.logging.log4j.message.ReusableMessage;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.message.TimestampMessage;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
 import org.apache.logging.log4j.util.StackLocatorUtil;
 import org.apache.logging.log4j.util.StringBuilders;
 import org.apache.logging.log4j.util.StringMap;
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
index 1ac3f5dd89..6f40131165 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
@@ -29,8 +29,8 @@ import org.apache.logging.log4j.core.time.Clock;
 import org.apache.logging.log4j.core.time.NanoClock;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.plugins.Inject;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 
 /**
  * Garbage-free LogEventFactory that recycles mutable {@link LogEvent} 
instances.
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
index dceca48d88..569e3873f0 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
@@ -24,8 +24,8 @@ import org.apache.logging.log4j.core.config.LoggerConfig;
 import org.apache.logging.log4j.core.impl.Log4jPropertyKey;
 import org.apache.logging.log4j.core.impl.LogEventFactory;
 import org.apache.logging.log4j.plugins.PluginElement;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.util.PropertiesUtil;
 import org.apache.logging.log4j.util.StringBuilders;
 
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
index f2fe28baa5..09354f7902 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
@@ -36,7 +36,7 @@ import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
 import org.apache.logging.log4j.plugins.PluginElement;
 import org.apache.logging.log4j.plugins.PluginFactory;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
 import org.apache.logging.log4j.util.PropertiesUtil;
 import org.apache.logging.log4j.util.PropertyEnvironment;
 import org.apache.logging.log4j.util.Strings;
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
index e61083552d..360dd56e06 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
@@ -23,7 +23,7 @@ import java.nio.charset.CharsetEncoder;
 import java.nio.charset.CodingErrorAction;
 import java.util.Objects;
 import org.apache.logging.log4j.core.util.Constants;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 
 /**
  * {@link Encoder} for {@link StringBuilder}s.
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java
index e2a3c74213..a60d6e4c9c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java
@@ -17,7 +17,7 @@
 package org.apache.logging.log4j.core.util;
 
 import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
 import org.apache.logging.log4j.util.Lazy;
 
 /**
diff --git a/log4j-api-queue-jctools/pom.xml b/log4j-jctools/pom.xml
similarity index 76%
rename from log4j-api-queue-jctools/pom.xml
rename to log4j-jctools/pom.xml
index 18dd10d906..a66acc8017 100644
--- a/log4j-api-queue-jctools/pom.xml
+++ b/log4j-jctools/pom.xml
@@ -26,9 +26,9 @@
     <relativePath>../log4j-parent</relativePath>
   </parent>
 
-  <artifactId>log4j-api-queue-jctools</artifactId>
-  <name>Apache Log4j API queue provider (JCTools)</name>
-  <description>Provides JCTools-based queue implementations for the Apache 
Log4j API.</description>
+  <artifactId>log4j-jctools</artifactId>
+  <name>Apache Log4j JCTools-based supplements</name>
+  <description>Provides JCTools-based data structure implementations for the 
Apache Log4j.</description>
 
   <properties>
 
@@ -37,7 +37,6 @@
     <!--
       ~ OSGi and JPMS options
       -->
-    
<bnd-module-name>org.apache.logging.log4j.api.queue.jctools</bnd-module-name>
     <Fragment-Host>org.apache.logging.log4j.core</Fragment-Host>
 
   </properties>
@@ -46,7 +45,7 @@
 
     <dependency>
       <groupId>org.apache.logging.log4j</groupId>
-      <artifactId>log4j-api</artifactId>
+      <artifactId>log4j-core</artifactId>
     </dependency>
 
     <dependency>
@@ -54,6 +53,18 @@
       <artifactId>jctools-core</artifactId>
     </dependency>
 
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-engine</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+
   </dependencies>
 
 </project>
diff --git 
a/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsMpmcRecyclerFactoryProvider.java
 
b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsMpmcRecyclerFactoryProvider.java
new file mode 100644
index 0000000000..3a8fe11305
--- /dev/null
+++ 
b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsMpmcRecyclerFactoryProvider.java
@@ -0,0 +1,106 @@
+/*
+ * 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.jctools;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.logging.log4j.spi.recycler.Recycler.DEFAULT_CAPACITY;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import java.util.Queue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.spi.LoggingSystemProperty;
+import org.apache.logging.log4j.spi.recycler.AbstractRecycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+import org.jctools.queues.MpmcArrayQueue;
+
+/**
+ * A multi-producer-multi-consumer, thread-safe {@link Recycler} factory 
provider implementation based on <a 
href="https://jctools.github.io/JCTools/";>JCTools</a>.
+ */
+@ServiceProvider(RecyclerFactoryProvider.class)
+public final class JCToolsMpmcRecyclerFactoryProvider implements 
RecyclerFactoryProvider {
+
+    @Override
+    public int getOrder() {
+        return 600;
+    }
+
+    @Override
+    public String getName() {
+        return "jctools-mpmc";
+    }
+
+    @Override
+    public RecyclerFactory createForEnvironment(final PropertyEnvironment 
environment) {
+        requireNonNull(environment, "environment");
+        final int capacity = 
environment.getIntegerProperty(LoggingSystemProperty.RECYCLER_CAPACITY, 
DEFAULT_CAPACITY);
+        if (capacity < 1) {
+            throw new IllegalArgumentException("was expecting a `capacity` 
greater than 1, found: " + capacity);
+        }
+        return new JCToolsMpmcRecyclerFactory(capacity);
+    }
+
+    // Visible for testing
+    static final class JCToolsMpmcRecyclerFactory implements RecyclerFactory {
+
+        // Visible for testing
+        private final int capacity;
+
+        private JCToolsMpmcRecyclerFactory(final int capacity) {
+            this.capacity = capacity;
+        }
+
+        @Override
+        public <V> Recycler<V> create(Supplier<V> supplier, Consumer<V> 
cleaner) {
+            requireNonNull(supplier, "supplier");
+            requireNonNull(cleaner, "cleaner");
+            final MpmcArrayQueue<V> queue = new MpmcArrayQueue<>(capacity);
+            return new JCToolsMpmcRecycler<>(supplier, cleaner, queue);
+        }
+
+        // Visible for testing
+        static final class JCToolsMpmcRecycler<V> extends AbstractRecycler<V> {
+
+            private final Consumer<V> cleaner;
+
+            // Visible for testing
+            final Queue<V> queue;
+
+            private JCToolsMpmcRecycler(final Supplier<V> supplier, final 
Consumer<V> cleaner, final Queue<V> queue) {
+                super(supplier);
+                this.cleaner = cleaner;
+                this.queue = queue;
+            }
+
+            @Override
+            public V acquire() {
+                final V value = queue.poll();
+                return value != null ? value : createInstance();
+            }
+
+            @Override
+            public void release(final V value) {
+                requireNonNull(value, "value");
+                cleaner.accept(value);
+                queue.offer(value);
+            }
+        }
+    }
+}
diff --git 
a/log4j-api-queue-jctools/src/main/java/org/apache/logging/log4j/api/queue/JCToolsQueueFactory.java
 
b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsMpmcRecyclerFactoryProviderTest.java
similarity index 50%
rename from 
log4j-api-queue-jctools/src/main/java/org/apache/logging/log4j/api/queue/JCToolsQueueFactory.java
rename to 
log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsMpmcRecyclerFactoryProviderTest.java
index 57a39e31f8..b936c14309 100644
--- 
a/log4j-api-queue-jctools/src/main/java/org/apache/logging/log4j/api/queue/JCToolsQueueFactory.java
+++ 
b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsMpmcRecyclerFactoryProviderTest.java
@@ -14,24 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.api.queue;
+package org.apache.logging.log4j.jctools;
 
-import aQute.bnd.annotation.spi.ServiceProvider;
-import java.util.Queue;
-import org.apache.logging.log4j.spi.QueueFactory;
-import org.jctools.queues.MpmcArrayQueue;
+import static org.assertj.core.api.Assertions.assertThat;
 
-/**
- * A multi-producer-multi-consumer, thread-safe {@link QueueFactory} 
implementation based on <a 
href="https://jctools.github.io/JCTools/";>JCTools</a>.
- */
-@ServiceProvider(QueueFactory.class)
-public final class JCToolsQueueFactory implements QueueFactory {
+import java.util.Comparator;
+import java.util.List;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryRegistry;
+import org.junit.jupiter.api.Test;
+
+class JCToolsMpmcRecyclerFactoryProviderTest {
 
-    @Override
-    public <E> Queue<E> create(final int capacity) {
-        if (capacity < 1) {
-            throw new IllegalArgumentException("invalid capacity: " + 
capacity);
-        }
-        return new MpmcArrayQueue<>(capacity);
+    @Test
+    void verify_is_the_first() {
+        final List<Class<?>> providerClasses = 
RecyclerFactoryRegistry.getRecyclerFactoryProviders().stream()
+                
.sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder))
+                .<Class<?>>map(RecyclerFactoryProvider::getClass)
+                .toList();
+        
assertThat(providerClasses).startsWith(JCToolsMpmcRecyclerFactoryProvider.class);
     }
 }
diff --git 
a/log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider
 
b/log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider
new file mode 100644
index 0000000000..36741a8bb5
--- /dev/null
+++ 
b/log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider
@@ -0,0 +1,6 @@
+# `META-INF/services/*` files are automatically generated by 
`bnd-maven-plugin`.
+# We use `bnd:jar` goal, which only places them into the JAR.
+# As a result, tests of the same artifact (running against `target/` contents, 
not the JAR) don't have them.
+# To mitigate this, we manually create this file here only for tests.
+# `logging-parent` version `10.5.0` will switch from `bnd:jar` to 
`bnd:bnd-process`, then this hack won't be needed.
+org.apache.logging.log4j.jctools.JCToolsMpmcRecyclerFactoryProvider
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
index 30a668b311..f8d92fd4d5 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
@@ -34,7 +34,7 @@ import 
org.apache.logging.log4j.layout.template.json.util.JsonWriter;
 import org.apache.logging.log4j.layout.template.json.util.Uris;
 import org.apache.logging.log4j.plugins.*;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
 import org.apache.logging.log4j.util.Strings;
 
 @Configurable(elementType = Layout.ELEMENT_TYPE)
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
index eebc2e9f2b..77a80af09c 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
@@ -23,7 +23,7 @@ import java.util.concurrent.locks.LockSupport;
 import java.util.function.Consumer;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
 
 /**
  * Resolves a number from an internal counter.
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
index 27db91b743..0f7fc723cc 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
@@ -21,7 +21,7 @@ import 
org.apache.logging.log4j.layout.template.json.util.JsonWriter;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.ParameterConsumer;
 import org.apache.logging.log4j.message.ParameterVisitable;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
 
 /**
  * {@link Message} parameter (i.e., {@link Message#getParameters()}) resolver.
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
index f6da114660..64356bfe2e 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
@@ -22,8 +22,8 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.TriConsumer;
 
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
index 451c190e97..8d505eff8f 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
@@ -25,8 +25,8 @@ import java.util.stream.Collectors;
 import org.apache.logging.log4j.layout.template.json.util.CharSequencePointer;
 import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
 import 
org.apache.logging.log4j.layout.template.json.util.TruncatingBufferedPrintWriter;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 
 /**
  * Exception stack trace to JSON string resolver used by {@link 
ExceptionResolver}.
diff --git 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java
 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java
index 403aeaa09d..58ad4360fd 100644
--- 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java
+++ 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java
@@ -23,11 +23,9 @@ import java.util.Map;
 import java.util.UnknownFormatConversionException;
 import java.util.concurrent.ConcurrentHashMap;
 import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.internal.RecyclerFactories;
 import org.apache.logging.log4j.plugins.Inject;
 import org.apache.logging.log4j.plugins.Singleton;
 import org.apache.logging.log4j.plugins.util.TypeUtil;
-import org.apache.logging.log4j.spi.RecyclerFactory;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.Cast;
 import org.apache.logging.log4j.util.EnglishEnums;
@@ -60,7 +58,6 @@ public class TypeConverterFactory {
         registerTypeAlias(Integer.class, Integer.TYPE);
         registerTypeConverter(Long.class, Long::valueOf);
         registerTypeAlias(Long.class, Long.TYPE);
-        registerTypeConverter(RecyclerFactory.class, 
RecyclerFactories::ofSpec);
         registerTypeConverter(Short.class, Short::valueOf);
         registerTypeAlias(Short.class, Short.TYPE);
         registerTypeConverter(String.class, s -> s);
diff --git 
a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java 
b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java
index 64be7b447c..919cf2dee5 100644
--- a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java
+++ b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java
@@ -24,7 +24,7 @@ import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.spi.AbstractLogger;
 import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MarkerFactory;
 import org.slf4j.spi.LocationAwareLogger;
diff --git a/pom.xml b/pom.xml
index 8862927529..2344d4c60f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -234,7 +234,6 @@
          Note that modules here must have a corresponding entry in 
`dependencyManagement > dependencies` block below! -->
     <module>log4j-1.2-api</module>
     <module>log4j-api</module>
-    <module>log4j-api-queue-jctools</module>
     <module>log4j-api-test</module>
     <module>log4j-appserver</module>
     <module>log4j-core</module>
@@ -246,6 +245,7 @@
     <module>log4j-gctests</module>
     <module>log4j-iostreams</module>
     <module>log4j-jcl</module>
+    <module>log4j-jctools</module>
     <module>log4j-jdbc</module>
     <module>log4j-jdbc-dbcp2</module>
     <module>log4j-jndi</module>


Reply via email to