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