在生产环境中测量方法级执行性能时,单次测量的结果往往受系统调度、资源波动等因素影响存在较大误差,无法反映真实性能表现。System.nanoTime 是 Java 中提供的高精度计时工具,适合用于短时间间隔测量,但单独使用它只能得到单次执行耗时,无法排除偶然误差。结合统计学算法对多次采样的耗时数据进行处理,能够过滤异常值、计算置信区间,得到更贴近真实情况的性能数据。

为什么选择 System.nanoTime 作为计时工具
Java 中常用的计时方法有两种,分别是 System.currentTimeMillis 和 System.nanoTime。System.currentTimeMillis 返回的是当前时间与 1970 年 1 月 1 日 UTC 时间相差的毫秒数,它的精度受系统时钟影响,最小精度通常是毫秒级,而且如果系统时间被调整,这个值也会发生变化,不适合用于短时间间隔的精确测量。
而 System.nanoTime 返回的是 JVM 的高精度时间源的数值,单位是纳秒,它的精度远高于 System.currentTimeMillis,并且不受系统时钟调整的影响,专门用于测量时间间隔,是方法级性能测量的首选计时工具。需要注意的是,System.nanoTime 的值本身没有绝对意义,只有两次调用之间的差值才有意义。
基础采样实现
使用 System.nanoTime 测量方法执行耗时的基础逻辑是在方法执行前后分别调用 System.nanoTime,计算两者的差值,再将纳秒转换为更易读的单位。以下是一个简单的采样示例:
public class PerformanceSampler {
public static void sampleMethod() {
// 方法执行前的纳秒时间
long start = System.nanoTime();
// 待测量的目标方法
targetMethod();
// 方法执行后的纳秒时间
long end = System.nanoTime();
// 计算耗时,转换为微秒
long costMicro = (end - start) / 1000;
System.out.println("方法执行耗时:" + costMicro + " 微秒");
}
private static void targetMethod() {
// 模拟方法执行逻辑
try {
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
上述代码只能得到单次执行的耗时,在生产环境中,由于 GC、线程调度、CPU 资源竞争等因素的影响,单次结果的偶然性很大,无法代表方法的真实性能,因此需要多次采样后结合统计学算法处理。
统计学算法处理采样数据
多次采样后我们会得到一组耗时数据,直接取平均值仍然可能受极端异常值影响,因此需要用统计学方法处理数据,得到更可靠的性能指标。
1. 去除异常值
采样数据中可能存在因系统临时波动产生的极端大值或极端小值,这些异常值会严重影响统计结果。我们可以使用四分位距法(IQR)来识别并去除异常值。
四分位距法的逻辑是:先将数据从小到大排序,计算第一四分位数 Q1(25% 位置的值)、第三四分位数 Q3(75% 位置的值),然后计算 IQR = Q3 - Q1,正常值的范围是 [Q1 - 1.5*IQR, Q3 + 1.5*IQR],超出这个范围的就是异常值,需要剔除。
以下是去除异常值的代码实现:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class StatisticsUtil {
/**
* 使用四分位距法去除异常值
* @param data 原始采样数据,单位是微秒
* @return 去除异常值后的数据列表
*/
public static List<Long> removeOutliers(List<Long> data) {
if (data == null || data.size() < 4) {
// 数据量太少时不做异常值处理,直接返回原数据
return new ArrayList<>(data);
}
// 排序数据
List<Long> sortedData = new ArrayList<>(data);
Collections.sort(sortedData);
int size = sortedData.size();
// 计算Q1和Q3的位置
double q1Pos = size * 0.25;
double q3Pos = size * 0.75;
long q1, q3;
// 处理位置不是整数的情况,取相邻两个值的平均值
if (q1Pos % 1 == 0) {
q1 = sortedData.get((int) q1Pos);
} else {
int lower = (int) Math.floor(q1Pos);
int upper = (int) Math.ceil(q1Pos);
q1 = (sortedData.get(lower) + sortedData.get(upper)) / 2;
}
if (q3Pos % 1 == 0) {
q3 = sortedData.get((int) q3Pos);
} else {
int lower = (int) Math.floor(q3Pos);
int upper = (int) Math.ceil(q3Pos);
q3 = (sortedData.get(lower) + sortedData.get(upper)) / 2;
}
// 计算IQR和正常范围边界
long iqr = q3 - q1;
long lowerBound = q1 - 1500 * iqr; // 1.5*IQR,这里用1500是因为iqr是微秒,1.5倍用整数计算
long upperBound = q3 + 1500 * iqr;
// 过滤异常值
List<Long> result = new ArrayList<>();
for (Long value : sortedData) {
if (value >= lowerBound && value <= upperBound) {
result.add(value);
}
}
return result;
}
}
2. 计算核心统计指标
去除异常值后,我们可以计算平均耗时、中位数耗时、P99 耗时等核心指标,这些指标能更全面地反映方法的性能表现:
- 平均耗时:所有正常样本的平均值,反映整体平均性能
- 中位数耗时:排序后中间位置的值,不受极端值影响,反映典型性能
- P99 耗时:99% 的样本都小于该值,反映极端情况下的性能表现,对生产环境很重要
以下是计算这些指标的实现:
import java.util.Collections;
import java.util.List;
public class StatisticsUtil {
/**
* 计算平均耗时
* @param data 去除异常值后的数据
* @return 平均耗时,单位微秒
*/
public static double calculateAverage(List<Long> data) {
if (data == null || data.isEmpty()) {
return 0.0;
}
long sum = 0;
for (Long value : data) {
sum += value;
}
return (double) sum / data.size();
}
/**
* 计算中位数耗时
* @param data 去除异常值后的数据
* @return 中位数耗时,单位微秒
*/
public static double calculateMedian(List<Long> data) {
if (data == null || data.isEmpty()) {
return 0.0;
}
List<Long> sorted = new ArrayList<>(data);
Collections.sort(sorted);
int size = sorted.size();
if (size % 2 == 1) {
return sorted.get(size / 2);
} else {
return (sorted.get(size / 2 - 1) + sorted.get(size / 2)) / 2.0;
}
}
/**
* 计算P99耗时
* @param data 去除异常值后的数据
* @return P99耗时,单位微秒
*/
public static long calculateP99(List<Long> data) {
if (data == null || data.isEmpty()) {
return 0;
}
List<Long> sorted = new ArrayList<>(data);
Collections.sort(sorted);
int index = (int) Math.ceil(0.99 * sorted.size()) - 1;
return sorted.get(index);
}
}
3. 计算置信区间
为了知道我们的统计结果有多可靠,可以计算平均值的置信区间。在样本量足够大时,可以使用正态分布来计算 95% 置信区间,即我们有 95% 的把握认为真实的平均耗时在这个区间内。
置信区间的计算需要用到样本均值、样本标准差和样本量,公式是:置信区间 = 均值 ± 1.96 * (标准差 / √样本量),其中 1.96 是 95% 置信水平对应的 Z 值。
以下是计算置信区间的实现:
import java.util.List;
public class StatisticsUtil {
/**
* 计算95%置信区间
* @param data 去除异常值后的数据
* @return 置信区间数组,[下限, 上限],单位微秒
*/
public static double[] calculateConfidenceInterval(List<Long> data) {
if (data == null || data.size() < 2) {
return new double[]{0.0, 0.0};
}
double mean = calculateAverage(data);
// 计算样本标准差
double sumSquareDiff = 0.0;
for (Long value : data) {
double diff = value - mean;
sumSquareDiff += diff * diff;
}
double stdDev = Math.sqrt(sumSquareDiff / (data.size() - 1));
// 95%置信水平对应的Z值
double z = 1.96;
double margin = z * stdDev / Math.sqrt(data.size());
return new double[]{mean - margin, mean + margin};
}
}
生产环境完整测量方案
结合上述工具,我们可以实现一个生产环境可用的方法级性能测量方案,需要注意生产环境中采样不能影响正常业务,因此要控制采样频率和采样量。
完整实现示例如下:
import java.util.ArrayList;
import java.util.List;
public class MethodPerformanceMeasurer {
// 采样次数,生产环境建议设置合理值,避免过多影响性能
private static final int SAMPLE_COUNT = 100;
// 采样间隔,单位毫秒,避免频繁采样
private static final int SAMPLE_INTERVAL_MS = 100;
public static void measureMethod() {
List<Long> samples = new ArrayList<>();
for (int i = 0; i < SAMPLE_COUNT; i++) {
long start = System.nanoTime();
// 执行目标方法
targetMethod();
long end = System.nanoTime();
// 转换为微秒加入采样列表
samples.add((end - start) / 1000);
// 控制采样间隔
try {
Thread.sleep(SAMPLE_INTERVAL_MS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
// 去除异常值
List<Long> validSamples = StatisticsUtil.removeOutliers(samples);
if (validSamples.isEmpty()) {
System.out.println("无有效采样数据");
return;
}
// 计算核心指标
double avg = StatisticsUtil.calculateAverage(validSamples);
double median = StatisticsUtil.calculateMedian(validSamples);
long p99 = StatisticsUtil.calculateP99(validSamples);
double[] confidenceInterval = StatisticsUtil.calculateConfidenceInterval(validSamples);
// 输出结果
System.out.println("方法性能测量结果:");
System.out.println("有效采样次数:" + validSamples.size());
System.out.println("平均耗时:" + String.format("%.2f", avg) + " 微秒");
System.out.println("中位数耗时:" + String.format("%.2f", median) + " 微秒");
System.out.println("P99耗时:" + p99 + " 微秒");
System.out.println("95%置信区间:[" + String.format("%.2f", confidenceInterval[0]) + ", " + String.format("%.2f", confidenceInterval[1]) + "] 微秒");
}
private static void targetMethod() {
// 实际业务方法逻辑
}
}
注意事项
在生产环境使用该方案时,需要注意以下几点:
- 采样频率不能过高,避免测量本身消耗过多 CPU 资源,影响业务性能
- System.nanoTime 不能用于测量跨 JVM 的时间间隔,也不能用于测量线程睡眠或等待的时间,只适合测量纯 CPU 执行的时间间隔
- 如果方法执行时间本身很短,建议增加采样次数,减少偶然误差
- 统计结果要结合业务场景分析,比如 P99 耗时高可能是偶发的资源竞争导致,需要结合其他监控指标排查
注意:不要在正式的生产代码中硬编码测量逻辑,建议通过 AOP 或者 Arthas 等工具动态注入测量逻辑,避免修改业务代码,也方便随时开启或关闭测量。
System.nanoTime统计学算法方法级性能测量生产环境性能监控修改时间:2026-07-06 08:27:44