Why Static Means Dangerous Why your own custom java.util.logging logger can be a bad thing...
Simple Java platform without any weird buzzwords…
Recently I had a chance to work with pretty old legacy code. If you would like to feel exactly the same thing like me imagine complex web application working without dependency injection for services. Actually there was no services layer at all ;-). Instead of this – bunch of util static helper classes that connects to database or 3rd party services so we shouldn’t even speak any object instances.
No matter how often you like to test something, that was a big challenge to modify something in the core part with a few (unit) tests on the board. Awesome!
…but that wasn’t the end. In order to test or change anything the quickest way of doing that was to use hot redeployment (in this case for jetty).
Where are my beatiful logs ? My favourite Sherlock Holmes job!
Hot redeploy is so fast (I’m skipping here such great solutions as JRebel)
because you don’t need to restart JVM, restart web container (jetty in this
case).
The only one thing you need to do is to touch
(Unix command) specified jetty
context XML file with webapp context descriptor. Jetty just reloads only one
webapp without restarting anything else (even 1-2 minutes are saved per major
redeploy in comparison to full cold restart).
…but here comes the problem…
After first quick reload part of my logs just have disappeared. The most awkward thing was that framework logs were available as usually anytime I redeployed the application.
Finally I found the suspicious place – of course in the project codebase. Take a look at the following code (Please note it’s not the exact original class):
import java.util.logging.Logger;
import java.util.logging.LogManager;
public class ProjectLogger extends Logger {
private static ProjectLogger instance;
public static synchronized Logger getLogger(Class<?> class) {
if (instance != null) {
return instance;
} else {
String pkgName = ProjectLogger.class.getPackage().getName();
instance = new ProjectLogger(pkg.getName());
LogManager manager = LogManager.getLogManager();
// The below line is crucial here. It activates this logger instance
manager.addLogger(instance);
return instance;
}
}
private ProjectLogger(String pkgName) {
// There is only one constructor from parent class we can use
super(pkgName, null)
// Custom logging initialization goes here
}
public void info(String message, Object[] args) {
// Customized implementation
}
public void warning(String message, Object[] args) {
// Customized implementation
}
public void isInfoEnabled() {
// New method goes here
}
public void isWarningEnabled() {
// New method goes here
}
// Other useful methods originally not implemented for original Logger class
}
The crucial to understand for me was that:
-
LogManager class (see its source code) is initialized at the very begining of JVM lifecycle. Please note the
static {...}
initializer which assigns reference to a staticmanager
variable. -
The class if it’s loaded once by class loader (i.e. by
LogManager.getLogManager()
call) it will be always present – the static variable in itself will be always defined and by class contact this reference cannot be changed unless you are subclassing it. -
LogManager class holds tree of logger nodes as well as logger mappings.
-
ProjectLogger
class from the project I was used to work on is removed totally and created from scratch after each redeploy (I’ve checked that already by checkinginstance
variable value) because Jetty 7 defines separate class loader for each webapp context.
…and the most important one:
addLogger() adds only weak reference of class (to avoid GC/memory leaks) and returns boolean value as a status of operation which isn’t checked in the current class implementation.
Conclusions so far
If it’s hard for you to visualize all implications I will summarize what is happening there between jetty hot redeploys:
-
The 1st Jetty/JVM start and 1st initialization of application creates
ProjectLogger
classes and registers it’s reference in Java built-inLogManager
class. -
The 2nd and each next redeploy destroys
ProjectLogger
class completely (Dedicated classloader is destroyed). The newProjectLogger
class don’t know anything about the previous class instance (theinstance
static variable needs to be initialized butLogManager
still holds weak reference to the oldProjectLogger
class. -
All classes that uses new
ProjectLogger
logger will be logging messages through the newly created class instance which somehow doesn’t work.
What’s happening during next class initialization?
Recall getLogger()
method:
public static synchronized Logger getLogger(Class<?> class) {
if (instance != null) {
return instance;
} else {
// Note that logger package is always the same
String pkgName = ProjectLogger.class.getPackage().getName();
instance = new ProjectLogger(pkg.getName());
LogManager manager = LogManager.getLogManager();
// The below line is crucial here. It activates this logger instance
manager.addLogger(instance);
return instance;
}
}
I’ve found that manager.addLogger(instance)
returns false after each next
redeploy as the previous ProjectLogger
weak reference is defined. Wow! That
sounds tricky.
In this place I can only say: huh I wonder how many places in Java works in exactly the same way. Is it safe to reuse and reuse exactly the same JVM instance without making any restarts? It’s so real to have such similar conditions in different places or even memory leaks. Is it safe to use and store statically state ?
How to fix ?
I was looking many places where I could destroy the old instance but it’s not possible – there is no public method available for LogManager class which I could use to remove only that weak reference. The reset() method is too drastic as my initial logging configuration could be lost.
Finally I’ve figured out trying different approaches by trial and error what’s working correctly:
public static synchronized Logger getLogger(Class<?> class) {
if (instance != null) {
return instance;
} else {
String pkgName = ProjectLogger.class.getPackage().getName();
instance = new ProjectLogger(pkg.getName());
LogManager manager = LogManager.getLogManager();
// --- NEW PART ---
// We need to check the state of operation
boolean isReallyAdded = manager.addLogger(instance);
if (!isReallyAdded) {
// The operation failed so there is previous weak reference there
Logger existingLogger = manager.getLogger(pkgName);
// Make sure for some way it's not a null reference somehow.
if (existingLogger != null) {
newLogger.setParent(existingLogger);
}
}
// --- END OF NEW PART ---
return instance;
}
}
You would probably ask why we cannot use existingLogger
reference directly
to assign to our static instance
?
The answer is simple existingLogger
class is only a weak reference of type
Logger
to the original ProjectLogger
object and even casting it to the
ProjectLogger
class will cause ClassCastException. The only one valid
method here is to set child for the existingLogger
logger so our current
class will be activated in the LogManager
tree and log handlers will print
out finally your messages.
What I have learned after that lesson?
I couldn’t believe how such simple things like simple message logging job are so complicated in practice but the most important moral for me is:
-
Try to not use stateful static classes (use only static helper class without state)
-
Don’t assume your JVM will be leaving forever (the probability of memory leaks is rather higher than lower)
-
Don’t use hot redeployment on production in any way. Instead of this fully initialize new JVM and exclude temporarily this particular server from the application cluster.
-
Use normal non-singleton objects and avoid static variables to non primitive types. If you need to hold object references in that way think about this twice before you actually implement it.
-
Do not extend classes if you don’t know their ecosystem. In our case this was the
java.util.logging.Logger
class. Far more better solution is to use helper methods for helper classes.
blog comments powered by Disqus