我正在使用解释器进行运行时检测。 我现在的重点是在任何
ReEntractLock
lock()
和 unlock()
函数之前添加函数调用。
作为参考,这是 Test.java:
class Test {
static Lock lock = new ReentrantLock();
public String name;
public Test(String name) {
this.name = name;
}
public void test() {
lock.lock();
try {
System.out.println("Hello " + name);
name = "World";
} finally {
lock.unlock();
}
}
}
通过检查字节码,我发现每次锁定/解锁时,都有一个
invokeinterafce
字节码。
public void test();
Code:
0: getstatic #13 // Field lock:Ljava/util/concurrent/locks/Lock;
3: invokeinterface #17, 1 // InterfaceMethod java/util/concurrent/locks/Lock.lock:()V
8: ...
26: getstatic #13 // Field lock:Ljava/util/concurrent/locks/Lock;
29: invokeinterface #23, 1 // InterfaceMethod java/util/concurrent/locks/Lock.unlock:()V
34: ...
所以我进入
src/hotspot/cpu/x86/templateTable_x86.cpp
,并添加了一个 vm leaf 调用。
void TemplateTable::invokeinterface(int byte_no) {
...
// rbx: Method* to call
// rcx: receiver
// Check for abstract method error
// Note: This should be done more efficiently via a throw_abstract_method_error
// interpreter entry point and a conditional jump to it in case of a null
// method.
__ testptr(rbx, rbx);
__ jcc(Assembler::zero, no_such_method);
__ profile_arguments_type(rdx, rbx, rbcp, true);
__ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::monitor_invoke), rbx); // <- the call, rbx contains the method pointer
...
}
但是,这样我就得到了调用接口调用的每个方法,而I能想到的唯一方法就是检查名称,就像这样
if (strstr(name, "java.util.concurrent.locks.ReentrantLock"))...
然而,这看起来很糟糕,并且可能会增加太多额外的开销。
是否有其他方法可以跟踪
lock/unlock
拨打的 java.util.concurrent
电话或者更便宜的方法来进行姓名比较?
检测
invokeinterface
还不够。如果 lock
字段使用 ReentrantLock
类型声明,则将使用 lock.lock()
而不是 invokevirtual
来调用 invokeinterface
。
好消息是您不需要更改 VM 源来跟踪方法调用。有一个用于此类事物的标准 API,称为“JVM 工具接口”(JVM TI)。您所需要做的就是创建一个代理,为 MethodEntry 和 MethodExit 事件安装回调。您可以在这个答案中找到使用这些事件的示例。 如文档中所述,启用 MethodEntry/MethodExit 事件可能会显着降低性能。要以更高效的方式拦截对
ReentrantLock.lock
/
unlock
方法的调用,请考虑使用 字节码检测。这个想法是在运行时动态更改
lock
/unlock
方法的实现,插入字节码来调用监控函数。此类检测方法可以受益于 JIT 编译和所有 JVM 优化。