EDIT:
I found classloader leak in my webapplication.
It boils down to 3rd party library initializing CORBA via JNDI's COS naming service and not exposing a call to cleanly shutdown JNDI's context. This leaves some CORBA related threads and other resources referencing my webapp classloader and preventing it from being garbage collected. This results in OutOfMemory Error: PermGen after few redeploys/reloads.
For now I increased the PermGen memory in JVM and it makes the intervals between server crashes longer. This obviously is not a fix but a workaround (and a poor one for that matter).
I guess my question is is there any way I can cleanly shutdown JNDI context without holding reference to it. My instincts tell me no, but maybe I don't know about some magic feature of JNDI that would allow me to get hold of that context.
So the way the 3rd party library initializes CORBA objects is something along this lines (exception handling and other details omited for brevity):
private CorbaObjectAggregate initCorba() {
InitialContext ctx = null;
CorbaObjectAggregate corbaObjects = new CorbaObjectAggregate();
ORB orb = null;
Properties env = getContextEnvironment();
String[] args = null;
orb = ORB.init(args, null);
env.put("java.naming.corba.orb", orb);
ctx = new InitialContext(env);
//a bunch of object lookups follow
corbaObjects.someCorbaObjectReference = (SomeCorbaObjectClass) ctx.lookup("somePaht");
return corbaObjects;
}
So the reference to ctx is gone after that method finishes executing...
I tried stopping the threads manually but it didn't fix the leak. I guess there are some other corba resources holding onto classloader. I suppose I could try to hunt them down in some cleanup method and free the classloader this way, but I was hoping for some cleaner solution.
Just for clarity, the 3rd party library is closed source and I can't really change it. It's also not viable option to get support form the company behind it.
That is going to be tricky to fix. There are likely to be two issues:
- classes loaded from the JAR holding a reference to the web-app class loader
- threads started by that process having the web-app class loader as their context class-loader.
Something along the following lines should help:
Move the JAR to $CATALINA_BASE/lib. That will mean that the classes are loaded by the common class loader. The down side is that they are also visible to and shared by every web application.
Find out where in your application the initialisation is triggered. Before that code executes, set the thread context class loader to the system class loader (or the parent of the current (web-app) class loader) and reset the thread context class loader after the init call. That should mean any threads created do not have the web-app class loader as the context class loader.
If threads get created at other points in time, fixing this could get very tricky, very quickly.
For some background that may help understand what is going on, see:
http://people.apache.org/~markt/presentations/2010-11-04-Memory-Leaks-60mins.pdf
To see the sort of thing Tomcat does internally to work-around these issues see:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/JreMemoryLeakPreventionListener.java?view=annotate
Related
In section 21.2.2 Programming Restrictions of JSR-318 there is text as follows
The enterprise bean must not attempt to create a class loader; obtain the current class loader; set the context class loader; set security manager; create a new security manager; stop the JVM; or change the input, output, and error streams.
But in JSR-345 Section 16.2.2 Programming Restrictions, that same text is now:
The enterprise bean must not attempt to create a class loader; set the context class loader; set
security manager; create a new security manager; stop the JVM; or change the input, output,
and error streams
I was attempting to explain this to someone, and wanted to pull up the JSR, for reference, via a web search and was puzzled when what I was reading wasn't as I remembered.
My question is, does anyone have any insight into why this language changed? Why this now implies that it's OK to obtain the classloader (so long as you don't violate other Reflection API changes that still exist in section 16.2.2 of JSR-345)?
I have three modules: module-a, module-b, module-c. Module-a and module-b are in boot layer. Layer for module-c I create myself. Module-c has JPMS implementation of the service which interface is in module-a.
This is the way I create layer with module-c in module-b.
ModuleFinder finder = ModuleFinder.of(moduleCPath);
ModuleLayer parent = ModuleLayer.boot();
Configuration cf = parent.configuration().resolve(finder, ModuleFinder.of(), Set.of("module-c"));
ClassLoader scl = ClassLoader.getSystemClassLoader();
ModuleLayer layer = parent.defineModulesWithOneLoader(cf, scl);
Then in module-b I call service from module-c. After service execution completed I don't need module-c and new created layer any more. How to remove it from JVM and release all resources? Is it enough to do layer = null;?
The module layer, the modules in the layer, and class loaders supporting the layer, are eligible to be GC'ed/unloaded when they are no longer reachable.
If you want to prove this to yourself then create a weak reference to the layer object and you should see that the reference is cleared (and queued if you are using a reference queue) when the layer is GC'ed.
An EMPTY_LAYER shall solve your use-case(from one of the comments on the question, trying to assign new HashSet<> as roots) here, wherein the references to other layers no more handled within the layer that you created :
layer = ModuleLayer.empty();
Returns the empty layer. There are no modules in the empty layer. It
has no parents.
On the thought of being able to remove a layer explicitly form the JVM, I would not probably expect such an API exposed publicly since a JVM is supposed to have at least one non-empty layer, the boot layer, that is created when the Java virtual machine is started.
And if such a method is exposed, I wonder if users can try and remove this layer as well. Though I am trying to be technically hypothetical on this part.
1 InitialContext initialContext = new InitialContext();
2 EJBHome ejbHome = (EJBHome) initialContext.lookup(jndiLocation);
3 Class ejbHomeClass = ejbHome.getClass();
4 Method createMethod = ejbHomeClass.getMethod("create", new Class[] { });
The exception is thrown from line 2 when the code executes the initial context lookup and stores it in an EJBHome object.
I have seen this issue several times and tried several solutions such as including the j2ee.jar in the classpath and manifest. However, their solutions might not apply to this issue since it occurs on a thread.
You should not use user-defined threads with EJB.
The EJB container is responsible for managing system-related functionality such as security, threading, resource pooling, and so on. In order to control these facets of component operation, the container places certain restrictions on the components it manages.
See http://www.oracle.com/technetwork/java/restrictions-142267.html
I have read from the question How to gain access to a ServletContext instance from any method? that if I want to access the Servlet Context from any class in my Java web project I can declare a static field that points to the ServletContext from ServletContextListener, but a static field is a bad practice in Java web applications because the GC can't collect it until the JVM is turned off (correct me if I'm wrong in this point). Is there another way to access the ServletContext without using a listener or receiving it as a parameter? There is another workaround for solve this problem? I'm using JSF 1.2 and JBoss 5.1 GA for the Web Application.
Note: I know I can use
(ServletContext)FacesContext.getCurrentInstance().getExternalContext().getContext();
to access the ServletContext, but there is a method that runs at startup that needs to access the ServletContext and the FacesContext.getCurrentInstance() has not been initialized.
UPDATE:
We need to load some IP's from the web.xml into String constants when the web application starts up. To do this, we have created a Singleton class that loads the context-params in variables and then fill the String constants with some values of the Singleton class. This Singleton class manages lot of data and is giving out of memory exception errors. To fix this problem, we're modifying the Singleton class to be a simple class that is loaded as a ServerContext attribute, but then the String constants can't be loaded for the absence of the instance of this (not anymore) Singleton.
I'm not sure why you need a singleton. Just create one bean which you store in the application scope.
#Override
public void contextInitialized(ServletContextEvent event) {
ServletContext context = event.getServletContext();
Set<String> ips = parseContextParamSomehow(context.getInitParam("ips"));
Manager manager = new Manager();
manager.setIps(ips);
context.setAttribute("manager", manager);
}
It'll be available by #{manager} in EL context. Also as managed property of an arbitraty JSF managed bean. An alternative is to create an application scoped JSF managed bean and do the job in its constructor, but you're then postponing its construction to the first HTTP request which involves the bean.
I am attempting to use JNDI with a custom DataSource called CEDataSource. From my understanding for this to work I would have to create a custom factory as well.
So I created a custom factory that would return the CEDataSource object but now when I attempt to use this in Java with
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
// Look up our data source
CEDataSource ds = (CEDataSource)envCtx.lookup("jdbc/cePu");
I get the exception ClassCastException
"CEDataSource cannot be mapped to CEDataSource". I added the CEDataSource and the CEDataSourceFactory to the TOMCAT/lib folder as well as referenced this same jar on my deployed application.
Any help would be greatly appreciated on why this possible error may occur. Thanks
"CEDataSource cannot be mapped to CEDataSource" seems to point to the fact that it's not the same "CEDataSource" in both places.
What could be different is the classloader and this usually happens if you have the same jars/.class(es) in multiple locations.
Do you have multiple copies of your jar?
Try to have a single copy, maybe in the shared tomcat lib so it's loaded by the same classloader no matter from where you access it from.
It is actually not too difficult to start Tomcat under an Eclipse debug session (just put all the Bootstrap.jar in a project and add the System properties in the JVM parameters). II've done that many times, if only to dissect the bowels of that feline. Once this is done you can break on the class cast exception of the JNDI connection factory and you will then be able to see if your factory is called or not.
From what I remember Tomcat uses the DBCP DataSource. Actually repackaged under com.apache.tomcat.dbcp.dbcp.DataSource (IIRC).
So I would not be surprised if this is what you end up with as a result of your look-up.
With hindsight, I now realize I also forgot to mention that if any underlying class (for instance a JDBC driver) needed to create the instance of your CEDataSource is missing you also get this ClassCastException. Fair enough, but you always focus on the class itself and not on the other jars...
CEDataSource ds = (CEDataSource)envCtx.lookup("jdbc/cePu");
The lookup you are doing on jdbc/cePu is not of class type CEDataSource , it belongs to some other class type, that is why you are getting class cast exception. if you could show me the configuration for jdbc/cePu that would be helpful.