我想知道是否有任何关于重复调用(在Java中)Method.getAnnotation(Class)
和Field.getAnnotation(Class)
方法的性能的比较/研究,而不是(在程序启动时)存储带有类的元数据信息的预先计算的Map,并在以后重复查询它。哪一个将提供最好的运行时性能?
这个性能在Java 5、6和7下是一样的吗?
Map应该是更可取的方法。主要问题不仅仅是关于缓存。还提高了多线程争用。在method . getannotation()中,它调用一个同步私有方法declaredAnnotations()。
我知道这是一个相当老的问题,但是对于最近的jdk的结果可能仍然很有趣。
我编写了一些JMH基准测试,以了解注释信息缓存可能产生的影响:
@State(Scope.Thread)
public static class StateWithMethodAndHashMap {
public StateWithMethodAndHashMap() {
try {
cache = new HashMap<>();
method = AnnotationCachingBenchmark.class.getMethod("methodWithAnnotations");
final Annotation annotation = method.getAnnotation(Deprecated.class);
cache.put(method, annotation);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
final HashMap<Method, Annotation> cache;
final Method method;
}
@State(Scope.Thread)
public static class StateWithMethodAndConcurrentHashMap {
public StateWithMethodAndConcurrentHashMap() {
try {
cache = new ConcurrentHashMap<>();
method = AnnotationCachingBenchmark.class.getMethod("methodWithAnnotations");
cache.put(method, method.getAnnotation(Deprecated.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
final ConcurrentHashMap<Method, Annotation> cache;
final Method method;
}
@State(Scope.Thread)
public static class StateWithMethod {
public StateWithMethod() {
try {
method = AnnotationCachingBenchmark.class.getMethod("methodWithAnnotations");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
final Method method;
}
@Deprecated
public void methodWithAnnotations() {
}
@Benchmark
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void annotationsByReflection(final Blackhole aBh, final StateWithMethod aState) throws Exception {
aBh.consume(aState.method.isAnnotationPresent(Deprecated.class)
|| aState.method.getClass().isAnnotationPresent(Deprecated.class));
}
@Benchmark
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void annotationsByHashMap(final Blackhole aBh, final StateWithMethodAndHashMap aState) throws Exception {
aBh.consume(aState.cache.get(aState.method));
}
@Benchmark
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void annotationsByConcurrentHashMap(final Blackhole aBh, final StateWithMethodAndConcurrentHashMap aState)
throws Exception {
aBh.consume(aState.cache.get(aState.method));
}
JDK 1.8.0_172, Java HotSpot(TM) 64位Server VM, 25.172-b11:
Benchmark Mode Cnt Score Error Units
AnnotationCachingBenchmark.annotationsByConcurrentHashMap thrpt 5 0.152 ± 0.009 ops/ns
AnnotationCachingBenchmark.annotationsByHashMap thrpt 5 0.144 ± 0.005 ops/ns
AnnotationCachingBenchmark.annotationsByReflection thrpt 5 0.043 ± 0.001 ops/ns
AnnotationCachingBenchmark.annotationsByConcurrentHashMap avgt 5 6.610 ± 0.094 ns/op
AnnotationCachingBenchmark.annotationsByHashMap avgt 5 6.963 ± 0.414 ns/op
AnnotationCachingBenchmark.annotationsByReflection avgt 5 23.248 ± 0.339 ns/op
JDK 13, OpenJDK 64位Server VM, 13+33:
Benchmark Mode Cnt Score Error Units
AnnotationCachingBenchmark.annotationsByConcurrentHashMap thrpt 5 0.128 ± 0.027 ops/ns
AnnotationCachingBenchmark.annotationsByHashMap thrpt 5 0.136 ± 0.031 ops/ns
AnnotationCachingBenchmark.annotationsByReflection thrpt 5 0.139 ± 0.010 ops/ns
AnnotationCachingBenchmark.annotationsByConcurrentHashMap avgt 5 7.335 ± 1.067 ns/op
AnnotationCachingBenchmark.annotationsByHashMap avgt 5 6.634 ± 0.184 ns/op
AnnotationCachingBenchmark.annotationsByReflection avgt 5 7.234 ± 0.567 ns/op
比较1.8和13,你可以清楚地看到注释被JDK缓存的效果。因此,对于最新的jdk,不需要关心缓存这些信息,因为它只会引入额外的开销。
我想这取决于JVM的实现。但是以Oracle JVM为例,它在方法和字段实例上维护了所有注释的缓存,这相当于你所说的map方法。
但是这里有一个陷阱;由于方法/字段实例对于每个对象都是唯一的,因此如果您最终为一个给定的类创建了许多对象,那么您将失去所提供的性能优势。在这种情况下,类名+方法名/类名+字段名到相关注释列表的静态映射胜过所使用的内置缓存方法。
顺便说一句,你是如何预先计算地图的?它是在应用程序启动时还是在一些自动生成的代码上完成的?您是否真的确认在您的案例中缓存注释实例是安全的?对于这样的问题,最好的解决方案是对你的应用进行配置/测量,并使用在给定用例中看起来成功的解决方案。