-
-
Notifications
You must be signed in to change notification settings - Fork 798
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
Add mechanism for forcing BufferRecycler
released (to call on shutdown)
#400
Comments
From #67 posted by @cowtowncoder: Thank you for the links that might be helpful here. One thought on BufferRecycler usage: while bit ugly, it would probably be possible to replace its use with old-skool combination of recycler "class", for static utility methods, but passing Object[] that contains buffers to be recycled. These would need to be cast, but doing this would remove any Jackson provided classes -- buffers are just byte[]s and char[]s after all. |
Thanks for your reply. |
In your thought, if you don't put any objects of jackson classes on the ThreadLocal, this would solve the classloading leak. However, if you reference a BufferRecycler by an Object reference (or in array Object[]) the objects itself still reference BufferRecycler class and thus the classloader. Therefore, it doesn't solve the issue. |
@jborgers Use of But as to |
@cowtowncoder I agree that use of ThreadLocal is a nice feature for performance. Only I have an issue with SoftReferences. They prevent out-of-memory however, cause high heap usage and high gc overhead. As I understand it, having no BufferRecycler instances, so char[][] and byte[][] directly in Object[] in ThreadLocal will indeed solve the classloader leak: there is no reference from the system classloader to a class loaded by the classloader of the undeployed application, so that classloader and its classes can be gc-ed. However, since each application creates its own ThreadLocal which ends up in a Map in Thread, the buffers created for the undeployed applications are still there. This is most of the added heap usage by the undeployed application. In my proposed solution, WeakReferences are used instead of SoftReferences, so if no other (stronger) reference references the BufferRecycler, it will be gc-ed. We keep a reference from JsonFactory to all BufferRecycler's in order to be able to release them (dereference) in a shutdown method. I think this list of references should be SoftReferences, to keep the same behavior in case of memory pressure, when not using the shutdown. |
@cowtowncoder I cloned the source, added and changed a few lines of code in JsonFactory as prototype implementation of my proposed solution. Would that be helpful? Should I create a fork? |
Sure, fork is usually a good way, even if just reproducing a problem or showing approach. |
Right, we are going to test this forked version in a load test and do heap analysis to see if it shows the desired effect when doing re-deployments: no more classloader leak and releasing of the unneeded BufferRecyclers in ThreadLocals, resulting in lower, stable heap usage. |
@jborgers That would be great. I think there may be one or two other places ( I can go ahead and check other format modules as well; these are the only components with buffer recycling within Jackson components. |
My colleague had good results with our improved version of JsonFactory/jackson-core with a small webservice running on HotSpot: buffers were gc-ed quickly after shutdown. Friday they had a version ready for the whole application running in WebSphere, I expect it will be fully load tested beginning of next week. |
Excellent, looking forward to seeing more! |
@cowtowncoder We have good results from the load test on WebSphere/J9-JVM! The BufferRecyclers are released after the shutdown method is called on undeploy and the classloader is also released. |
One possibility would be a PR; I could either merge it directly, or cut'n paste depending on how things go (I'd want to get this in Regardless of mode of merging changes one thing I would need is CLA: https://github.com/FasterXML/jackson/blob/master/contributor-agreement.pdf (or Corporate CLA variant that's found next to it). This is usually printed, filled & signed, scanned and email to |
@jborgers Um. I must have misunderstood here -- this is nothing at all what I had in mind. What I would be interested is the idea I had for getting rid of the class, not new piece of machinery to keep track of |
@cowtowncoder Sorry for the delay. Based on a code review, I made some improvements to the code. We did retesting on both Hotspot and IBM J9/WebSphere (SoftReferences behave differently for them.) We had some unavailability of team members. New version is now fully tested, also duration tested. I analyzed heap dumps and the conclusion is that everything works as expected. BufferRecyclers get released after shutdown call. Classes can be unloaded. We see a nice reduction in memory usage now and we are happy with the result. I will proceed with signing the doc. Not sure what you mean by your remark of Oct 14. Maybe I can explain the workings if unclear. Like discussed above, getting rid of the class (BufferRecycler) does not solve our problem. There needs to be a way to release the buffers (being BufferRecyclers or byte[], char[]) (we run with 400 threads) hence a reference is needed from a central place. In our improved version, we got rid of the overhead of the WeakReference, a strong reference to the SoftReference is enough. We use a Set instead of a List to have faster access and a ReferenceQueue to prevent a small memory leak. I added doc in the source to explain things. |
Ok. So, my original assumption was that there was some way to either trigger freeing of all But adding the whole overhead of linked list for each and every alloc and/or release is something I am not ok with: I will not add that into core. With potentially hundreds of threads that overhead will be sizable and you'd probably be better off just disabling recycling, and dropping use of However. I did refactor handling of |
Hi @cowtowncoder, thanks for your reply. What is added is adding a reference to a Set. In my view the relative overhead both time and space wise, is neglectable compared to the buffers. Add/remove to a Set is O(1) and very quick and heap taken is very small, I verified in heap analysis with MAT. Also our performance tests show that there is no degradation in response times. Yet, there is a huge win in memory usage after undeploy. For making use of an Unsafe feature I think that is not a good idea, an unstable solution since Java 9+ will be hiding these features in a java module. Furthermore, performance wise, I think that the high memory use of the buffers with many threads (we have 400) and the use of SoftReferences both makes garbage collection take more time and have longer pauses, e.g. with compaction. In my opinion SoftReferences make poor caches, see for instance https://stackoverflow.com/questions/5757969/softreference-gets-garbage-collected-too-early and are largely over-used. I think our improvement will be very beneficial to any user with more than a handful of threads using jackson and who do un/redeployments either in production of development. Therefore, I ask you to reconsider your decision to not merge this solution. To meet your worries about overhead, it might be an approach to enable the releasing functionality on request, register allocated buffers only then. Either by configuration and/or a method call e.g. enableShutdown(), enableReleasabilityOfBuffers() or registerBuffers() and include this in the official jackson.core. This way any user can choose to benefit and it will be possible for us to use this in our whole organization. |
I like to add that a solution with subclassing will not work for us since we use several libraries which in turn use jackson-core. We cannot change the class (name) they use, yet, we can change the jar-file hence the version of the library and JsonFactory class as long as it is backward compatible. I also want to stress that this is a serious problem for us, several of our high-volume production systems suffer from high heap usage and as a consequence increased cpu usage because of this issue. Your help is much appreciated. |
Ok, I'll have a look at latest patch. |
I guess that is an improvement, but there is now a potentially bit Mention to potential use of Unsafe was just speculative -- I was guessing there might be a way to force cleanup through I think the best course forward for your usage may really be disabling
which will avoid memory retention. |
Hi @cowtowncoder, not sure what you mean by 'now a potentially bit Set ... that is modified for each usage'. And for what reason you think it is likely a synchronization bottleneck. I think it would really be helpful to have a more interactive way of communication. Would it be possible for you to join a conference call with us? What is a good time for you? Next Wednesday would be best for us since all of us working on this issue are available and on location then. |
At this point I don't think more communication helps: I am not planning to include code as suggested in Jackson 2.9. I understand that you consider it important: but this is not an urgent problem for me. As to synchronization: it will be needed for each construction of parser, across all threads. For a large multi-threaded systems it becomes an issues, something for which Beside this, I still do not understand why JVM would not simply free buffers held on to via soft references as expected. There is no good reason why this should happen. I could see why class ( Given this, I am interested in helping have customizable aspects, something that you can configure. |
Hi, yes indeed it is important to us. I just analyzed two production systems (many servers are copies of these in our server farm.) One server has BufferRecyclers consuming almost half of the live data in the heap: 43%. the other 16%. I think not many developers do heap analysis and understand what they see. In addition, who understands class loading leaks and how SoftReferences and garbage collection work? I think many have these problems yet are unaware of it. Considering synchronization: the set is only accessed when creating a BufferRecycler, which is only once per thread, right? And the critical section is very small, uses identity hash and is O(1) as documented in the code and I think time taken is almost neglectable compared to the allocation performed by BufferRecycler. Yet, maybe I miss something and we may get lock contention in some cases. We could prevent this contention by use of a Set which is optimized for multi-threaded access: backed by a ConcurrentHashMap. ConcurrentHashMap uses lock striping (and in my opinion a better approach in general than use of ThreadLocals). We can use something like (simplified): Set<<>> allSoftBufRecyclers = Collections.newSetFromMap(new ConcurrentHashMap<<>, >()); And we are done. I am assuming we can use java 1.6 here. How do you expect the JVM to free buffers held on to via soft references? IBM JVM for instance clears (some of) the SoftReferences which uniquely retain their target once every N full gc's, where N can be 15 (for example). It may however take a while before a full gc takes place, on one production server for instance only once a day, so it takes 15 days in this example for the BufferRecyclers to be de-referenced. There can be multiple re-deployments in these 2 weeks. But it can also happen quickly, with a full gc every minute, every 15 minutes the BufferRecyclers are cleared from the SoftReferences (since BufferRecyclers are only softly reachable) while you use them, regardless if there is memory pressure or not. And you don't want them to be cleared. A reason why SoftReferences make poor caches: there is no control over the time-to-live. The classloader leak occurs because an object from a class of the system classloader (ThreadLocal) references an object of a class of the application classloader (BufferRecycler.) Something configurable could very well work for us. Like I suggested in my previous post. How would you like to have this configured? Like other Feature-s? |
Hi @cowtowncoder, I am eager to know your answer. |
I created a new version in my repo where I changed new releasability of buffer recyclers to be optional, enable by a feature and static method. I factored out needed machinery into a separate class, only instantiated if enabled. I also used the multi-threaded optimized Set. |
Ok. So, use of ```Soft reference objects, which are cleared at the discretion of the garbage collector in response to memory demand. Soft references are most often used to implement memory-sensitive caches. `` I am not surprised about longer time-to-live for SoftReferences per se (that is, in many ways, the idea). From earlier descriptions it sounded like there was something special, however, about your use case of hot-reloading classes, something that I have never used (nor seen used in production anywhere, although remember that application servers did allow that). Much more commonly service node would be shutdown, restarted; this simplifies life-cycle of system significantly. But I'll add bit more thoughts on separate comment, so hopefully we can find common ground, and I can understand your limitations wrt possible solution. |
So, from my perspective, I would be most interested in finding a way to allow extension point that allows your approach to be used in your case, regardless of what the default behavior would be. I would like to keep existing behavior as unchanged as possible, partly since release cycle is such that 2.x will be in maintenance mode and I'd like to limit amount internal change in this are. This because possible new problems can be equally difficult to track down. As you say, debugging memory management, retention issues is very tricky, especially in heavily multi-threaded systems. Extension points, then, could come in at least 2 flavors;
It sounds like (1) would not work for you, either. But perhaps (2) or (3) would work. Another idea that may or may not make sense: allowing use of |
Thanks for your reply and good to read you want to find common ground and a solution. I understand you want to keep the existing behavior as unchanged as possible for the reasons you mention. Happy to look along the lines of extension options (2) and (3). WeakReferences will be cleared too rapidly, on the following gc event. I created a new version along the lines of (2) and (3). It makes our added machinery optional. It separates out all added state and behavior into a new private inner class (ThreadLocalBufferManager) which will only be instantiated and used in case JsonFactory is configured to useReleasableThreadLocalBuffers. New behavior is only applied in case the feature is enabled (default: disabled.) It can be enabled with a new Feature or with a static method enableUseReleasableThreadLocalBuffers, which will instantiate the ThreadLocalBufferManager (bufferMgr), referenced by a static field. New registration will only happen in case the bufferMgr exists (feature is enabled) by the bufferMgr. The shutdown method will delegate to the bufferMgr only in case it exists (feature is enabled). Could you have a look at this version? Eager to know your thoughts. |
@cowtowncoder Please have a look at the new version and let me know your thoughts, so we can go forward. |
@cowtowncoder Waiting for your reply. We are eager to go forward. |
Ok. On "static configuration", I just mean things like env variable and system properties: more accurate would be "global settings", which affect all instances everywhere. With jackson being transitive dep on many things, different usage often requires different configs. Almost as bad as system-prop/env would be static singletons, although at least those would be shaded. On handlers: it would just mean making adding handler type (interface, abstract class), pluggable to factory/-ies, and getting called for allocation. One thing I forgot to ask was simple limitations from your side, wrt. sub-classing or ability to configure factory instances. I can understand that you can not force use of, say, sub-class of From my side I do not want substantial changes to default behavior, esp. for 2.x. |
ThreadLocals should be avoided in library code. I got here by doing research about possibly choosing Jackson for a project that will deserialise terabytes of JSON, streaming in an Akka-based system, where you don't know, own, or control the thread that you will be running on. In fact, your execution is freely intermingled in Akka's thread pools to maintain high, non-blocking CPU throughput. At least I can turn off the feature altogether. As another note, instead of using synchronisation of access for a common set of references, it would probably be better to use an AtomicReference to hold the set, and access it through that so that you maintain non-blocking throughout. |
@kirked The pull request implements a releasability of the thread-local buffers. You can release all buffers when needed, e.g. on shutdown of the application. Not sure this will help you. For 3.0 plan is to have a pluggable buffering approach and multiple implementations. Your use case is useful input for that. |
@kirked I am not going through argue the point on use of Anyway... as @jborgers said, intent is to allow alternative strategies, where users can choose different trade-offs. There are certainly benefits from different, centralized approach, where actual memory usage can be strictly limited for example. |
BufferRecycler
s are not released on shutdown of the application
Getting closer: merged #450 in |
BufferRecycler
s are not released on shutdown of the applicationBufferRecycler
released (to call on shutdown)
So: implementation checks for System Property
which is defined as String constant
and specific value of Access to clean up functionality is via class
which may be called at any point, and returns number of references cleared, or -1 to indicate tracking is not enabled. Actual count only gives upper bound of possibly de-referenced buffers. At this point functionality in its current form is just for 2.9.6 and later 2.x releases: it may or may not make it in 3.x as-is; if not, we will figure out something else to solve specific problem this version was designed to fix. |
@jborgers |
Nice! :-) |
We see a class loader memory leak by using Jackson: on redeployment of our application in WebSphere we see an increase of heap usage of a couple of hundred MB's en after several redeployments heap usage becomes close to the heap size and garbage collection takes a lot of CPU. Note that most extra heap is taken by the BufferRecyclers retaining char[][]'s.
SoftReferences may help to prevent out of memory errors, it doesn't help for gc overhead (including long compaction pauses.)
In addition, the BufferRecycler classes of previous deployed versions of the app are still in the ThreadLocals of all threads of the threadpool and prevent the classloader with all its classes to be unloaded.
See here: https://stackoverflow.com/questions/17968803/threadlocal-memory-leak for more on classloader leaks.
We would like Jackson to release/remove the BufferRecyclers from the ThreadLocals on shutdown, by calling a shutdown method on e.g. JsonFactory.
See also: http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/
The text was updated successfully, but these errors were encountered: