+--------------------------------------------------------------+ | JVM Process Heap | | +-------------------------+ +---------------------------+ | | | Method Area | | Heap Memory | | | | (Metaspace, Native) | | (Object Instances) | | | | +-------------------+ | | | | | | | Class Metadata | | | Visitor Instance 1 | | | | | Visitor.class | | | count=1 (Instance Var) | | | | | +-------------+ | | | visitCount ───────────┼───┐ | | | | Static Vars | | | | | │ | | | | visitCount=2| | | | Visitor Instance 2 | │ | | | +-------------+ | | | count=1 | │ | | +-------------------+ | | visitCount ───────────┼───┘ | | | | | (All instances | | Static Variables live | | Visitor Instance 3 | share ONE copy | | in Method Area | | count=5 | in Method Area) | | (Class lifecycle) | | visitCount ───────────┘ | +-------------------------+ | | | +---------------------------+ | +-------------------------+ | | | Stack Frames | (Thread Stack, per thread) | | | main() frame | | | | v1:ref --> Instance 1 | | | | v2:ref --> Instance 2 | | | | v3:ref --> Instance 3 | | | +-------------------------+ | +--------------------------------------------------------------+
.class 字節碼,生成 java.lang.Class 對象(堆中)。int=0, boolean=false, ref=null)。此時 visitCount = 0。<clinit>() 方法——按源碼順序執行靜態變量顯式賦值與 static { } 塊。此時 visitCount 仍為 0,直到構造器被調用。class Visitor {
int count = 0; // 實例變量
static int visitCount = 0; // 靜態變量
public Visitor() {
count++;
visitCount++;
}
}
public class StaticMemoryDemo {
public static void main(String[] args) {
Visitor v1 = new Visitor();
System.out.println("v1.count=" + v1.count + ", Visitor.visitCount=" + Visitor.visitCount); // 1, 1
Visitor v2 = new Visitor();
System.out.println("v2.count=" + v2.count + ", Visitor.visitCount=" + Visitor.visitCount); // 1, 2
Visitor v3 = new Visitor();
v3.count = 5; // 修改實例變量,僅影響 v3
v3.visitCount = 0; // 修改靜態變量,影響所有實例
System.out.println("v1.count=" + v1.count + ", v1.visitCount=" + v1.visitCount); // 1, 0
System.out.println("v2.count=" + v2.count + ", v2.visitCount=" + v2.visitCount); // 1, 0
System.out.println("v3.count=" + v3.count + ", v3.visitCount=" + v3.visitCount); // 5, 0
}
}
| 對象 | count (實例變量) | visitCount (靜態變量) | 說明 |
|---|---|---|---|
| v1 | 1 | 0 | v3 修改 visitCount 為 0,v1 讀取同一份副本 |
| v2 | 1 | 0 | 同上 |
| v3 | 5 | 0 | count 為實例私有,修改不影響 v1/v2 |
| 特性 | 靜態變量 (static) | 實例變量 (無 static) |
|---|---|---|
| 內存區域 | 方法區(元空間)靜態區 | 堆內存(對象實例內部) |
| 分配時機 | 類準備階段(默認值) 類初始化階段(顯式值) | 對象實例化時(new / 反射 / 反序列化) |
| 生命週期 | 類加載 → 類卸載(類加載器可回收時) | 對象創建 → GC 回收 |
| 副本數 | 全進程/類加載器唯一 | 每個實例一份 |
| 訪問方式 | 類名.變量 / 實例.變量 | 實例.變量 |
| 序列化 | 不參與(同 transient) | 參與(除非標記 transient) |
| 併發可見性 | 需 volatile 或同步保證 | 需 volatile 或同步保證 |
Class 對象在堆中。static List<BigObject> cache)若不清理會導致記憶體洩漏,無法被 GC。對策:用 WeakReference、CacheBuilder、定期清理、或遷移至專用緩存中間件。volatile static 變量存在可見性問題。對策:static volatile int counter 或 AtomicInteger。源碼編譯 → .class 文件
↓
類加載器載入 → Method Area (Metaspace)
├─ Class Metadata (方法、字段、註解...)
└─ Static Variables (分配內存、默認值、顯式初始化、static塊)
│
│ 全局唯一、共享、長生命週期
▼
引用類型 static 變量指向 Heap 中的對象
基本類型 static 變量直接存值
│
▼
實例創建 → Heap 分配對象
├─ Instance Variables (每個對象一份)
└─ 引用指向 Method Area 同一 static 變量
│
▼
實例銷毀 (GC) → Instance Variables 回收
Static Variables 依然存活(除非類卸載)
徹底理解 static 變量在「方法區分配、類初始化賦值、全局共享、生命週期綁定類」的完整鏈路,是排查記憶體洩漏、併發可見性異常、序列化陷阱、類加載器隔離問題的核心前提。建議結合 jmap -histo、jstat -gc、Arthas dashboard 實戰觀察元空間與堆內存變化,將理論內化為診斷直覺。