我们有很多类代码,它们有一些类似以下的样板:
private static Logger logger = null;
private static Logger getLogger() {
if (logger == null) {
logger = Logger.getLogger(MyClass.class);
}
return logger;
}
这个想法是该类可以将调试内容记录到Logger中。需要记录某些内容的第一个代码调用getLogger()并使记录器存在。
我不喜欢这种模式。首先,单例getLogger()不同步并同步它,而正确会无缘无故地给每个后续调用带来负担。
我真的希望能够将其简化为:
private static final Logger logger = Logger.getLogger(MyClass.class);
然后我可以直接引用记录器,甚至不用单独的getter。
我担心的问题是,通过这样做,即使从未调用过记录器,我也会在加载类时创建一个Logger。我有10,000多个奇怪的类都调用了getLogger(),所以我实际上在这里创建了多少个Logger实例?如果我的log4j属性包含几个appender,我只是一遍又一遍地引用相同的记录器,或者我是在创建10,000个这样的东西?
答案 0 :(得分:2)
只有在实际初始化类时才会创建对象,即如果它是使用的。那时,每个类的单个对象的小开销真的很重要吗?
最简单的答案:尝试两种方式,看看你是否能观察到性能/记忆/等方面的任何重大差异。我怀疑你能否,但如果可以的话,你将能够决定那么基于数据最合适的行动方式。
答案 1 :(得分:2)
如果您使用默认的Log4j配置(即默认的LoggerRepository,DefaultCategoryFactory等),那么您将创建10'000 Logger实例。他们消耗了多少内存?除了上帝和你的探查者之外,没有人知道这一点。 (我的猜测只有后一个会告诉你)。
如果内存占用空间太大,请将Logger初始化移动到静态内部类,如下所示:
static class LoggerHolder {
static Logger logger = Logger.getLogger(MyClass.class);
}
private static Logger getLogger() {
return LoggerHolder.logger;
}
这样,Logger
的实例将仅在第一次getLogger
调用时创建。 (这种技术称为初始化按需保持器(IODH),它是线程安全的,并且没有同步开销。)
我可以给你一个offtopic建议吗?考虑将Log4J替换为SLF4J + Logback库的组合。它们由同一作者撰写,并描述为“a successor to the popular log4j project, picking up where log4j leaves off
”。您可以在this SO thread中阅读更多内容。
答案 2 :(得分:1)
您正在为每个加载的类创建一个单独的记录器实例,但我相信它们的内存占用量很小。众所周知,Log4J被广泛优化,我怀疑他们没有考虑内存使用情况。
总的来说,如果你有10K不同的类,你的应用程序是巨大的 - 我怀疑你会注意到内存消耗的任何显着差异。但最好的办法是在你的具体环境中以两种方式衡量它。
答案 3 :(得分:0)
静态变量初始化一次,同一个对象用于所有实例。对于单例模式,您实际上不需要private static Logger getLogger()
方法。
只是会让它懒得装,但不要认为这是一个很大的收获。在很短的时间内创造和销毁对象的玄武。
答案 4 :(得分:0)
你想要的方式(静态最终字段)是通常所做的,并由PMD推荐。如果你有一个记录器,很有可能它被使用,所以懒惰地初始化它不会在内存方面获得任何东西,当然也没有太多的性能。
我会把它LOGGER
大写,因为它是一个常数。
答案 5 :(得分:0)
OP的非同步getLogger()
方法没问题,ince Logger
是线程安全的。 (希望如此。由于Logger是可变的,并且设计为同时使用,如果没有额外的同步,它的引用无法安全发布,我会感到惊讶。)
偶尔为同一个类创建2个记录器也不是问题。
但是不必担心内存使用问题。为每个类创建一个额外的对象不应该是一个有关的开销(希望Logger对象不是太大)