延續上文壓測,本文通過 Thread dump 與 JProfiler 數據,定位序列化性能崩潰的根因:java.io.ObjectStreamClass.processQueue() 內部調用 ReferenceQueue.poll() 時持有全局鎖,導致高併發下線程大面積阻塞。
"testthread21" RUNNABLE
at java.io.ObjectStreamClass.processQueue(Unknown Source)
at java.io.ObjectStreamClass.lookup(Unknown Source)
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
...
"testthread49" BLOCKED (waiting for monitor entry)
at java.lang.ref.ReferenceQueue.poll(Unknown Source)
- waiting to lock <0x02fb1d40> (a java.lang.ref.ReferenceQueue$Lock)
at java.io.ObjectStreamClass.processQueue(Unknown Source)
...
// ObjectStreamClass.processQueue
static void processQueue(ReferenceQueue<Class<?>> queue, ConcurrentMap<...> map) {
Reference<? extends Class<?>> ref;
while ((ref = queue.poll()) != null) { // ← poll() 內部 synchronized(lock)
map.remove(ref);
}
}
// ReferenceQueue.poll
public Reference<? extends T> poll() {
if (queueEmpty) return null;
synchronized (lock) { // ← 全局鎖
return reallyPoll(); // 極快
}
}
| 線程數 | lookup 累計時間佔比 | poll 方法耗時佔比 |
|---|---|---|
| 1 | 57.9% | 7.59% |
| 2 | 57.85% | 9.47% |
| 5 | 67.7% | 51.33% |
| 50 | 95.24% | 77.62% |
隨線程數增加,poll 等待鎖時間佔比從 7% 飆升至 77%,reallyPoll 本身極快(微秒級),但排隊等待將整體吞吐拉低。
ObjectStreamClass 內部 **全局同步鎖** 成為擴展性瓶頸,線程越多競爭越烈,吞吐反降。ObjectOutputStream(線程不安全需加鎖)、實現 Externalizable 手工控制寫入邏輯減少反射。ObjectStreamClass 使用 ConcurrentReferenceHashMap 降低鎖粒度,建議升級運行時。