Skip to content
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

Pull request for solving issue #400 #450

Closed
wants to merge 13 commits into from
Closed
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
159 changes: 150 additions & 9 deletions src/main/java/com/fasterxml/jackson/core/JsonFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
package com.fasterxml.jackson.core;

import java.io.*;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import com.fasterxml.jackson.core.io.*;
import com.fasterxml.jackson.core.json.*;
Expand Down Expand Up @@ -176,13 +179,19 @@ public static int collectDefaults() {
/**********************************************************
*/

/*
* For issue #400: We optionally keep references to all SoftReferences to BufferRecyclers which we put in all ThreadLocals
* We do this to be able to release them (dereference) in a releaseBuffers and shutdown method to reduce heap consumption.
* It can be enabled by the system property com.fasterxml.jackson.core.use_releasable_thread_local_buffers set to 'true'.
*/
private static final ThreadLocalBufferManager _bufferMgr;

/**
* This <code>ThreadLocal</code> contains a {@link java.lang.ref.SoftReference}
* to a {@link BufferRecycler} used to provide a low-cost
* buffer recycling between reader and writer instances.
*/
final protected static ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef
= new ThreadLocal<SoftReference<BufferRecycler>>();
final protected static ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef = new ThreadLocal<SoftReference<BufferRecycler>>();

/**
* Each factory comes equipped with a shared root symbol table.
Expand Down Expand Up @@ -258,7 +267,16 @@ public static int collectDefaults() {
/* Construction
/**********************************************************
*/


static {
if ("true".equals(System.getProperty("com.fasterxml.jackson.core.use_releasable_thread_local_buffers"))) {
_bufferMgr = ThreadLocalBufferManager.instance();
}
else {
_bufferMgr = null;
}
}

/**
* Default constructor used to create factory instances.
* Creation of a factory instance is a light-weight operation,
Expand Down Expand Up @@ -320,6 +338,36 @@ protected void _checkInvalidCopy(Class<?> exp)
}
}

/*
/**********************************************************
/* Shutdown, releasing resources
/**********************************************************
*/

/**
* Releases the buffers retained in ThreadLocals.
* To be called to save on consumed heap usage for instance on shutdown event of applications which make use of
* an environment like an appserver which stays alive and uses a thread pool that causes ThreadLocals created by the
* application to survive much longer than the application itself.
* Note: System property com.fasterxml.jackson.core.use_releasable_thread_local_buffers needs to be set to 'true'
*/
public static void releaseBuffers() {
if (_bufferMgr != null) {
_bufferMgr.releaseBuffers();
}
}

/**
* Releases the buffers retained in ThreadLocals.
* To be called on shutdown event of applications which make use of
* an environment like an appserver which stays alive and uses a thread pool that causes ThreadLocals created by the
* application to survive much longer than the application itself.
* Note: System property com.fasterxml.jackson.core.use_releasable_thread_local_buffers needs to be set to 'true'
*/
public static void shutdown() {
releaseBuffers();
}

/*
/**********************************************************
/* Serializable overrides
Expand Down Expand Up @@ -483,15 +531,15 @@ public Version version() {
*/

/**
* Method for enabling or disabling specified parser feature
* (check {@link JsonParser.Feature} for list of features)
* Method for enabling or disabling specified factory feature
* (check {@link JsonFactory.Feature} for list of features)
*/
public final JsonFactory configure(JsonFactory.Feature f, boolean state) {
return state ? enable(f) : disable(f);
}

/**
* Method for enabling specified parser feature
* Method for enabling specified factory feature
* (check {@link JsonFactory.Feature} for list of features)
*/
public JsonFactory enable(JsonFactory.Feature f) {
Expand All @@ -500,7 +548,7 @@ public JsonFactory enable(JsonFactory.Feature f) {
}

/**
* Method for disabling specified parser features
* Method for disabling specified factory features
* (check {@link JsonFactory.Feature} for list of features)
*/
public JsonFactory disable(JsonFactory.Feature f) {
Expand All @@ -514,7 +562,8 @@ public JsonFactory disable(JsonFactory.Feature f) {
public final boolean isEnabled(JsonFactory.Feature f) {
return (_factoryFeatures & f.getMask()) != 0;
}



/*
/**********************************************************
/* Configuration, parser configuration
Expand Down Expand Up @@ -1222,13 +1271,23 @@ public BufferRecycler _getBufferRecycler()

if (br == null) {
br = new BufferRecycler();
_recyclerRef.set(new SoftReference<BufferRecycler>(br));
SoftReference newRef;
if (_bufferMgr != null) {
newRef = _bufferMgr.getReleasableSoftBufRecycler(br);
}
else {
newRef = new SoftReference<BufferRecycler>(br);
}
_recyclerRef.set(newRef);
}
return br;
}
return new BufferRecycler();
}




/**
* Overridable factory method that actually instantiates desired
* context object.
Expand Down Expand Up @@ -1295,4 +1354,86 @@ private final boolean _isJSONFactory() {
// or its sub-class / delegated to one, no need to check for equality, identity is enough
return getFormatName() == FORMAT_NAME_JSON;
}

/*
* For issue #400: We keep a separate Set of all SoftReferences to BufferRecyclers which we put in all ThreadLocals
* We do this to be able to release them (dereference) in a releaseBuffers and shutdown method to reduce heap consumption.
* When gc clears a SoftReference, it puts it on a newly introduced referenceQueue. We use this queue to release the inactive SoftReferences from the Set.
*/
private static final class ThreadLocalBufferManager {

/**
* A lock to make sure releaseBuffers is only executed by one thread at a time since it iterates over and modifies the allSoftBufRecyclers.
*/
private final Object RELEASE_LOCK = new Object();

/**
* A set of all SoftReferences to all BufferRecyclers to be able to release them on shutdown.
* 'All' means the ones created by this class, in this classloader. There may be more from other classloaders.
* We use a HashSet to have quick O(1) add and remove operations.
* The Set is backed by a ConcurrentHashMap to mitigate locking overhead.
*/
private final Set<SoftReference<BufferRecycler>> allSoftBufRecyclers = Collections.newSetFromMap(new ConcurrentHashMap<SoftReference<BufferRecycler>, Boolean>());

/**
* Queue where gc will put just-cleared SoftReferences, previously referencing BufferRecyclers.
* We use it to remove the cleared softRefs from the above set.
*/
private final ReferenceQueue<BufferRecycler> refQueue = new ReferenceQueue<BufferRecycler>();

/**
* Returns the lazily intialized singleton instance
* @return the singleton instance
*/
private static ThreadLocalBufferManager instance() {
return ThreadLocalBufferManagerHolder.manager;
}
/**
* Remove cleared (inactive) SoftRefs from our set. Gc may have cleared one or more, and made them inactive.
* We minimize contention by keeping synchronized sections short: the poll/remove methods
*/
private void removeSoftRefsClearedByGc() {
SoftReference clearedSoftRef = null;
do {
clearedSoftRef = (SoftReference) refQueue.poll();
if (clearedSoftRef != null) {
allSoftBufRecyclers.remove(clearedSoftRef); // uses reference-equality, quick, and O(1) removal by HashSet
}
} while(clearedSoftRef != null);
}

private SoftReference getReleasableSoftBufRecycler(BufferRecycler br) {
SoftReference newRef;
newRef = new SoftReference<BufferRecycler>(br, refQueue);
// also retain softRef to br in a set to be able to release it on shutdown
allSoftBufRecyclers.add(newRef);
// gc may have cleared one or more SoftRefs, clean them up to avoid a memleak
removeSoftRefsClearedByGc();
return newRef;
}

/**
* Releases the buffers retained in ThreadLocals. To be called for instance on shutdown event of applications which make use of
* an environment like an appserver which stays alive and uses a thread pool that causes ThreadLocals created by the
* application to survive much longer than the application itself.
* It will clear all bufRecyclers from the SoftRefs and release all SoftRefs itself from our set.
*/
private void releaseBuffers() {
synchronized(RELEASE_LOCK) {
removeSoftRefsClearedByGc(); // make sure the refQueue is empty
for (SoftReference ref : allSoftBufRecyclers) {
ref.clear(); // possibly already cleared by gc, nothing happens in that case
}
allSoftBufRecyclers.clear(); //release cleared SoftRefs
}
}
}

/**
* ThreadLocalBufferManagerHolder uses the thread-safe initialize-on-demand, holder class idiom that implicitly
* incorporates lazy initialization by declaring a static variable within a static Holder inner class
*/
private static final class ThreadLocalBufferManagerHolder {
private static final ThreadLocalBufferManager manager = new ThreadLocalBufferManager();
}
}