靜態變量(類變量)與實例變量(成員變量)是 Java 面向對象內存模型的兩大基石。搞清它們在類加載、對象實例化、堆/元空間佈局、線程可見性上的本質區別,是寫出正確、高效、無內存洩漏代碼的前提。本文通過字節碼視角、JOL (Java Object Layout) 實測、併發場景驗證,徹底講透這對核心概念。
| 維度 | 靜態變量 (static) | 實例變量 |
|---|---|---|
| 所屬 | 類 | 對象實例 |
| 內存區域 | 元空間 類對象 | 堆 對象實例 |
| 分配時機 | 類加載準備階段 | new 實例化時 |
| 生命週期 | 類卸載時結束(通常伴隨 JVM) | 對象 GC 回收時結束 |
| 副本數量 | 全 JVM 單份 | 每個實例一份 |
| 訪問方式 | 類名.變量 或 實例.變量(不推薦) | 實例.變量 |
| 默認值 | 類加載準備階段賦零值 | 實例化時賦零值 |
| 線程可見性 | 需 volatile 或同步保證 | 需 volatile 或同步保證 |
| 序列化 | 不序列化 | 默認序列化(transient 排除) |
public class VariableLayout {
static long staticLong = 100L;
long instanceLong = 200L;
static Object staticRef = new Object();
Object instanceRef = new Object();
}
// 類對象佈局 (元空間)
VariableLayout.class 对象:
OFFSET SIZE TYPE DESCRIPTION
0 12 (object header)
12 8 long staticLong
20 4 Object staticRef (oop)
24 4 (alignment)
// 實例對象佈局 (堆)
VariableLayout 实例:
OFFSET SIZE TYPE DESCRIPTION
0 12 (object header)
12 8 long instanceLong
20 4 Object instanceRef (oop)
24 4 (alignment)
Instance size: 24 bytes
結論:靜態變量存儲在 java.lang.Class 對象中(元空間),實例變量內聯在對象實例中(堆)。兩者互不干擾。
public class InitOrder {
static { System.out.println("1. 靜態塊"); }
static int s = print("2. 靜態變量賦值");
{ System.out.println("3. 實例塊"); }
int i = print("4. 實例變量賦值");
public InitOrder() { System.out.println("5. 構造器"); }
static int print(String msg) { System.out.println(msg); return 0; }
public static void main(String[] args) {
System.out.println("--- 首次 new ---");
new InitOrder();
System.out.println("--- 再次 new ---");
new InitOrder();
}
}
/* 輸出:
1. 靜態塊
2. 靜態變量賦值
--- 首次 new ---
3. 實例塊
4. 實例變量賦值
5. 構造器
--- 再次 new ---
3. 實例塊
4. 實例變量賦值
5. 構造器
*/
靜態初始化僅在類首次主動使用時執行一次;實例初始化每 new 執行一次。
// 錯誤:靜態 List 持有大量對象引用,永不釋放
public class CacheLeak {
private static final List
public class Counter {
private static List list = new ArrayList<>(); // 非線程安全
public static void add(int v) { list.add(v); } // 併發下 size 不準、拋異常、數據丟失
}
// 修復:Collections.synchronizedList / CopyOnWriteArrayList / 線程局部變量 / 原子類
private static final List list = new CopyOnWriteArrayList<>();
// 或
private static final AtomicInteger count = new AtomicInteger(); // 計數場景首選
@Controller
public class UserController {
// 錯誤!所有請求共享同一 currentUser,嚴重安全漏洞
private static User currentUser;
@GetMapping("/profile")
public User profile() { return currentUser; }
}
// 正確:請求作用域 Bean、ThreadLocal、或方法參數傳遞
@Controller
@RequestScope // Spring 請求作用域
public class UserController {
private User currentUser; // 每請求一個實例
}
public class DbPool {
private static final DataSource DS = createDataSource(); // 應用關閉時需關閉
static { Runtime.getRuntime().addShutdownHook(new Thread(DS::close)); }
}
public class ConstVsStatic {
static final int CONSTANT = 100; // 編譯期常量,調用處內聯
static final int STATIC_FINAL = new Random().nextInt(); // 運行期賦值,非常量
static int mutableStatic = 0; // 可變靜態變量
}
static final 基本類型/字符串字面量 = 編譯期常量,調用方字節碼內聯值,修改需重新編譯調用方。static final 引用類型 = 引用不可變,對象內部狀態可變。ClassName.field),避免通過實例訪問產生誤導。