我们有一个OSGi容器,里面有很多产品,其中一个是我们的产品
我们正在运行一些性能测试,并且存在一个奇怪的问题,即每个OSGi容器重启都会导致我们的一些测试的性能偏差高达400%。
通过一些测试和事情,我能够跟踪这个方法:
public static Method getMethodForSlotKey(Class<?> cls, String slotKey, String methodType) {
Method[] methods = cls.getMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
String methName = method.getName();
if (methName.startsWith(methodType)) {
IDataAnnotation annot = method.getAnnotation(IDataAnnotation.class);
if (annot != null) {
String annotSlotKey = annot.SlotKey();
if (annotSlotKey != null && annotSlotKey.equals(slotKey)) {
Class<?>[] paramTypes = method.getParameterTypes();
// for now, check length == 1 for setter and 0 for getter.
int len = SET_TXT.equals(methodType) ? 1 : 0;
if (paramTypes != null && paramTypes.length == len) {
return method;
}
}
}
}
}
}
return null;
}
此方法主要进行反射和字符串比较。
现在,我所做的是缓存此方法的结果,立即我们的偏差降至10 - 20%。当然,这种方法经常被调用,所以有很明显的改进。
我仍然不明白为什么非缓存版本有如此高的偏差,唯一的区别是OSGi / JVM重启?重启期间究竟会发生什么?例如,对于不同的类加载器是否存在任何已知的性能问题? 是否有可能在OSGi环境中,库将在重新启动之间以不同的顺序加载?
我正在寻找一条线索,这是有道理的。
更新
事实证明这个电话:
Method[] methods = cls.getMethods();
引起偏差。我仍然不理解它,所以如果有人这样做,我会很高兴听到它。
答案 0 :(得分:0)
我怀疑这是因为您重新启动服务器/ OSGi容器正在清除您正在询问的类中的缓存Method[]
*。缓存将被清除以确保在重新加载的类具有与具有相同名称的旧API不同的公共API(即,不同的方法)的情况下的正确操作(例如,如果您加载较新的版本,则可能发生这种情况)
当你实现你的'wrapper'方法的缓存时,你正在规避这个并引入一个假设 - 重新加载的类将具有与先前加载的具有相同名称的类相同的方法。对于大多数情况来说可能是一个非常安全的假设,但不是你想要核心Java的假设。
(OpenJDK)Class
类is here的源代码。如果您仔细查看,就会看到getMethods()
来电privateGetPublicMethods()
。如果缓存的publicMethods
数组*为null,或者由clearCachesOnClassRedfinition()
使其为空,那么该类将不得不经历相对密集的过程,即查找类中的所有公共方法以及任何超类继承自它实现的接口。然后缓存,getMethods()
的速度将恢复正常。
*严格来说,缓存的值是对数组的软引用。
答案 1 :(得分:0)
这似乎是错综复杂的JIT的另一个案例,它似乎随着时间的推移不断变化和发展。这个答案可能会对您的问题有所了解:Java: what is JITC's reflection inflation?
我相信你正在观察JIT如何优化你的反射调用的后果。有一段时间(第一次X调用)会很慢,直到JIT决定优化你的方法。
您可以尝试的东西 - 测试这个理论 - 是修改您的代码并将违规调用置于虚拟循环中并在第一次调用时执行多次(确保您对结果执行某些操作)调用强制java不完全优化掉调用,如果它认为它没有后果)。如果这导致第一次使用的执行时间非常长,而后续调用的差异非常小,那么理论听起来是正确的。