2222 */
2323package com .sun .jna ;
2424
25+ import java .lang .ref .ReferenceQueue ;
26+ import java .lang .ref .WeakReference ;
2527import 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
5149 * @see Pointer
5250 */
5351public 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
0 commit comments