已做非空校验仍出现Java NullPointerException,求排查思路
针对你遇到的这种看似矛盾的NPE(已通过Objects.nonNull检查但仍触发空指针),结合单例类、JBoss容器的场景,可从以下方向排查:
多线程并发的竞态条件:
虽然代码里做了非空检查,但在第114行判断和第115行调用方法的间隙,可能有其他线程将logContext置为null。单例类本身不保证线程安全,如果logContext存在被多线程修改的逻辑(比如重置、销毁操作),就会出现这种“检查-执行”窗口内的并发问题。字段可见性问题:
如果logContext字段未被volatile修饰,在多线程环境下,某个线程修改logContext为null的操作,可能无法及时被其他线程感知。导致线程执行第114行时,读取的是本地缓存的非空值,但执行第115行时,实际访问的是内存中的null值,触发NPE。类加载器导致的多实例问题:
JBoss作为Java EE容器,存在多个类加载器(比如Web应用类加载器、容器类加载器)。如果单例类被不同类加载器加载多次,会生成多个独立的单例实例。其中某个实例的logContext可能被置为null,而该实例的addTraceIdToThreadContext方法被调用时,就会出现检查后仍空指针的情况。反射或字节码增强工具的修改:
某些框架、监控工具或JBoss自身的字节码增强机制,可能通过反射或动态修改字节码的方式,直接修改了logContext字段的值,绕过了代码中的非空检查逻辑。单例初始化过程中的异常:
如果单例是懒加载模式,在实例初始化过程中(比如构造方法里)就调用了addTraceIdToThreadContext,此时logContext可能还未完成初始化,或者初始化过程中出现异常导致logContext为null,但后续判断时因为JIT指令重排等原因,让nonNull返回true,最终触发NPE。
内容的提问来源于stack exchange,提问作者henriwang

