| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.uima.cas.impl; |
| |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.apache.uima.internal.util.Misc; |
| import org.apache.uima.jcas.cas.TOP; |
| import org.apache.uima.jcas.impl.JCasHashMap; |
| import org.apache.uima.util.IteratorNvc; |
| |
| // @formatter:off |
| /** |
| * A map from ints representing FS id's (or "addresses") to those FSs There is one map instance per |
| * CAS (all views). |
| * |
| * The map is not populated, normally. It is only populated when there is a need to |
| * be able to map from the id to the FS, or to prevent the FS from being GC'd |
| * -- The low level CAS apis for creating FSs have this need, because they return the id, and this |
| * won't prevent the FS from being GC'd. |
| * |
| * Removes not supported; they happen when the map is reset / cleared This corresponds to the v2 |
| * property of "once created, a FS cannot be reclaimed (until reset)" |
| * |
| * Threading: to support read-only views, concurrent with updates, needs to be thread safe |
| */ |
| // @formatter:on |
| public class Id2FS implements Iterable<TOP> { |
| |
| static final boolean MEASURE = false; |
| private static final int MEASURE_STACK_SIZE = 10; |
| private static Map<MeasureCaller, MeasureCaller> callers = MEASURE ? new HashMap<>() : null; |
| private static Map<MeasureCaller, MeasureCaller> walkableCallers = MEASURE ? new HashMap<>() |
| : null; |
| |
| private static final String REPORT_FS_PINNING = "uima.report.fs.pinning"; |
| private static final boolean IS_REPORT_PINNING; |
| private static int pinning_count; // ignoring multithreading issues, not critical |
| static { |
| String s = // ""; // debug |
| System.getProperty(REPORT_FS_PINNING); |
| IS_REPORT_PINNING = (s != null); |
| if (IS_REPORT_PINNING) { |
| pinning_count = (s.length() == 0) ? 10 : Integer.parseInt(s); |
| } |
| } |
| |
| // /** |
| // * Set this JVM property to true for backwards compatibility, where an application retains |
| // * some references to Feature Structures held only using the low-level references (which are |
| // ints).. |
| // */ |
| // public static final String DISABLE_FS_GC = "uima.disable_feature_structure_garbage_collection"; |
| // |
| // public static final boolean IS_DISABLE_FS_GC = // true || // disabled due to performance |
| // Misc.getNoValueSystemProperty(DISABLE_FS_GC); |
| |
| final private JCasHashMap id2fs; |
| final private int initialSize; |
| |
| public Id2FS(int initialHeapSize) { |
| initialSize = Math.max(32, initialHeapSize >> 4); // won't shrink below this |
| id2fs = new JCasHashMap(initialSize); |
| } |
| |
| private void maybeReport() { |
| if (!IS_REPORT_PINNING) { |
| return; |
| } |
| pinning_count--; |
| if (pinning_count < 0) { |
| return; |
| } |
| System.out.println("UIMA Report: FS pinning " + pinning_count + " occuring here:"); |
| new Throwable().printStackTrace(System.out); |
| } |
| |
| /** put but assert wasn't there before */ |
| void put(int id, TOP fs) { |
| TOP prev = id2fs.put(id, fs); |
| assert prev == null; |
| maybeReport(); |
| } |
| |
| /** |
| * Skips the assert that the item wasn't already present |
| * |
| * @param fs |
| * the fs to add |
| */ |
| void putUnconditionally(TOP fs) { |
| id2fs.put(fs._id, fs); |
| maybeReport(); |
| } |
| |
| /** |
| * make an id map to an fs, asserting there was a previous mapping for this id |
| * |
| * @param id |
| * - |
| * @param fs |
| * - |
| */ |
| void putChange(int id, TOP fs) { |
| TOP prev = id2fs.put(id, fs); |
| assert prev != null; // changing a preexisting value |
| maybeReport(); |
| } |
| |
| void put(TOP fs) { |
| put(fs._id, fs); |
| } |
| |
| TOP get(int id) { |
| return id2fs.get(id); |
| } |
| |
| // /** |
| // * @param fs - |
| // */ |
| // public void add(TOP fs) { |
| // id2fsw.add( |
| // null // experiment - hangs |
| //// IS_DISABLE_FS_GC |
| //// ? fs |
| //// : new WeakReference<TOP>(fs) |
| // ); |
| // maxSize ++; // tracked for computing shrinking upon clear() call |
| // } |
| |
| // public void setStrongRef(TOP fs, int i) { |
| // id2fsw.set(i, fs); |
| // } |
| |
| // public void replaceWithStrongRef(TOP fs) { |
| // if (IS_DISABLE_FS_GC) { |
| // return; |
| // } |
| // id2fsw.set(fs._id, fs); |
| // } |
| |
| int size() { |
| return id2fs.getApproximateSize(); |
| } |
| |
| /** |
| * adjusts the underlying array down in size if grew beyond the reset heap size value |
| */ |
| void clear() { |
| id2fs.clear(); |
| // disabled for now |
| // use common routine in Misc if re-enabling |
| |
| // secondTimeShrinkable = Misc.maybeShrink( |
| // secondTimeShrinkable, id2fsw.size(), Misc.nextHigherPowerOf2(maxSize), 2, initialSize, |
| // newCapacity -> { |
| // id2fsw = new ArrayList<>(newCapacity); |
| // }, |
| // () -> { |
| // id2fsw.clear(); |
| // }); |
| // id2fsw.add(null); // so that ids start at 1 |
| |
| // if (id2fsw.size() > (CASImpl.DEFAULT_RESET_HEAP_SIZE >> 4)) { |
| // id2fsw.clear(); |
| // id2fsw.add(null); // so that ids start at 1 |
| // id2fsw.trimToSize(); |
| // id2fsw.ensureCapacity(CASImpl.DEFAULT_INITIAL_HEAP_SIZE >> 4); |
| // } else { |
| // id2fsw.clear(); |
| // id2fsw.add(null); // so that ids start at 1 |
| // } |
| } |
| |
| // /** |
| // * plus means all reachable, plus maybe others not reachable but not yet gc'd |
| // * @param action |
| // */ |
| // void walkReachablePlusFSsSorted(Consumer<TOP> action) { |
| // walkReachablePlueFSsSorted(action, 1); |
| // } |
| |
| // /** |
| // * walk a part of the id2fsw list; for delta, just the part above the line |
| // * @param action |
| // * @param items the part of the id2fsw list to walk |
| // */ |
| // void walkReachablePlueFSsSorted(Consumer<TOP> action, int fromId) { |
| // |
| //// int i; |
| //// if (fromId == 1) { |
| //// i = fromId; |
| //// } else { |
| //// TOP holdkey = TOP.createSearchKey(fromId); // hold to kep from getting GC'd |
| //// WeakReference<TOP> key = new WeakReference<TOP>(holdkey); |
| //// i = Collections.binarySearch(id2fsw, key, new Comparator<WeakReference<TOP>>() { |
| //// @Override |
| //// public int compare(WeakReference<TOP> o1, WeakReference<TOP> o2) { |
| //// TOP k1 = o1.get(); |
| //// if (k1 == null) return -1; |
| //// return k1.compareTo(holdkey); |
| //// } |
| //// }); |
| //// |
| //// if (i < 0) { |
| //// i = -(i + 1); // i is (-(insertion point) - 1) |
| //// } |
| //// } |
| // // in this impl, the id is the index. |
| // if (MEASURE) { |
| // trace(walkableCallers); |
| // } |
| // |
| // final int sz = id2fs.size(); |
| // for (int i = fromId; i < sz; i++) { |
| // Object o = id2fs.get(i); |
| // if (o == null) { |
| // continue; |
| // } |
| // |
| // if (o instanceof TOP) { |
| // action.accept((TOP)o); |
| // } else { |
| // TOP fs = ((WeakReference<TOP>)o).get(); |
| // if (fs == null) { |
| //// id2fs.set(i, null); |
| // continue; |
| // } |
| // action.accept(fs); |
| // } |
| // } |
| // } |
| |
| void traceWeakGets() { |
| trace(callers); |
| } |
| |
| void trace(Map<MeasureCaller, MeasureCaller> map) { |
| synchronized (map) { |
| StackTraceElement[] e = Thread.currentThread().getStackTrace(); |
| MeasureCaller k = new MeasureCaller(); |
| for (int i = 3, j = 0; i < e.length; i++, j++) { |
| if (j >= MEASURE_STACK_SIZE) { |
| break; |
| } |
| k.className[j] = e[i].getClassName(); |
| k.methodName[j] = e[i].getMethodName(); |
| k.lineNumber[j] = e[i].getLineNumber(); |
| } |
| MeasureCaller prev = map.putIfAbsent(k, k); |
| if (null != prev) { |
| prev.count++; |
| } |
| } |
| } |
| |
| private static class MeasureCaller { |
| int count = 1; |
| String[] className = new String[MEASURE_STACK_SIZE]; |
| String[] methodName = new String[MEASURE_STACK_SIZE]; |
| int[] lineNumber = new int[MEASURE_STACK_SIZE]; |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.lang.Object#hashCode() |
| */ |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + Arrays.hashCode(className); |
| result = prime * result + Arrays.hashCode(lineNumber); |
| result = prime * result + Arrays.hashCode(methodName); |
| return result; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.lang.Object#equals(java.lang.Object) |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if ((obj == null) || !(obj instanceof MeasureCaller)) { |
| return false; |
| } |
| MeasureCaller other = (MeasureCaller) obj; |
| if (!Arrays.equals(className, other.className)) { |
| return false; |
| } |
| if (!Arrays.equals(lineNumber, other.lineNumber)) { |
| return false; |
| } |
| if (!Arrays.equals(methodName, other.methodName)) { |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| private static void dumpCallers(String title, Map<MeasureCaller, MeasureCaller> map) { |
| System.out.println(title + ": size:" + map.size()); |
| MeasureCaller[] a = map.keySet().toArray(new MeasureCaller[map.size()]); |
| Arrays.sort(a, (c1, c2) -> -Integer.compare(c1.count, c2.count)); |
| |
| for (MeasureCaller c : a) { |
| StringBuilder sb = new StringBuilder(); |
| |
| for (int i = 0; i < MEASURE_STACK_SIZE; i++) { |
| if (c.className[i] == null) { |
| break; |
| } |
| if (i != 0) { |
| sb.append(", "); |
| } |
| sb.append(Misc.formatcaller(c.className[i], c.methodName[i], c.lineNumber[i])); |
| } |
| |
| System.out.format("count: %,d, %s%n", c.count, sb); |
| } |
| } |
| |
| static { |
| if (MEASURE) { |
| Runtime.getRuntime().addShutdownHook(new Thread(null, () -> { |
| dumpCallers("Callers of getId with weak ref", callers); |
| dumpCallers("Callers of walkReachablePlueFSsSorted", walkableCallers); |
| }, "Dump id2fs weak")); |
| } |
| } |
| |
| @Override |
| public IteratorNvc<TOP> iterator() { |
| return id2fs.iterator(); |
| } |
| |
| } |