JSP/Servlet 應用性能優化不靠單一銀彈,而在於系統級的「減少重複計算、降低鎖競爭、壓縮網絡傳輸、精準控制對象生命周期」。本文提煉七大高收益優化方向,附代碼模板與避坑指南。
場景:JNDI 查找 DataSource、載入配置文件、初始化線程池、預熱緩存。
public class InitCacheServlet extends HttpServlet {
private DataSource ds;
private Map<String, String> configMap;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
try {
Context ctx = new InitialContext();
ds = (DataSource) ctx.lookup("java:comp/env/jdbc/mainDS"); // 緩存 DataSource
configMap = loadConfig(config.getServletContext().getResourceAsStream("/WEB-INF/app.properties"));
} catch (NamingException | IOException e) {
throw new ServletException(e);
}
}
// 業務方法直接用 ds.getConnection(),無需重複 JNDI lookup
}
收益:消除每請求一次 JNDI 查找開銷(通常 1-5ms)。
原理:開發模式下容器輪詢文件修改時間,觸發重新編譯,類加載器頻繁創建導致 Metaspace 抖動、GC 增加。
配置 (Tomcat conf/web.xml 或項目 web.xml):
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>development</param-name>
<param-value>false</param-value> <!-- 關鍵:生產設為 false -->
</init-param>
<init-param>
<param-name>checkInterval</param-name>
<param-value>0</param-value> <!-- 0 表示不檢查 -->
</init-param>
</servlet>
<%@ page session="false" %>,避免創建無用 Session 對象。session.invalidate()。web.xml <session-config><session-timeout>15</session-timeout></session-config>(分鐘),減少內存佔用。Tomcat 原生配置 (server.xml):
<Connector port="8080" protocol="HTTP/1.1"
compression="on"
compressionMinSize="2048"
noCompressionUserAgents="gozilla, traviata"
compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/json,application/javascript,application/xml" />
收益:文本類響應體積通常縮減 70%-90%,首屏加載明顯提速。
Tomcat server.xml:
<Executor name="tomcatThreadPool" namePrefix="http-"
maxThreads="200" minSpareThreads="25" maxIdleTime="60000" />
<Connector executor="tomcatThreadPool" port="8080" ... />
maxThreads:根據 CPU 核心數、業務類型(IO 密集可設 200-500,CPU 密集設 核心數×2)。minSpareThreads:預熱線程,避免突發流量創建線程延遲。ThreadPool MBean(currentThreadsBusy / currentThreadCount),長期 > 80% 需擴容。| 機制 | 語法 | 編譯/運行期 | 性能 | 適用場景 |
|---|---|---|---|---|
| 靜態包含 | <%@ include file="header.jsp" %> | 編譯期合併為一 Servlet | **最快**,無額外請求分發 | 頁頭頁尾、版權、靜態片段 |
| 動態包含 | <jsp:include page="dynamic.jsp" flush="true"/> | 運行期 RequestDispatcher.include | 稍慢,但支援傳參、動態路徑、獨立編譯 | 內容區塊、需參數化的片段 |
原則:能用靜態包含就不用動態包含。
<jsp:useBean id="cart" class="com.example.Cart" scope="session" />
| scope | 生命周期 | 典型用途 | 誤用後果 |
|---|---|---|---|
| page | 單次請求渲染期間 | 頁面級工具對象、臨時計算結果 | 無 |
| request | 一次 HTTP 請求(含 forward) | 表單回填、請求級緩存 | 放大對象導致內存洩漏 |
| session | 用戶會話(默認 30 分鐘無操作) | 用戶身份、購物車、偏好設置 | 放請求級對象 → 佔用內存、線程不安全 |
| application | 整個 Web 應用生命周期 | 全局配置、共享緩存、Service 實例 | 放用戶級對象 → 數據洩露、併發錯誤 |
原則:選最小能滿足需求的作用域,用完即銷毀,減少 GC 壓力與併發風險。
StringBuilder 替代 +(循環/高頻調用處)。System.out.println,改用 SLF4J + Logback 異步日誌。ServletOutputStream;文本用 PrintWriter,避免雙向轉換開銷。性能優化是系統工程,建議按「開啟壓縮 → 關閉自動重載 → 調優線程池 → 精準 Session/Bean 作用域 → init 緩存 → 靜態包含 → 微優化」順序推進,每步壓測驗證收益。