One of the main causes for this error is a classloader not being garbage collected when you undeploy an application. A few days ago I encountered this problem but with a certain twist causing it to become even worse.
It all started with JBoss servers suffering from PermGen bloat which lead eventually to a "java.lang.OutOfMemoryError: PermGen space" exception.
Analyzing a heap dump from one of the servers using jhat we saw a long list of about 2000 $Proxy classes which where the immediate suspects for the situation
After some checking some of these proxies we could determine two things:
- They have no instances
- They are loaded by instances of JBoss RemotingClassLoader
Some more digging showed that there are 819 instances of the JBoss RemotingClassLoader all of them loading only $Proxy classes. This seemed to be a little odd so we used jmap with the "-permstat" option to look at the current status of one of the servers and what we got was a long list of JBoss RemotingClassLoaders - they where all dead !
OK, we have a list of dead classloaders loading proxy classes this is definitely the cause for the leak, but why?
Back to jhat we checked the reference chains to one of the proxies. The result showed a suspect that may be causing the leak: a static field named "classesToGetAndSetters" in Wicket's PropertyResolver class.
A quick pick into the Wicket source code discovered that classesToGetAndSetters is a map caching getters and setters. The problem is that Class objects are used as keys and that this is an unbounded cache with no eviction policy. Now, as long as you put "regular" (non Proxy) classes into this cache you will only encounter the usual classloader leak problem when unloading the application. This was already taken care by wicket, but we have a different problem.
In our case for each Proxy instance there is also a new $Proxy class filling up this cache. To add more complexity all these proxies where retrieved using JBoss remoting. A new RemotingClassLoader was created each time but never released since the cached $Proxy class is still referencing it.
The solution for such an issue is using a bound cache with some sort of eviction policy or something like google-collections ReferenceMap.