|
| 1 | +package com.fasterxml.jackson.core.util; |
| 2 | + |
| 3 | +import java.lang.ref.ReferenceQueue; |
| 4 | +import java.lang.ref.SoftReference; |
| 5 | + |
| 6 | +import java.util.*; |
| 7 | +import java.util.concurrent.ConcurrentHashMap; |
| 8 | + |
| 9 | +/** |
| 10 | + * For issue [jackson-core#400] We keep a separate Set of all SoftReferences to BufferRecyclers |
| 11 | + * which are (also) referenced using `ThreadLocals`. |
| 12 | + * We do this to be able to release them (dereference) in `releaseBuffers()` and `shutdown()` |
| 13 | + * method to reduce heap consumption during hot reloading of services where otherwise |
| 14 | + * {@link ClassLoader} would have dangling reference via {@link ThreadLocal}s. |
| 15 | + * When gc clears a SoftReference, it puts it on a newly introduced referenceQueue. |
| 16 | + * We use this queue to release the inactive SoftReferences from the Set. |
| 17 | + * |
| 18 | + * @since 2.9.6 |
| 19 | + */ |
| 20 | +class ThreadLocalBufferManager |
| 21 | +{ |
| 22 | + /** |
| 23 | + * A lock to make sure releaseBuffers is only executed by one thread at a time |
| 24 | + * since it iterates over and modifies the allSoftBufRecyclers. |
| 25 | + */ |
| 26 | + private final Object RELEASE_LOCK = new Object(); |
| 27 | + |
| 28 | + /** |
| 29 | + * A set of all SoftReferences to all BufferRecyclers to be able to release them on shutdown. |
| 30 | + * 'All' means the ones created by this class, in this classloader. |
| 31 | + * There may be more from other classloaders. |
| 32 | + * We use a HashSet to have quick O(1) add and remove operations. |
| 33 | + *<p> |
| 34 | + * NOTE: assumption is that {@link SoftReference} has its {@code equals()} and |
| 35 | + * {@code hashCode()} implementations defined so that they use object identity, so |
| 36 | + * we do not need to use something like {@link IdentityHashMap} |
| 37 | + */ |
| 38 | + private final Map<SoftReference<BufferRecycler>,Boolean> _trackedRecyclers |
| 39 | + = new ConcurrentHashMap<SoftReference<BufferRecycler>, Boolean>(); |
| 40 | + |
| 41 | + /** |
| 42 | + * Queue where gc will put just-cleared SoftReferences, previously referencing BufferRecyclers. |
| 43 | + * We use it to remove the cleared softRefs from the above set. |
| 44 | + */ |
| 45 | + private final ReferenceQueue<BufferRecycler> _refQueue = new ReferenceQueue<BufferRecycler>(); |
| 46 | + |
| 47 | + /* |
| 48 | + /********************************************************** |
| 49 | + /* Public API |
| 50 | + /********************************************************** |
| 51 | + */ |
| 52 | + |
| 53 | + /** |
| 54 | + * Returns the lazily initialized singleton instance |
| 55 | + */ |
| 56 | + public static ThreadLocalBufferManager instance() { |
| 57 | + return ThreadLocalBufferManagerHolder.manager; |
| 58 | + } |
| 59 | + |
| 60 | + /** |
| 61 | + * Releases the buffers retained in ThreadLocals. To be called for instance on shutdown event of applications which make use of |
| 62 | + * an environment like an appserver which stays alive and uses a thread pool that causes ThreadLocals created by the |
| 63 | + * application to survive much longer than the application itself. |
| 64 | + * It will clear all bufRecyclers from the SoftRefs and release all SoftRefs itself from our set. |
| 65 | + */ |
| 66 | + public int releaseBuffers() { |
| 67 | + synchronized (RELEASE_LOCK) { |
| 68 | + int count = 0; |
| 69 | + // does this need to be in sync block too? Looping over Map definitely has to but... |
| 70 | + removeSoftRefsClearedByGc(); // make sure the refQueue is empty |
| 71 | + for (SoftReference<BufferRecycler> ref : _trackedRecyclers.keySet()) { |
| 72 | + ref.clear(); // possibly already cleared by gc, nothing happens in that case |
| 73 | + ++count; |
| 74 | + } |
| 75 | + _trackedRecyclers.clear(); //release cleared SoftRefs |
| 76 | + return count; |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + public SoftReference<BufferRecycler> wrapAndTrack(BufferRecycler br) { |
| 81 | + SoftReference<BufferRecycler> newRef; |
| 82 | + newRef = new SoftReference<BufferRecycler>(br, _refQueue); |
| 83 | + // also retain softRef to br in a set to be able to release it on shutdown |
| 84 | + _trackedRecyclers.put(newRef, true); |
| 85 | + // gc may have cleared one or more SoftRefs, clean them up to avoid a memleak |
| 86 | + removeSoftRefsClearedByGc(); |
| 87 | + return newRef; |
| 88 | + } |
| 89 | + |
| 90 | + /* |
| 91 | + /********************************************************** |
| 92 | + /* Internal methods |
| 93 | + /********************************************************** |
| 94 | + */ |
| 95 | + |
| 96 | + /** |
| 97 | + * Remove cleared (inactive) SoftRefs from our set. Gc may have cleared one or more, |
| 98 | + * and made them inactive. We minimize contention by keeping synchronized sections short: |
| 99 | + * the poll/remove methods |
| 100 | + */ |
| 101 | + private void removeSoftRefsClearedByGc() { |
| 102 | + SoftReference<?> clearedSoftRef; |
| 103 | + while ((clearedSoftRef = (SoftReference<?>) _refQueue.poll()) != null) { |
| 104 | + // uses reference-equality, quick, and O(1) removal by HashSet |
| 105 | + _trackedRecyclers.remove(clearedSoftRef); |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + /** |
| 110 | + * ThreadLocalBufferManagerHolder uses the thread-safe initialize-on-demand, holder class idiom that implicitly |
| 111 | + * incorporates lazy initialization by declaring a static variable within a static Holder inner class |
| 112 | + */ |
| 113 | + private static final class ThreadLocalBufferManagerHolder { |
| 114 | + static final ThreadLocalBufferManager manager = new ThreadLocalBufferManager(); |
| 115 | + } |
| 116 | +} |
0 commit comments