github.com/champo/mobile@v0.0.0-20190107162257-dc0771356504/bind/java/Seq.java (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package go; 6 7 import java.lang.ref.PhantomReference; 8 import java.lang.ref.Reference; 9 import java.lang.ref.ReferenceQueue; 10 import java.util.Arrays; 11 import java.util.Collection; 12 import java.util.Collections; 13 import java.util.IdentityHashMap; 14 import java.util.HashSet; 15 import java.util.Set; 16 import java.util.logging.Logger; 17 18 import go.Universe; 19 20 // Seq is a sequence of machine-dependent encoded values. 21 // Used by automatically generated language bindings to talk to Go. 22 public class Seq { 23 private static Logger log = Logger.getLogger("GoSeq"); 24 25 // also known to bind/seq/ref.go and bind/objc/seq_darwin.m 26 private static final int NULL_REFNUM = 41; 27 28 // use single Ref for null Object 29 public static final Ref nullRef = new Ref(NULL_REFNUM, null); 30 31 // The singleton GoRefQueue 32 private static final GoRefQueue goRefQueue = new GoRefQueue(); 33 34 static { 35 // Look for the shim class auto-generated by gomobile bind. 36 // Its only purpose is to call System.loadLibrary. 37 try { 38 Class loadJNI = Class.forName("go.LoadJNI"); 39 setContext(loadJNI.getDeclaredField("ctx").get(null)); 40 } catch (ClassNotFoundException e) { 41 // Ignore, assume the user will load JNI for it. 42 log.warning("LoadJNI class not found"); 43 } catch (NoSuchFieldException e) { 44 log.severe("LoadJNI class missing field: " + e); 45 } catch (IllegalAccessException e) { 46 log.severe("LoadJNI class bad field: " + e); 47 } 48 init(); 49 Universe.touch(); 50 } 51 52 private static native void init(); 53 54 // Empty method to run class initializer 55 public static void touch() {} 56 57 private Seq() { 58 } 59 60 // ctx is an android.context.Context. 61 static native void setContext(java.lang.Object ctx); 62 63 public static void incRefnum(int refnum) { 64 tracker.incRefnum(refnum); 65 } 66 67 // incRef increments the reference count of Java objects. 68 // For proxies for Go objects, it calls into the Proxy method 69 // incRefnum() to make sure the Go reference count is positive 70 // even if the Proxy is garbage collected and its Ref is finalized. 71 public static int incRef(Object o) { 72 return tracker.inc(o); 73 } 74 75 public static int incGoObjectRef(GoObject o) { 76 return o.incRefnum(); 77 } 78 79 // trackGoRef tracks a Go reference and decrements its refcount 80 // when the given GoObject wrapper is garbage collected. 81 // 82 // TODO(crawshaw): We could cut down allocations for frequently 83 // sent Go objects by maintaining a map to weak references. This 84 // however, would require allocating two objects per reference 85 // instead of one. It also introduces weak references, the bane 86 // of any Java debugging session. 87 // 88 // When we have real code, examine the tradeoffs. 89 public static void trackGoRef(int refnum, GoObject obj) { 90 if (refnum > 0) { 91 throw new RuntimeException("trackGoRef called with Java refnum " + refnum); 92 } 93 goRefQueue.track(refnum, obj); 94 } 95 96 public static Ref getRef(int refnum) { 97 return tracker.get(refnum); 98 } 99 100 // Increment the Go reference count before sending over a refnum. 101 // The ref parameter is only used to make sure the referenced 102 // object is not garbage collected before Go increments the 103 // count. It's the equivalent of Go's runtime.KeepAlive. 104 public static native void incGoRef(int refnum, GoObject ref); 105 106 // Informs the Go ref tracker that Java is done with this refnum. 107 static native void destroyRef(int refnum); 108 109 // decRef is called from seq.FinalizeRef 110 static void decRef(int refnum) { 111 tracker.dec(refnum); 112 } 113 114 // A GoObject is a Java class implemented in Go. When a GoObject 115 // is passed to Go, it is wrapped in a Go proxy, to make it behave 116 // the same as passing a regular Java class. 117 public interface GoObject { 118 // Increment refcount and return the refnum of the proxy. 119 // 120 // The Go reference count need to be bumped while the 121 // refnum is passed to Go, to avoid finalizing and 122 // invalidating it before being translated on the Go side. 123 int incRefnum(); 124 } 125 // A Proxy is a Java object that proxies a Go object. Proxies, unlike 126 // GoObjects, are unwrapped to their Go counterpart when deserialized 127 // in Go. 128 public interface Proxy extends GoObject {} 129 130 // A Ref represents an instance of a Java object passed back and forth 131 // across the language boundary. 132 public static final class Ref { 133 public final int refnum; 134 135 private int refcnt; // Track how many times sent to Go. 136 137 public final Object obj; // The referenced Java obj. 138 139 Ref(int refnum, Object o) { 140 if (refnum < 0) { 141 throw new RuntimeException("Ref instantiated with a Go refnum " + refnum); 142 } 143 this.refnum = refnum; 144 this.refcnt = 0; 145 this.obj = o; 146 } 147 148 void inc() { 149 // Count how many times this ref's Java object is passed to Go. 150 if (refcnt == Integer.MAX_VALUE) { 151 throw new RuntimeException("refnum " + refnum + " overflow"); 152 } 153 refcnt++; 154 } 155 } 156 157 static final RefTracker tracker = new RefTracker(); 158 159 static final class RefTracker { 160 private static final int REF_OFFSET = 42; 161 162 // Next Java object reference number. 163 // 164 // Reference numbers are positive for Java objects, 165 // and start, arbitrarily at a different offset to Go 166 // to make debugging by reading Seq hex a little easier. 167 private int next = REF_OFFSET; // next Java object ref 168 169 // Java objects that have been passed to Go. refnum -> Ref 170 // The Ref obj field is non-null. 171 // This map pins Java objects so they don't get GCed while the 172 // only reference to them is held by Go code. 173 private final RefMap javaObjs = new RefMap(); 174 175 // Java objects to refnum 176 private final IdentityHashMap<Object, Integer> javaRefs = new IdentityHashMap<>(); 177 178 // inc increments the reference count of a Java object when it 179 // is sent to Go. inc returns the refnum for the object. 180 synchronized int inc(Object o) { 181 if (o == null) { 182 return NULL_REFNUM; 183 } 184 if (o instanceof Proxy) { 185 return ((Proxy)o).incRefnum(); 186 } 187 Integer refnumObj = javaRefs.get(o); 188 if (refnumObj == null) { 189 if (next == Integer.MAX_VALUE) { 190 throw new RuntimeException("createRef overflow for " + o); 191 } 192 refnumObj = next++; 193 javaRefs.put(o, refnumObj); 194 } 195 int refnum = refnumObj; 196 Ref ref = javaObjs.get(refnum); 197 if (ref == null) { 198 ref = new Ref(refnum, o); 199 javaObjs.put(refnum, ref); 200 } 201 ref.inc(); 202 return refnum; 203 } 204 205 synchronized void incRefnum(int refnum) { 206 Ref ref = javaObjs.get(refnum); 207 if (ref == null) { 208 throw new RuntimeException("referenced Java object is not found: refnum="+refnum); 209 } 210 ref.inc(); 211 } 212 213 // dec decrements the reference count of a Java object when 214 // Go signals a corresponding proxy object is finalized. 215 // If the count reaches zero, the Java object is removed 216 // from the javaObjs map. 217 synchronized void dec(int refnum) { 218 if (refnum <= 0) { 219 // We don't keep track of the Go object. 220 // This must not happen. 221 log.severe("dec request for Go object "+ refnum); 222 return; 223 } 224 if (refnum == Seq.nullRef.refnum) { 225 return; 226 } 227 // Java objects are removed on request of Go. 228 Ref obj = javaObjs.get(refnum); 229 if (obj == null) { 230 throw new RuntimeException("referenced Java object is not found: refnum="+refnum); 231 } 232 obj.refcnt--; 233 if (obj.refcnt <= 0) { 234 javaObjs.remove(refnum); 235 javaRefs.remove(obj.obj); 236 } 237 } 238 239 // get returns an existing Ref to a Java object. 240 synchronized Ref get(int refnum) { 241 if (refnum < 0) { 242 throw new RuntimeException("ref called with Go refnum " + refnum); 243 } 244 if (refnum == NULL_REFNUM) { 245 return nullRef; 246 } 247 Ref ref = javaObjs.get(refnum); 248 if (ref == null) { 249 throw new RuntimeException("unknown java Ref: "+refnum); 250 } 251 return ref; 252 } 253 } 254 255 // GoRefQueue is a queue of GoRefs that are no longer live. An internal thread 256 // processes the queue and decrement the reference count on the Go side. 257 static class GoRefQueue extends ReferenceQueue<GoObject> { 258 // The set of tracked GoRefs. If we don't hold on to the GoRef instances, the Java GC 259 // will not add them to the queue when their referents are reclaimed. 260 private final Collection<GoRef> refs = Collections.synchronizedCollection(new HashSet<GoRef>()); 261 262 void track(int refnum, GoObject obj) { 263 refs.add(new GoRef(refnum, obj, this)); 264 } 265 266 GoRefQueue() { 267 Thread daemon = new Thread(new Runnable() { 268 @Override public void run() { 269 while (true) { 270 try { 271 GoRef ref = (GoRef)remove(); 272 refs.remove(ref); 273 destroyRef(ref.refnum); 274 ref.clear(); 275 } catch (InterruptedException e) { 276 // Ignore 277 } 278 } 279 } 280 }); 281 daemon.setDaemon(true); 282 daemon.setName("GoRefQueue Finalizer Thread"); 283 daemon.start(); 284 } 285 } 286 287 // A GoRef is a PhantomReference to a Java proxy for a Go object. 288 // GoRefs are enqueued to the singleton GoRefQueue when no longer live, 289 // so the corresponding reference count can be decremented. 290 static class GoRef extends PhantomReference<GoObject> { 291 final int refnum; 292 293 GoRef(int refnum, GoObject obj, GoRefQueue q) { 294 super(obj, q); 295 if (refnum > 0) { 296 throw new RuntimeException("GoRef instantiated with a Java refnum " + refnum); 297 } 298 this.refnum = refnum; 299 } 300 } 301 302 // RefMap is a mapping of integers to Ref objects. 303 // 304 // The integers can be sparse. In Go this would be a map[int]*Ref. 305 static final class RefMap { 306 private int next = 0; 307 private int live = 0; 308 private int[] keys = new int[16]; 309 private Ref[] objs = new Ref[16]; 310 311 RefMap() {} 312 313 Ref get(int key) { 314 int i = Arrays.binarySearch(keys, 0, next, key); 315 if (i >= 0) { 316 return objs[i]; 317 } 318 return null; 319 } 320 321 void remove(int key) { 322 int i = Arrays.binarySearch(keys, 0, next, key); 323 if (i >= 0) { 324 if (objs[i] != null) { 325 objs[i] = null; 326 live--; 327 } 328 } 329 } 330 331 void put(int key, Ref obj) { 332 if (obj == null) { 333 throw new RuntimeException("put a null ref (with key "+key+")"); 334 } 335 int i = Arrays.binarySearch(keys, 0, next, key); 336 if (i >= 0) { 337 if (objs[i] == null) { 338 objs[i] = obj; 339 live++; 340 } 341 if (objs[i] != obj) { 342 throw new RuntimeException("replacing an existing ref (with key "+key+")"); 343 } 344 return; 345 } 346 if (next >= keys.length) { 347 grow(); 348 i = Arrays.binarySearch(keys, 0, next, key); 349 } 350 i = ~i; 351 if (i < next) { 352 // Insert, shift everything afterwards down. 353 System.arraycopy(keys, i, keys, i+1, next-i); 354 System.arraycopy(objs, i, objs, i+1, next-i); 355 } 356 keys[i] = key; 357 objs[i] = obj; 358 live++; 359 next++; 360 } 361 362 private void grow() { 363 // Compact and (if necessary) grow backing store. 364 int[] newKeys; 365 Ref[] newObjs; 366 int len = 2*roundPow2(live); 367 if (len > keys.length) { 368 newKeys = new int[keys.length*2]; 369 newObjs = new Ref[objs.length*2]; 370 } else { 371 newKeys = keys; 372 newObjs = objs; 373 } 374 375 int j = 0; 376 for (int i = 0; i < keys.length; i++) { 377 if (objs[i] != null) { 378 newKeys[j] = keys[i]; 379 newObjs[j] = objs[i]; 380 j++; 381 } 382 } 383 for (int i = j; i < newKeys.length; i++) { 384 newKeys[i] = 0; 385 newObjs[i] = null; 386 } 387 388 keys = newKeys; 389 objs = newObjs; 390 next = j; 391 392 if (live != next) { 393 throw new RuntimeException("bad state: live="+live+", next="+next); 394 } 395 } 396 397 private static int roundPow2(int x) { 398 int p = 1; 399 while (p < x) { 400 p *= 2; 401 } 402 return p; 403 } 404 } 405 }