Web 應用中,用戶關閉瀏覽器、跳轉外站、進程崩潰等非正常登出不會觸發 logout 動作。若僅依賴顯式登出,Application 級在線用戶列表將殘留僵屍數據,影響管理員決策(如備份窗口、系統升級通知)。
利用 JVM 垃圾回收機制:Session 失效 → 關聯業務對象不可達 → GC 調用 finalize() → 執行清理邏輯。
package com;
public class UserInfo {
public String name = "";
public String sessionId = "";
}
package com;
import java.util.Vector;
public class TestSession {
public UserInfo user = new UserInfo();
private Vector onlineUsers; // 應用級共享列表引用
// 驗證邏輯省略
public boolean verify(String username, String password) { return true; }
// 注入共享列表引用
public void setSessionVar(String sessionId, Vector appVector) {
this.user.sessionId = sessionId;
this.onlineUsers = appVector;
}
// 線程安全添加用戶
private static synchronized void addOnlineUser(UserInfo u, Vector vec) {
if (u == null || vec == null) return;
for (int i = 0; i < vec.size(); i++) {
if (((UserInfo)vec.get(i)).sessionId.equals(u.sessionId)) {
vec.set(i, u); // 更新已有
return;
}
}
vec.add(u); // 新增
}
// 線程安全移除用戶
private static synchronized void removeOnlineUser(UserInfo u, Vector vec) {
if (u == null || vec == null) return;
for (int i = 0; i < vec.size(); i++) {
if (((UserInfo)vec.get(i)).sessionId.equals(u.sessionId)) {
vec.remove(i);
return;
}
}
}
// 核心:finalize() 作為安全網
protected void finalize() {
removeOnlineUser(this.user, this.onlineUsers);
}
// 業務入口
public boolean login(String username) {
this.user.name = username;
addOnlineUser(this.user, this.onlineUsers);
return true;
}
public boolean logout() {
this.finalize(); // 顯式登出時主動調用
this.user = null;
this.onlineUsers = null;
return true;
}
}
<%
String action = request.getParameter("actiontype");
TestSession ts = (TestSession) session.getAttribute("testSession");
Vector appUsers = (Vector) application.getAttribute("app_users");
if (appUsers == null) {
appUsers = new Vector();
application.setAttribute("app_users", appUsers);
}
if ("login".equals(action)) {
if (ts != null) { // 已登入則先登出
session.removeAttribute("testSession");
if (!ts.user.name.equals("")) ts.logout();
}
ts = new TestSession();
if (!ts.verify(request.getParameter("username"), request.getParameter("password"))) {
out.print("登入失敗");
} else {
session.setAttribute("testSession", ts);
ts.setSessionVar(session.getId(), appUsers);
ts.login(request.getParameter("username"));
out.print("登入成功");
}
} else if ("logout".equals(action)) {
if (ts != null) {
session.removeAttribute("testSession");
if (!ts.user.name.equals("")) {
out.print(ts.user.name + " 登出成功");
ts.logout();
}
}
session.invalidate();
}
%>
<%
Vector vec = (Vector) application.getAttribute("app_users");
if (vec != null) {
for (int i = 0; i < vec.size(); i++) {
UserInfo u = (UserInfo) vec.get(i);
out.println(u.name + " | Session: " + u.sessionId + "
");
}
}
%>
userA → 監控頁顯示 userAuserB → 監控頁顯示 userA, userBonlineUsers 是 Application 級共享對象,所有用戶線程並發訪問TestSession,無法防止多用戶同時操作共享列表TestSession.class(Class 對象),實現類級別互斥,任意時刻僅一線程能進入 add/removevalueUnbound(),Session 失效時確定性回調,無需依賴 GC。@WebListener 監聽 HttpSessionEvent,集中處理清理邏輯。lastAccessedTime 超時用戶並清理。finalize() 是 JVM 提供的「最後機會」,而非「首選工具」。在 HttpSessionBindingListener 等標準機制缺失的舊版本 Servlet 環境中,它能作為補償性安全網解決非正常登出清理問題。但現代開發中,請優先選擇確定性生命週期回調,將 finalize() 留給真正無法以其他方式處理的邊緣場景(如 JNI 關聯的原生資源釋放)。