Reputation: 4788
Disclaimer: I know how classes are loaded in JVM and how and when they are unloaded. The question is not about the current behaviour, the question is, why JVM does not support "forced" class/classloader unloading?
It could have the following semantics: when classloader is "forced unloaded", all classes it loaded are marked by "unloaded", meaning no new instances will be created (an exception will be thrown, like "ClassUnloadedException"). Then, all instances of such unloaded classes are marked as "unloaded" too, so every access to them will throw InstanceUnloadedException (just like NullPointerException).
Implementation: I think, this could be done during garbage collection. For example, compacting collector moves live objects anyway, so it can check if class of current object was "unloaded" and instead of moving object change the reference to guarded page of memory (accessing it will throw the abovementioned InstanceUnloadedException). This will effectively make object garbage, too. Or probably that could be done during "mark" phase of GC. Anyway, I think this is technically possible, with little overhead when no "unloading" occurs.
The rationale: Such "hardcore" mechanism could be useful for runtimes where a lot of dynamic code reloading occurs and failure of particular application or part of it is tolerable whereas failure of whole JVM is undesirable. For example, application servers and OSGi runtimes.
In my experience, dynamic redeployment in 9 cases of 10 leads to PermGenSpace due to the references not being cleaned up correctly (like ThreadLocal in static field filled in long-living thread, etc). Also, having an explicit exception instead of hard-to-debug leak could help polishing the code so no references are leaked into the long-living scope uncontrolled.
What do you think?
Upvotes: 3
Views: 1547
Reputation: 14613
This feature would just cause havoc and confusion. Forcing the unload of a class would bring a lot of problems, like deprecated Thread.stop() had, except that would be many more times worse.
Just for comparing, Thread.stop() tends to leave a lot of objects in inconsistent states due to the abrupt thread interrupting, and the thread could be executing any type of code. Coding against that in practice is impossible, or at least an tremendous extreme effort. It is considered between almost impossible and completely impossible to write a correct multithreded code in that scenario.
In your case, that sort of feature would have similar bad side-effects, but in much worse scale. The code could get the exception anywhere unexpectedly, so in practice would be very difficult or impossible to code defensively against it or handle it. Suppose that you have a try block doing some IO, and then a class is abruptely unloaded. The code will throw a ClassUnloadedException in an unexpected place, potentially leaving objects in inconsistent states. If you try to defend your code against it, the code responsible for that defense might fail as well due to another unexpected ClassUnloadedException. If you have a finally block that tries to close a resource and a ClassUnloadedException is thrown inside that block, the resource could not be closed. And again, handling it would be very hard, because the handler could get a ClassUnloadedException too.
By the way, NullPointerException is completely predictable. You got a pointer to something null and tried to derefence it. Normally it is a programming error, but the behaviour is completelly predictable. ClassCastException, IllegalStateException, IllegalArgumentException and other RuntimeExceptions are (or at least should be) happening in predictable conditions. Exceptions which are not RuntimeException may happen unexpectedly sometimes (like IOException), but the compiler forces you to handle or rethrow them.
On the other hand, StackOverflowError, OutOfMemoryError, ExceptionIninitializerError and NoClassDefFoundError, are unpredictable things that may happen anywhere in the code and very rarely there is something possible to do to handle or recover from them. When some program hits that, normally they just go erratic crazy. The few ones that try to handle them, limits to warning the user that it must be terminated immediatelly, maybe trying to save the unsaved data. Your ClassUnloadedException is a typical thing that would be a ClassUnloadedError instead. It would manifest itself like a ExceptionIninitializerError or NoClassDefFoundError which in 99% of the cases means just that your application is completely broken, except that it would be much worse because it has not a fail-fast behaviour, and so it gets still more randomness and unpredictableness to it.
Dynamic redeployment is by its very nature, one of the most ugly hacks that may happens in a JVM/Container since it changes abruptely the code of something that is already running on it, which tends to get you to a very erratic random buggy behavior. But it has its value, since it helps a lot in debugging. So, a defense against erratic behavior that the container implements, is to create a new set of classes to the running program and shares memory with the older one. If the new and the old parts of your program don't communicate directly (i.e., just by compatible serialization or by no communication at all), you are fine. You are normally safe too if no structural changes occurs and no living object depends of some specific implementation detail that changed. If you do follow these rules, no ClassUnloadedError will be show. However there are some situations where you may not follow these rules and still be safe, like fixing a bug in a method and changing its parameters and no live object exists which depends on the bug (i.e., they never existed or they are all dead).
But if you really wants a ClassUnloadedError being thrown if an object of the older part is accessed, as this behaviour flags that one of that isolament rules were broke, and then bring everything down. So, there is no point in have new and old parts of the program in the same time, it would be simpler to just redeploy it completely.
And about the implementation in the GC, it does not works. A dead object is dead, no matter if its class object is dead too or alive. The living objects of unloaded classes can't be garbage collected, because they are still reachable to other objects, no matter if the implementation of every method will magically change to something that always throws an Exception/Error. Backtracking the references to the object would be a very expensive operation in any implementation, and multithreading this would be still worse, possibly with a severe performance hit in perfectly living objects and classes.
Further, dynamic loading classes are not intended for production use, just for developer tests. So, it is no worth to buy all that trouble and complexity for this feature.
Concluding, in practice your idea creates something that combines something similar to Thread.stop() with something similar to NoClassdefFoundError, but is stronger than the sum of the two. Like a queen is a combination of a bishop and a rook in chess, but is stronger than the sum of the two. It is a really bad idea.
Upvotes: 3