WhitelistCheckingMethodVisitor的主要任务是为方法调用创建调用图。规则很简单,如果候选方法只调用确定性的方法,那么它也是确定性的。在对整个类进行了扫描之后,调用图可以帮助我们判断整个类是否是确定性的。
这个实现起来很简单,我们通过覆盖AMS Visitor API的visitMethodInsn()方法来实现。
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { CandidateMethod candidateMethod = candidacyStatus.getCandidateMethod(currentMethodName); String internalName = owner + "." + name + ":" + desc; if (candidacyStatus.putIfAbsent(internalName)) { candidacyStatus.addToBacklog(internalName); } CandidateMethod referencedCandidateMethod = candidacyStatus.getCandidateMethod(internalName); candidateMethod.addReferencedCandidateMethod(referencedCandidateMethod); // ... }method visitor也需要考虑到一些边界情况,还需要做一些清理工作。例如,Java运行时会停止超限的线程,并抛出ThreadDeath错误,捕捉到ThreadDeath异常的代码会试图绕过确定性检查。
对于这种情况,为了避免ThreadDeath异常或者它的父类(Error或Throwable)被捕捉到,method visitor需要实现一些逻辑,这些逻辑代码需要在访问try-catch代码块时被调用。
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { if (type == null) throw new IllegalArgumentException("Exception type must " + "not be null in try/catch block in " + currentMethodName); // 不允许ThreadDeath或它的父类被捕捉到,从而保证了确定性 if (type.equals(Utils.THREAD_DEATH) || type.equals(Utils.ERROR) || type.equals(Utils.THROWABLE)) { CandidateMethod candidateMethod = candidacyStatus.getCandidateMethod(currentMethodName); candidateMethod.disallowed("Method " + currentMethodName + " attempts to catch ThreadDeath, Error or Throwable"); } } }在确定了单个方法的调用图之后,通过简单的分析就能判断方法是否是确定性的。如果一个方法具有如下两个特点,那么它就是确定性的。
没有明显的非确定性特征。
只调用了确定性的方法。
单个方法扫描完毕之后,程序返回。等所有方法扫描完毕,class visitor会检查是否所有方法都被标记为确定性的。如果是,那么这个类就被标记为可加载的,并在加载之前注入运行时资源监测代码。
这个白名单框架刚面世不久,建议对其多做一些测试。它所提供的确定性执行机制不仅适用于新的需求场景,也为那些对确定性要求很高的生产系统提供了良好的基础。
关于作者Ben Evans是jClarity的联合创始人。jClarity是一个初创公司,致力于为开发和运维团队提供性能方面的工具和服务。他是LJC(London Java User Group)的组织者,也是JCP执行委员会成员,为Java生态系统制定规范。他获得过Java Champion殊荣,并3次拿下JavaOne Rockstar Speaker的称号。他是《Java程序员修炼之道》(The Well-Grounded Java Developer)和《Java技术手册》(Java in a Nutshell)的合著者,还是Java平台、性能、并发等相关主题的演讲常客。Ben擅长演讲、教授、协作和咨询,可以直接联系他以便获得更多的信息。