Skip to content

Fix #3311: essentially backporting #1994 from 3.0 to establish limits on serializer cache #3532

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand All @@ -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.
*<p>
* NOTE: keys are of various types (see below for key types), in addition to
* basic {@link JavaType} used for "untyped" serializers.
*/
private final HashMap<TypeKey, JsonSerializer<Object>> _sharedMap
= new HashMap<TypeKey, JsonSerializer<Object>>(64);
private final LRUMap<TypeKey, JsonSerializer<Object>> _sharedMap;

/**
* Most recent read-only instance, created from _sharedMap, if any.
*/
private final AtomicReference<ReadOnlyClassToSerializerMap> _readOnlyMap
= new AtomicReference<ReadOnlyClassToSerializerMap>();
private final AtomicReference<ReadOnlyClassToSerializerMap> _readOnlyMap;

public SerializerCache() { }
public SerializerCache() {
this(DEFAULT_MAX_CACHED);
}

public SerializerCache(int maxCached) {
int initial = Math.min(64, maxCached>>2);
_sharedMap = new LRUMap<TypeKey, JsonSerializer<Object>>(initial, maxCached);
_readOnlyMap = new AtomicReference<ReadOnlyClassToSerializerMap>();
}

/**
* Method that can be called to get a read-only instance populated from the
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand All @@ -23,17 +22,15 @@ public final class ReadOnlyClassToSerializerMap

private final int _mask;

public ReadOnlyClassToSerializerMap(Map<TypeKey,JsonSerializer<Object>> serializers)
public ReadOnlyClassToSerializerMap(LRUMap<TypeKey,JsonSerializer<Object>> src)
{
int size = findSize(serializers.size());
_size = size;
_mask = (size-1);
Bucket[] buckets = new Bucket[size];
for (Map.Entry<TypeKey,JsonSerializer<Object>> 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;
}

Expand All @@ -51,7 +48,7 @@ private final static int findSize(int size)
/**
* Factory method for constructing an instance.
*/
public static ReadOnlyClassToSerializerMap from(HashMap<TypeKey, JsonSerializer<Object>> src) {
public static ReadOnlyClassToSerializerMap from(LRUMap<TypeKey, JsonSerializer<Object>> src) {
return new ReadOnlyClassToSerializerMap(src);
}

Expand Down
19 changes: 17 additions & 2 deletions src/main/java/com/fasterxml/jackson/databind/util/LRUMap.java
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand Down Expand Up @@ -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<K,V> consumer) {
for (Map.Entry<K,V> entry : _map.entrySet()) {
consumer.accept(entry.getKey(), entry.getValue());
}
}

/*
/**********************************************************************
/* Serializable overrides
/**********************************************************
/**********************************************************************
*/

protected Object readResolve() {
Expand Down