关于PerfMark反射加载Logger避免类意外加载的疑问
关于PerfMark反射加载Logger避免类加载的疑问解答
代码修改背景
原代码与修改后的代码对比:
if (Boolean.getBoolean("io.perfmark.PerfMark.debug")) { - Logger.getLogger(PerfMark.class.getName()).log(Level.FINE, "Error during PerfMark.<clinit>", err); + // We need to be careful here, as it's easy to accidentally cause a class load. Logger is loaded + // reflectively to avoid accidentally pulling it in. + // TODO(carl-mastrangelo): Maybe make this load SLF4J instead? + Class<?> logClass = Class.forName("java.util.logging.Logger"); + Object logger = logClass.getMethod("getLogger", String.class).invoke(null, PerfMark.class.getName()); .. }
核心疑问
注释说明反射加载Logger是为了避免意外类加载,但测试发现无论反射还是直接调用,只有当if条件为真时才会触发Logger类的初始化(静态字段断点触发),if条件为假时都不会初始化Logger。想知道此处到底避免了哪个类的意外加载?
解答
这里要区分Java类加载的加载/解析阶段和初始化阶段:
- 直接调用
Logger.getLogger(...)时,编译阶段会将Logger和Level类标记为PerfMark类的依赖项。当JVM加载PerfMark类时,在链接阶段的解析步骤中,类加载器会尝试解析这些依赖类的符号引用,这会导致Logger、Level甚至其关联的日志类被提前加载(即使还没到初始化阶段)。 - 而反射调用时,
"java.util.logging.Logger"只是字符串常量,编译阶段不会产生对Logger类的依赖,JVM加载PerfMark类时,不会去解析Logger类的符号引用,也就不会提前加载Logger类。
你的测试断点是在静态字段(属于初始化阶段触发的逻辑),所以只有执行到代码时才会触发,但类的加载/解析可能在更早的PerfMark类加载阶段就发生了。PerfMark作为性能标记库,需要尽可能降低对应用启动性能的影响——哪怕只是加载而不初始化类,也会占用类加载器资源、增加启动时间。
这种反射方式的核心作用是:在默认关闭debug开关的场景下,完全避免Logger及相关日志类被提前加载/解析,只有当debug开关开启且代码执行到该分支时,才会触发Logger类的加载流程,最大程度减少不必要的启动开销。
内容的提问来源于stack exchange,提问作者Jane
相关产品推荐
相关产品推荐

