Why Does Pointer Collision Cause Concurrency Bugs in Java and How to Fix It?
Pointer collision is an efficient memory allocation technique that can trigger severe concurrency problems in multithreaded Java environments, and this article explains the underlying causes, illustrates conflict scenarios, and presents common remedies such as CAS (Compare‑And‑Swap) and TLAB (Thread‑Local Allocation Buffer) with sample code.
Pointer collision is an efficient memory allocation method used in memory compaction, but in multithreaded environments it can cause concurrency issues.
Causes of Concurrency Problems
In the pointer‑collision allocation mechanism, a boundary pointer separates used memory from unused memory. When a new object is allocated, the pointer moves forward by the object size. The read‑modify‑write operation on the pointer is not atomic, so multiple threads may read the same pointer value and allocate overlapping memory regions, leading to allocation errors and data corruption.
For example, if the boundary pointer is at address 100 and two threads (Thread A and Thread B) each request 10 units, both may read 100, move the pointer to 110, and both believe they have allocated the range 100‑109, causing a conflict.
Common Solutions
1. CAS (Compare‑And‑Swap)
CAS is an optimistic lock that uses three operands: the memory location (V), the expected old value (A), and the new value (B). A thread reads the current pointer as A, then attempts to update it to B. If V equals A, the update succeeds; otherwise, the thread retries.
class MemoryAllocator {
private AtomicInteger pointer; // CAS via AtomicInteger
public MemoryAllocator(int initialPointer) {
this.pointer = new AtomicInteger(initialPointer);
}
// Allocate memory
public int allocate(int size) {
while (true) {
int current = pointer.get(); // current pointer
int next = current + size; // new pointer
// Try to update pointer using CAS
if (pointer.compareAndSet(current, next)) {
return current; // allocation successful
}
// CAS failed, retry
}
}
}
public class PointerCollisionTest {
public static void main(String[] args) {
MemoryAllocator allocator = new MemoryAllocator(100);
// Create multiple threads for allocation
Thread thread1 = new Thread(() -> {
int address = allocator.allocate(10);
System.out.println("Thread 1 allocated memory at begin address: " + address);
});
Thread thread2 = new Thread(() -> {
int address = allocator.allocate(20);
System.out.println("Thread 2 allocated memory at begin address: " + address);
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}In the code above, AtomicInteger provides CAS support, and compareAndSet updates the pointer only when the expected value matches the current value.
2. TLAB (Thread‑Local Allocation Buffer)
TLAB is a thread‑private memory allocation buffer. Each thread pre‑allocates a small region in the Java heap as its own TLAB and attempts to allocate objects within it. Because TLAB is private, allocations inside it do not cause concurrency issues. When a TLAB runs out of space, the thread falls back to global heap allocation, where mechanisms like CAS can be used.
Using TLAB reduces contention between threads and improves allocation efficiency. The JVM enables TLAB by default, and it can be explicitly turned on with the -XX:+UseTLAB flag.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Xuanwu Backend Tech Stack
Primarily covers fundamental Java concepts, mainstream frameworks, deep dives into underlying principles, and JVM internals.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
