Skip to content

Commit 59b052a

Browse files
joerg1985matthiasblaesing
authored andcommitted
Reworked the way allocated memory is tracked
Replaced the WeakHashMap with a doubly linked list of WeakReferences to improve performance by reducing the time spent in synchronized blocks. A ReferenceQueue is used to dispose/unlink list entries as early as possible to avoid OOM exceptions under high load with low memory.
1 parent dc0c453 commit 59b052a

File tree

2 files changed

+160
-25
lines changed

2 files changed

+160
-25
lines changed

src/com/sun/jna/Memory.java

+152-14
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,10 @@
2222
*/
2323
package com.sun.jna;
2424

25+
import java.lang.ref.ReferenceQueue;
26+
import java.lang.ref.WeakReference;
2527
import java.nio.ByteBuffer;
26-
import java.util.Collection;
27-
import java.util.Collections;
28-
import java.util.LinkedList;
29-
import java.util.Map;
30-
import java.util.WeakHashMap;
28+
import java.util.ArrayList;
3129

3230
/**
3331
* A <code>Pointer</code> to memory obtained from the native heap via a
@@ -51,9 +49,82 @@
5149
* @see Pointer
5250
*/
5351
public class Memory extends Pointer {
54-
/** Keep track of all allocated memory so we can dispose of it before unloading. */
55-
private static final Map<Memory, Boolean> allocatedMemory =
56-
Collections.synchronizedMap(new WeakHashMap<Memory, Boolean>());
52+
53+
private static ReferenceQueue<Memory> QUEUE = new ReferenceQueue<Memory>();
54+
private static LinkedReference HEAD; // the head of the doubly linked list used for instance tracking
55+
56+
/**
57+
* Keep track of all allocated memory so we can dispose of it before
58+
* unloading. This is done using a doubly linked list to enable fast
59+
* removal of tracked instances.
60+
*/
61+
private static class LinkedReference extends WeakReference<Memory> {
62+
63+
private LinkedReference next;
64+
private LinkedReference prev;
65+
66+
private LinkedReference(Memory referent) {
67+
super(referent, QUEUE);
68+
}
69+
70+
/**
71+
* Add the given {@code instance} to the instance tracking.
72+
*
73+
* @param instance the instance to track
74+
*/
75+
static LinkedReference track(Memory instance) {
76+
// use a different lock here to allow the finialzier to unlink elements too
77+
synchronized (QUEUE) {
78+
LinkedReference stale;
79+
80+
// handle stale references here to avoid GC overheating when memory is limited
81+
while ((stale = (LinkedReference) QUEUE.poll()) != null) {
82+
stale.unlink();
83+
}
84+
}
85+
86+
// keep object allocation outside the syncronized block
87+
LinkedReference entry = new LinkedReference(instance);
88+
89+
synchronized (LinkedReference.class) {
90+
if (HEAD != null) {
91+
entry.next = HEAD;
92+
HEAD = HEAD.prev = entry;
93+
} else {
94+
HEAD = entry;
95+
}
96+
}
97+
98+
return entry;
99+
}
100+
101+
/**
102+
* Remove the related instance from tracking and update the linked list.
103+
*/
104+
private void unlink() {
105+
synchronized (LinkedReference.class) {
106+
LinkedReference next;
107+
108+
if (HEAD != this) {
109+
if (this.prev == null) {
110+
// this entry was detached before, e.g. disposeAll was called and finalizers are running now
111+
return;
112+
}
113+
114+
next = this.prev.next = this.next;
115+
} else {
116+
next = HEAD = HEAD.next;
117+
}
118+
119+
if (next != null) {
120+
next.prev = this.prev;
121+
}
122+
123+
// set prev to null to detect detached entries
124+
this.prev = null;
125+
}
126+
}
127+
}
57128

58129
private static final WeakMemoryHolder buffers = new WeakMemoryHolder();
59130

@@ -66,13 +137,71 @@ public static void purge() {
66137

67138
/** Dispose of all allocated memory. */
68139
public static void disposeAll() {
69-
// use a copy since dispose() modifies the map
70-
Collection<Memory> refs = new LinkedList<Memory>(allocatedMemory.keySet());
71-
for (Memory r : refs) {
72-
r.dispose();
140+
synchronized (LinkedReference.class) {
141+
LinkedReference entry;
142+
143+
while ((entry = HEAD) != null) {
144+
Memory memory = HEAD.get();
145+
146+
if (memory != null) {
147+
// dispose does the unlink call internal
148+
memory.dispose();
149+
} else {
150+
HEAD.unlink();
151+
}
152+
153+
if (HEAD == entry) {
154+
throw new IllegalStateException("the HEAD did not change");
155+
}
156+
}
157+
}
158+
159+
synchronized (QUEUE) {
160+
LinkedReference stale;
161+
162+
// try to release as mutch memory as possible
163+
while ((stale = (LinkedReference) QUEUE.poll()) != null) {
164+
stale.unlink();
165+
}
73166
}
74167
}
75168

169+
/**
170+
* Unit-testing only, ensure the doubly linked list is in a good shape.
171+
*
172+
* @return the number of tracked instances
173+
*/
174+
static int integrityCheck() {
175+
synchronized (LinkedReference.class) {
176+
if (HEAD == null) {
177+
return 0;
178+
}
179+
180+
ArrayList<LinkedReference> entries = new ArrayList<LinkedReference>();
181+
LinkedReference entry = HEAD;
182+
183+
while (entry != null) {
184+
entries.add(entry);
185+
entry = entry.next;
186+
}
187+
188+
int index = entries.size() - 1;
189+
entry = entries.get(index);
190+
191+
while (entry != null) {
192+
if (entries.get(index) != entry) {
193+
throw new IllegalStateException(entries.get(index) + " vs. " + entry + " at index " + index);
194+
}
195+
196+
entry = entry.prev;
197+
index--;
198+
}
199+
200+
return entries.size();
201+
}
202+
}
203+
204+
private final LinkedReference reference; // used to track the instance
76205
protected long size; // Size of the malloc'ed space
77206

78207
/** Provide a view into the original memory. Keeps an implicit reference
@@ -113,11 +242,13 @@ public Memory(long size) {
113242
if (peer == 0)
114243
throw new OutOfMemoryError("Cannot allocate " + size + " bytes");
115244

116-
allocatedMemory.put(this, Boolean.TRUE);
245+
reference = LinkedReference.track(this);
117246
}
118247

119248
protected Memory() {
120249
super();
250+
251+
reference = null;
121252
}
122253

123254
/** Provide a view of this memory using the given offset as the base address. The
@@ -182,11 +313,18 @@ protected void finalize() {
182313

183314
/** Free the native memory and set peer to zero */
184315
protected synchronized void dispose() {
316+
if (peer == 0) {
317+
// someone called dispose before, the finalizer will call dispose again
318+
return;
319+
}
320+
185321
try {
186322
free(peer);
187323
} finally {
188-
allocatedMemory.remove(this);
189324
peer = 0;
325+
// no null check here, tracking is only null for SharedMemory
326+
// SharedMemory is overriding the dispose method
327+
reference.unlink();
190328
}
191329
}
192330

test/com/sun/jna/MemoryTest.java

+8-11
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@
2525

2626
import java.lang.ref.Reference;
2727
import java.lang.ref.WeakReference;
28-
import java.lang.reflect.Field;
2928
import java.nio.ByteBuffer;
30-
import java.util.Map;
3129

3230
import junit.framework.TestCase;
3331

@@ -183,23 +181,22 @@ public void testDump() {
183181
"[0c0d0e]" + ls, m.dump());
184182
}
185183

186-
public void testRemoveAllocatedMemoryMap() throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
184+
public void testRemoveAllocatedMemory() {
187185
// Make sure there are no remaining allocations
188186
Memory.disposeAll();
189-
190-
// get a reference to the allocated memory
191-
Field allocatedMemoryField = Memory.class.getDeclaredField("allocatedMemory");
192-
allocatedMemoryField.setAccessible(true);
193-
Map<Memory, Reference<Memory>> allocatedMemory = (Map<Memory, Reference<Memory>>) allocatedMemoryField.get(null);
194-
assertEquals(0, allocatedMemory.size());
187+
assertEquals(0, Memory.integrityCheck());
195188

196189
// Test allocation and ensure it is accounted for
197190
Memory mem = new Memory(1024);
198-
assertEquals(1, allocatedMemory.size());
191+
assertEquals(1, Memory.integrityCheck());
192+
193+
// Test shared memory is not tracked
194+
Pointer shared = mem.share(0, 32);
195+
assertEquals(1, Memory.integrityCheck());
199196

200197
// Dispose memory and ensure allocation is removed from allocatedMemory-Map
201198
mem.dispose();
202-
assertEquals(0, allocatedMemory.size());
199+
assertEquals(0, Memory.integrityCheck());
203200
}
204201

205202
public static void main(String[] args) {

0 commit comments

Comments
 (0)