Skip to content

Commit

Permalink
Start work on integrating #450 to resolve #400
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Apr 9, 2018
1 parent 80b7c19 commit 9b53cf5
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,32 @@
*/
public class BufferRecyclers
{
/**
* System property that is checked to see if recycled buffers (see {@link BufferRecycler})
* should be tracked, for purpose of forcing release of all such buffers, typically
* during major classloading.
*
* @since 2.9.6
*/
public final static String SYSTEM_PROPERTY_TRACK_REUSABLE_BUFFERS
= "com.fasterxml.jackson.core.util.BufferRecyclers.trackReusableBuffers";

/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/

/**
* Flag that indicates whether {@link BufferRecycler} instances should be tracked.
*/
private final static ThreadLocalBufferManager _bufferRecyclerTracker;
static {
_bufferRecyclerTracker = "true".equals(System.getProperty("com.fasterxml.jackson.core.use_releasable_thread_local_buffers"))
? ThreadLocalBufferManager.instance()
: null;
}

/*
/**********************************************************
/* BufferRecyclers for parsers, generators
Expand All @@ -29,18 +55,46 @@ public class BufferRecyclers
final protected static ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef
= new ThreadLocal<SoftReference<BufferRecycler>>();

/**
* Main accessor to call for accessing possibly recycled {@link BufferRecycler} instance.
*/
public static BufferRecycler getBufferRecycler()
{
SoftReference<BufferRecycler> ref = _recyclerRef.get();
BufferRecycler br = (ref == null) ? null : ref.get();

if (br == null) {
br = new BufferRecycler();
_recyclerRef.set(new SoftReference<BufferRecycler>(br));
if (_bufferRecyclerTracker != null) {
ref = _bufferRecyclerTracker.wrapAndTrack(br);
} else {
ref = new SoftReference<BufferRecycler>(br);
}
_recyclerRef.set(ref);
}
return br;
}

/**
* Specialized method that will release all recycled {@link BufferRecycler} if
* (and only if) recycler tracking has been enabled
* (see {@link #SYSTEM_PROPERTY_TRACK_REUSABLE_BUFFERS}).
* This method is usually called on shutdown of the container like Application Server
* to ensure that no references are reachable via {@link ThreadLocal}s as this may cause
* unintentional retention of sizable amounts of memory. It may also be called regularly
* if GC for some reason does not clear up {@link SoftReference}s aggressively enough.
*
* @return Number of buffers released, if tracking enabled (zero or more); -1 if tracking not enabled.
*
* @since 2.9.6
*/
public static int releaseBuffers() {
if (_bufferRecyclerTracker != null) {
return _bufferRecyclerTracker.releaseBuffers();
}
return -1;
}

/*
/**********************************************************
/* JsonStringEncoder
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.fasterxml.jackson.core.util;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
* For issue [jackson-core#400] We keep a separate Set of all SoftReferences to BufferRecyclers
* which are (also) referenced using `ThreadLocals`.
* We do this to be able to release them (dereference) in `releaseBuffers()` and `shutdown()`
* method to reduce heap consumption during hot reloading of services where otherwise
* {@link ClassLoader} would have dangling reference via {@link ThreadLocal}s.
* 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.
*
* @since 2.9.6
*/
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.
*<p>
* NOTE: assumption is that {@link SoftReference} has its {@code equals()} and
* {@code hashCode()} implementations defined so that they use object identity, so
* we do not need to use something like {@link IdentityHashMap}
*/
private final Map<SoftReference<BufferRecycler>,Boolean> _trackedRecyclers
= 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>();

/*
/**********************************************************
/* Public API
/**********************************************************
*/

/**
* Returns the lazily initialized singleton instance
*/
public static ThreadLocalBufferManager instance() {
return ThreadLocalBufferManagerHolder.manager;
}

/**
* 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.
*/
public int releaseBuffers() {
synchronized (RELEASE_LOCK) {
int count = 0;
// does this need to be in sync block too? Looping over Map definitely has to but...
removeSoftRefsClearedByGc(); // make sure the refQueue is empty
for (SoftReference<BufferRecycler> ref : _trackedRecyclers.keySet()) {
ref.clear(); // possibly already cleared by gc, nothing happens in that case
++count;
}
_trackedRecyclers.clear(); //release cleared SoftRefs
return count;
}
}

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

/*
/**********************************************************
/* Internal methods
/**********************************************************
*/

/**
* 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;
while ((clearedSoftRef = (SoftReference<?>) _refQueue.poll()) != null) {
// uses reference-equality, quick, and O(1) removal by HashSet
_trackedRecyclers.remove(clearedSoftRef);
}
}

/**
* 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 {
static final ThreadLocalBufferManager manager = new ThreadLocalBufferManager();
}
}

0 comments on commit 9b53cf5

Please sign in to comment.