使用JMH对Java静态方法做微基准测试时触发IndexOutOfBoundsException
问题分析与解决方案
首先,这个IndexOutOfBoundsException的直接原因很明确:你尝试访问的索引(902)超出了id列表的大小(0)——也就是说,你的列表根本没有被填充任何元素,是空的。
为什么列表是空的?
问题出在你的JMH @Setup方法上:你把它声明成了static final,但JMH的@Setup方法不能是静态的,也不能用final修饰。
JMH的工作机制是创建基准测试类的实例,然后调用实例的@Setup方法来完成初始化工作。静态方法属于类本身,不会被JMH的实例调用,所以你的setup()代码从头到尾都没执行过,那些id、property_address等列表自然还是空的。
另外还有两个潜在的坑:
- 你把
n声明成了静态变量,它会在类加载时就生成,但此时列表还没填充,虽然这次报错不是因为这个,但逻辑上不合理。 - 你的
setup()方法里没有给first_name和last_name添加元素,但基准方法里却调用了它们的get()方法,就算前面的问题解决了,这里也会触发同样的越界异常。
修正后的代码
下面是调整后的完整基准测试代码,我标注了关键的修改点:
import org.openjdk.jmh.annotations.*; import java.util.ArrayList; import java.util.List; import java.util.Random; // 必须添加@State注解,指定作用域,JMH才能正确管理实例和初始化 @State(Scope.Benchmark) public class MyBenchmark { private Random rand = new Random(); private int n; // 改成实例变量,不要静态 // 把列表也改成实例变量(当然静态也能工作,但配合@State用实例变量更符合JMH的设计) private List<Integer> id = new ArrayList<>(); private List<Integer> property_address = new ArrayList<>(); private List<Integer> first_name = new ArrayList<>(); private List<Integer> last_name = new ArrayList<>(); private List<Integer> email = new ArrayList<>(); private List<Integer> owner_address = new ArrayList<>(); private List<Integer> price = new ArrayList<>(); private List<Integer> date_sold = new ArrayList<>(); // 移除static和final,改成普通实例方法 @Setup public void setup() { int x = 1000; for (int i = 0; i < x; i++) { id.add(rand.nextInt(500) + 1); property_address.add(rand.nextInt(1000) + 1); first_name.add(rand.nextInt(1000) + 1); // 补充填充first_name last_name.add(rand.nextInt(1000) + 1); // 补充填充last_name email.add(rand.nextInt(1000) + 1); owner_address.add(rand.nextInt(1000) + 1); price.add(rand.nextInt(1000) + 1); date_sold.add(rand.nextInt(1000) + 1); } // 把n的初始化移到这里,确保列表已经填充完成后再生成有效索引 n = rand.nextInt(999) + 1; System.out.println("Setup Complete"); } @Benchmark public void getPropertybyId() { // 注意:基准测试里尽量不要用System.out.println! // IO操作会严重干扰测试结果,如果你只是要验证逻辑,可以保留,但正式测试一定要删掉 System.out.println(id.get(n) + ", " + property_address.get(n) + ", " + first_name.get(n) + ", " + last_name.get(n) + ", " + email.get(n) + ", " + owner_address.get(n) + ", " + price.get(n) + ", " + date_sold.get(n)); } }
额外的优化建议
- 去掉基准方法里的
System.out.println:IO操作的开销远大于List的get()操作,会让你的基准测试结果完全失真。如果需要验证逻辑,可以在调试时保留,正式测试一定要移除。 - 如果你想更精准地测试
get()操作的性能,可以把基准方法的返回值改成这些元素的组合(比如返回一个字符串或者自定义对象),JMH会自动处理,不会影响测试结果。
内容的提问来源于stack exchange,提问作者Kabira Suleman
相关产品推荐
相关产品推荐

