J2EE 應用若響應緩慢、無法承受流量增長,往往源於 JSP 與 Servlet 層面的效能瓶頸。本文整理八項經實戰驗證的優化技巧,涵蓋開發期設計與部署期配置,助你大幅提升系統吞吐量與彈性。
HttpServlet.init() 中快取靜態資源init() 僅在 Servlet 實例化後呼叫一次,適合執行昂貴的初始化操作。典型案例:從 JNDI 取得 DataSource 並快取為實例變數,避免每次請求都做 JNDI 查詢。
public class ControllerServlet extends HttpServlet {
private javax.sql.DataSource testDS = null;
public void init(ServletConfig config) throws ServletException {
super.init(config);
try {
Context ctx = new InitialContext();
testDS = (javax.sql.DataSource) ctx.lookup("jdbc/testDS");
} catch (NamingException ne) { ne.printStackTrace(); }
}
public javax.sql.DataSource getTestDS() { return testDS; }
}
開發期自動重載方便,但正式環境會導致類別載入器頻繁掃描、記憶體洩漏、類別衝突。正式部署務必在容器設定關閉 reloadable 或 development 模式。
<%@ page session="false" %> 避免自動建立 Session。session.invalidate() 釋放資源。maxInactiveInterval,減少記憶體佔用與 GC 壓力。檢查請求 Accept-Encoding 標頭,支援 gzip 時用 GZIPOutputStream 包裝輸出流,可大幅縮減 HTML 傳輸體積:
String encoding = request.getHeader("Accept-Encoding");
OutputStream out;
if (encoding != null && encoding.indexOf("gzip") != -1) {
response.setHeader("Content-Encoding", "gzip");
out = new GZIPOutputStream(response.getOutputStream());
} else {
out = response.getOutputStream();
}
該介面會導致容器為每個請求建立獨立 Servlet 實例,產生極大系統開銷,且已在 Servlet 2.4 棄用。執行緒安全問題應用同步區塊、ThreadLocal 或無狀態設計解決。
容器預設可能為每請求建立/銷毀執行緒。設定執行緒池(最小/最大執行緒數、增長步長)可重用執行緒,顯著降低建銷銷開銷,提升併發處理能力。
<%@ include file="common.jsp" %> — 編譯期合併,效能最佳,適用不常變動的片段。<jsp:include page="dynamic.jsp" flush="true"/> — 請求期合併,適用頻繁變動內容,但有額外開銷。原則:能用靜態 Include 就不用動態。
<jsp:useBean> 設定適當 ScopeScope 決定 Bean 生命週期與可見範圍:
page(預設):僅當前頁面請求有效。request:同一請求轉發鏈共享。session:跨請求同一用戶共享,易造成記憶體堆積。application:全域共享,需注意執行緒安全。錯誤選用 session 或 application 會導致物件過早駐留記憶體,增加 GC 壓力。務必依業務需求選擇最小範圍,並及時移除。
這八項技巧涵蓋資源快取、容器配置、Session 管理、傳輸優化、執行緒模型、編譯策略與 Scope 設計。逐項落實,能讓 JSP/Servlet 應用在高併發下保持低延遲、高穩定。