diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x
index 9764a071b7..cc87e67e3d 100644
--- a/release-notes/VERSION-2.x
+++ b/release-notes/VERSION-2.x
@@ -9,6 +9,9 @@ Project: jackson-databind
#2541: Cannot merge polymorphic objects
(reported by Matthew A)
(fix contributed by James W)
+#3311: Add serializer-cache size limit to avoid Metaspace issues from
+ caching Serializers
+ (requested by mcolemanNOW@github)
#3338: `configOverride.setMergeable(false)` not supported by `ArrayNode`
(requested by Ernst-Jan vdL)
#3357: `@JsonIgnore` does not if together with `@JsonProperty` or `@JsonFormat`
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/SerializerCache.java b/src/main/java/com/fasterxml/jackson/databind/ser/SerializerCache.java
index 153a5334bd..54896f9225 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/SerializerCache.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/SerializerCache.java
@@ -1,10 +1,10 @@
package com.fasterxml.jackson.databind.ser;
-import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap;
+import com.fasterxml.jackson.databind.util.LRUMap;
import com.fasterxml.jackson.databind.util.TypeKey;
/**
@@ -24,22 +24,34 @@
*/
public final class SerializerCache
{
+ /**
+ * By default, allow caching of up to 4000 serializer entries (for possibly up to
+ * 1000 types; but depending access patterns may be as few as half of that).
+ */
+ public final static int DEFAULT_MAX_CACHED = 4000;
+
/**
* Shared, modifiable map; all access needs to be through synchronized blocks.
*
* NOTE: keys are of various types (see below for key types), in addition to
* basic {@link JavaType} used for "untyped" serializers.
*/
- private final HashMap> _sharedMap
- = new HashMap>(64);
+ private final LRUMap> _sharedMap;
/**
* Most recent read-only instance, created from _sharedMap, if any.
*/
- private final AtomicReference _readOnlyMap
- = new AtomicReference();
+ private final AtomicReference _readOnlyMap;
- public SerializerCache() { }
+ public SerializerCache() {
+ this(DEFAULT_MAX_CACHED);
+ }
+
+ public SerializerCache(int maxCached) {
+ int initial = Math.min(64, maxCached>>2);
+ _sharedMap = new LRUMap>(initial, maxCached);
+ _readOnlyMap = new AtomicReference();
+ }
/**
* Method that can be called to get a read-only instance populated from the
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/ReadOnlyClassToSerializerMap.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/ReadOnlyClassToSerializerMap.java
index feacbce21e..7b3531d109 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/ReadOnlyClassToSerializerMap.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/ReadOnlyClassToSerializerMap.java
@@ -1,9 +1,8 @@
package com.fasterxml.jackson.databind.ser.impl;
-import java.util.*;
-
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.util.LRUMap;
import com.fasterxml.jackson.databind.util.TypeKey;
/**
@@ -23,17 +22,15 @@ public final class ReadOnlyClassToSerializerMap
private final int _mask;
- public ReadOnlyClassToSerializerMap(Map> serializers)
+ public ReadOnlyClassToSerializerMap(LRUMap> src)
{
- int size = findSize(serializers.size());
- _size = size;
- _mask = (size-1);
- Bucket[] buckets = new Bucket[size];
- for (Map.Entry> entry : serializers.entrySet()) {
- TypeKey key = entry.getKey();
+ _size = findSize(src.size());
+ _mask = (_size-1);
+ Bucket[] buckets = new Bucket[_size];
+ src.contents((key, value) -> {
int index = key.hashCode() & _mask;
- buckets[index] = new Bucket(buckets[index], key, entry.getValue());
- }
+ buckets[index] = new Bucket(buckets[index], key, value);
+ });
_buckets = buckets;
}
@@ -51,7 +48,7 @@ private final static int findSize(int size)
/**
* Factory method for constructing an instance.
*/
- public static ReadOnlyClassToSerializerMap from(HashMap> src) {
+ public static ReadOnlyClassToSerializerMap from(LRUMap> src) {
return new ReadOnlyClassToSerializerMap(src);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/LRUMap.java b/src/main/java/com/fasterxml/jackson/databind/util/LRUMap.java
index 2ce8fe3a09..af9f7b79ec 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/LRUMap.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/LRUMap.java
@@ -1,5 +1,8 @@
package com.fasterxml.jackson.databind.util;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
import com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap;
/**
@@ -61,9 +64,21 @@ public V putIfAbsent(K key, V value) {
public int size() { return _map.size(); }
/*
- /**********************************************************
+ /**********************************************************************
+ /* Extended API (2.14)
+ /**********************************************************************
+ */
+
+ public void contents(BiConsumer consumer) {
+ for (Map.Entry entry : _map.entrySet()) {
+ consumer.accept(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /*
+ /**********************************************************************
/* Serializable overrides
- /**********************************************************
+ /**********************************************************************
*/
protected Object readResolve() {