-
-
Notifications
You must be signed in to change notification settings - Fork 280
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
shutdown hook causes classloader leak when library is used inside a servlet container #376
Comments
Hi @vimil, I just pushed some partial fixes for this -- I say "partial fixes" because all Can you please test the git master version and let me know if it resolves your issue? |
@vimil Actually the more I think about it, the more I think that the change I checked in might completely fix the problem. Assuming that the changes actually work, the only references that ClassGraph (or its shutdown hook) will hold to objects that reference the context classloader should be weak references, so the context classloader should no longer be bound to the lifetime of the JVM. |
It's very difficult to drop all refs to context classloaders! But I think the latest checkin should do it. |
Let me test it out and get back to you |
The issue is not resolved because of the following reason. The anonymous thread class is loaded by the webapp's classloader and put into a shutdownhook map that is part of the system class loader when the addshutdownhook method is called. So the webappclassloader cannot be garbage collected because there is a strong reference from system classloader via the shutdownhook map to instance of anonymous thread class whose classloader is the webappclassloader. Unless the shutdownhook is removed the webappclassloader won't get garbage collected. I think library should just expose a teardown or release method and leave it the to clients using the library to either call the method using a shutdownhook or from a oncontextdestroyed servletcontext listener when running inside a servlet container. here is link to some approaches to solve this issue |
@vimil OK, but where exactly is the classloader reference being held? Previously I already overrode the context classloader reference in the new I have a hunch that maybe the reference to the context classloader was being held in the There is no reason why the shutdown hook needs to retain a reference to the context classloader, so I would much rather first fix this by dropping that reference. However once that is fixed, I will also provide an option for disabling the shutdown hook if you want to do that, since if you're in the business of loading and reloading apps dynamically, you probably don't want every load to permanently add a shutdown hook. However I would still like to run the same shutdown logic when a web application is unloaded. Is there another hook I can use in tomcat to run a function when an application is unloaded? |
@lukehutch all java objects including the hookthread instance in this case have a reference to their class object. (the getclass() method returns this reference). The class object has a reference back to the classloader that loaded the class. This is different from the threads contextclass loader and cannot be changed. The thread's contextclassloader is a mechanism used to load other classes from the the threads run method. It is not a way to change the classloader that was used load the anonymous thread class itself. The reference to the classloader that loaded an object cannot be changed after the object is created. Tomcat provides servletcontext listeners that can be implemented to execute logic during unemployment of a webapp. Have a look at this link |
@vimil The master version now has The latest checkin also defers the loading of the I will further move the setup of shutdown hooks to the first invocation of Can you please give some example code that shows how to detect if the current class is running inside a tomcat container? |
@lukehutch Thanks for looking into this. If we call getClass().getClassLoader() we will get the classloader the current class is running under. In the case of tomcat this will be https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/loader/WebappClassLoader.html However we would want code that is more generic and not specific to just tomcat. Let me look to see what others have done. Thanks |
OK, I assumed that calling this on a system class would return the classloader the system class was loaded under. It is also possible to get the parent classloader of the current classloader. Would that solve the problem? The Tomcat-specific code can be put here: I can add a method to |
From what I read it is not recomended to detect this. https://stackoverflow.com/questions/2976884/detect-if-running-in-servlet-container-or-standalone You dont have to use a system class. you can do ScanResult.class.getClassLoader(). That will return tomcats webappclassloader reference. However as I found out writing this detection logic is not foolproof and cannot be made generic |
OK, please take a look at the latest changes. I get the root classloader, and set up a thread that runs with that classloader rather than the context loader, and within that thread, |
Hmm, actually I'm not sure if those changes are enough, since |
@vimil Actually I now see that it is impossible to do what I was trying to do. Any shutdown hook code must be loaded by the container classloader, otherwise the class wouldn't be able to be found in the general case. So the shutdown hook will always hold a reference to the context classloader. That really sucks :-/
Actually ClassGraph already has to detect if the classloader is a |
@vimil Please check the latest commit, it registers a I would like to do this without creating a runtime dependency upon Tomcat. In I tried doing this, but then I noticed this comment: https://stackoverflow.com/a/32887891/3950982
|
OK, the latest version should have a fairly clean registration/unregistration mechanism for Tomcat (and I added the same mechanism for Spring): https://github.com/classgraph/classgraph/blob/master/pom.xml#L139 These should be discovered at runtime by the Tomcat and Spring annotation scanners. Please take a look. You should see in the log (at level INFO), on container startup:
And at shutdown:
|
The same change should be made for other classloader environments that may frequently load and unload classes. Unfortunately I know very little about any of these. Can anyone suggest other classloader environments that may need proper lifecycle support in this way?:
|
since all servlet containers implement servlet 3.0 spec you just need to have a provided dependency on servlet-api the weblistener annotation will work for all servlet containers like jboss, weblogic websphere |
Changed, thanks. Are there any environments on the above list that are not servlet environments, and may need their own lifecycle management? Also, please let me know if the master version with |
Sure, I will do that. osgi equinox felix are osgi containers and implement the bundle listener interface https://osgi.org/javadoc/r4v42/org/osgi/framework/BundleListener.html I think you could implement that interface for osgi containers |
Thanks. I see there is also Also, is there a possibility, for either servlets, Spring, or OSGi, that a container will be destroyed, but the previously-loaded classes will still be loaded in some parent classloader? The reason I ask is that I have made the assumption that the container lifecycle will be exactly the same as the classloader lifecycle -- so |
in the case of servlet containers each webapp loads its own copy of classes from the classgraph library. The classes loaded by one webapp are not visible to another webapp. The only situation when two webapps can share the same library is when the library is placed in the servlet container's classpath. In the case of tomcat this is done by placing the jar in tomcat/lib folder. However if the classgraph library is placed in tomcat/lib folder then the servletcontextlistener won't get registered because it is not part of any webapp's classpath. So ScanResult.closeall will only get called by the shutdownhook which is what is desired. |
The changes look good to me. I am closing this issue |
OK, thanks for verifying. I should make this work for Spring and OSGi too before pushing out a release, so I'll reopen for now. If you have any knowledge of those two environments, please comment on the listener registration mechanism (i.e. for Spring, whether the code already committed to master should work, and for OSGi, how to register a Please also comment if you know if the Thanks! |
Hmm. Turns out this is not as easy to do in OSGi. For each bundle, a I will also assume that the So I will close this again without OSGi support, but with servlet and (untested) Spring support, and hope it doesn't break Spring for anyone! |
Released in version 4.8.51. Thanks @vimil for reporting. |
@vimil Actually I took this one step further, and entirely removed the shutdown hook, because all resources that ClassGraph may create or keep open should be cleaned up on proper JVM shutdown, even without a shutdown hook, making the shutdown hook redundant:
The shutdown hook has been a source of several problems in the past, so the new method of registering a lifecycle listener and calling The only issue with this approach is that all runtime environments that do support containerized applications will need to add a lifecycle listener, otherwise Released in version 4.8.52. |
Thanks for getting this issue fixed |
Scanresult class adds a shutdown hook. This causes a classloader leak when tomcat unloads a webapplication that uses classgraph library.
Is there a way to prevent the shutdownhook from being added?
The text was updated successfully, but these errors were encountered: