在 Java 服務端開發中,性能問題往往隱藏在特定業務邏輯的執行時間裡。傳統 JVM 剖析工具雖強大,但多用於事後分析,難以持續監控生產環境。Perf4j 專為持續性能統計設計:它擴展主流日誌框架(Log4j、Logback、SLF4J 等),自動統計代碼塊或方法的執行時間,並支持將聚合指標(平均值、最大值、TPS 等)輸出到文件、JMX、Servlet 圖表等多種終端,實現「零侵入式」長期監控。
public static void basicStopWatch() throws InterruptedException {
StopWatch stopWatch = new StopWatch("TransactionA"); // 構造即開始計時
Thread.sleep(2000); // 模擬業務邏輯
String result = stopWatch.stop(); // 停止並獲取統計字符串
System.out.print(result);
// 輸出示例:start[1340442785756] time[1995] tag[TransactionA]
}
tag 用於區分業務事務,是後續統計聚合的關鍵鍵值。
public static void loggingStopWatch() throws InterruptedException {
LoggingStopWatch stopWatch = new LoggingStopWatch();
stopWatch.setTimeThreshold(1000); // 閾值過濾:小於 1ms 不記錄
Thread.sleep(2000);
stopWatch.lap("TransactionB"); // 結束當前階段,開啟新階段
Thread.sleep(500);
stopWatch.stop("TransactionC"); // 結束並記錄
}
配合日誌框架時,使用對應子類:Log4JStopWatch、Slf4JStopWatch 等,自動將數據輸出到配置的 Appender。
<appender name="Perf4jAppender" class="org.perf4j.log4j.AsyncCoalescingStatisticsAppender">
<!-- 聚合輸出時間片,生產環境建議調大減少 I/O -->
<param name="TimeSlice" value="10000" />
<appender-ref ref="fileAppender" />
</appender>
<appender name="fileAppender" class="org.apache.log4j.FileAppender">
<param name="File" value="performanceData.log" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%m%n" />
</layout>
</appender>
<!-- Perf4j 默認 Logger -->
<logger name="org.perf4j.TimingLogger" additivity="false">
<level value="INFO" />
<appender-ref ref="Perf4jAppender" />
</logger>
TimeSlice=10000 表示每 10 秒聚合一次統計輸出。
Performance Statistics 2012-07-02 21:45:30 - 2012-07-02 21:45:40
Tag Avg(ms) Min Max Std Dev Count
LogicalBlock1 1997.0 1997 1997 0.0 1
LogicalBlock2 499.0 499 499 0.0 1
避免在業務代碼中硬編碼計時邏輯,通過 AOP 動態織入。
需引入 perf4j.jar、perf4j-log4j.jar、spring-aop.jar、aspectjweaver.jar 等。
<aop:config>
<aop:aspect id="timing" ref="timingAspect">
<aop:pointcut id="timingcut"
expression="execution(* cn.test.perf4j.example..*.*(..)) and @annotation(profiled)"/>
<aop:around pointcut-ref="timingcut" method="doPerfLogging"/>
</aop:aspect>
</aop:config>
<bean id="timingAspect" class="org.perf4j.log4j.aop.TimingAspect"/>
<bean id="processService" class="cn.test.perf4j.example.ProcessService"/>
切點表達式中的 @annotation(profiled) 必須保留,用於傳遞 @Profiled 註解參數。
@Profiled(tag="doTimedService", timeThreshold=500L)
public void doService() { ... }
支持屬性:tag(事務名)、timeThreshold(閾值)、el(EL 表達式)、level(日誌級別)、logFailuresSeparately(異常單獨統計)、logger(自定義 Logger)。
META-INF/services/org.apache.commons.logging.LogFactory 指定 Log4j 實現類。PARENT_LAST。log4j.xml 放置於 classpath,僅配置 Perf4j Appender,其餘日誌仍由 WebSphere 統管。<appender name="perf4jJmxAppender" class="org.perf4j.log4j.JmxAttributeStatisticsAppender">
<param name="TagNamesToExpose" value="doServiceA,doServiceB"/>
<param name="NotificationThresholds" value="doServiceAMin(>200)"/>
</appender>
通過 JConsole 即可查看 MBean 屬性與實時圖表(見下圖)。

<appender name="meanExecutionTime" class="org.perf4j.log4j.GraphingStatisticsAppender">
<param name="GraphType" value="Mean"/>
<param name="TagNamesToGraph" value="doServiceA,doServiceB"/>
<appender-ref ref="meanTimeFileAppender"/>
</appender>
GraphType 可選:Mean、Max、Min、StdDev、Count、TPS。
<servlet>
<servlet-name>perf4jMonitor</servlet-name>
<servlet-class>org.perf4j.log4j.servlet.GraphingServlet</servlet-class>
<init-param>
<param-name>graphNames</param-name>
<param-value>meanExecutionTime,executionTPS</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>perf4jMonitor</servlet-name>
<url-pattern>/perf4jMonitor</url-pattern>
</servlet-mapping>
訪問 /perf4jMonitor 即可查看實時性能圖表,可嵌入運維管理平台。
StopWatch 快速定位瓶頸。