blob: f8d7525e5bf7a567fa17a453c18f34a7a8e4260b [file] [log] [blame]
/*
* 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;
/**
* 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
*/
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;
// /**
// * 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) {
this.initialSize = Math.max(32, initialHeapSize >> 4); // won't shrink below this
id2fs = new JCasHashMap(initialSize);
}
/** put but assert wasn't there before */
void put(int id, TOP fs) {
TOP prev = id2fs.put(id, fs);
assert prev == null;
}
/**
* 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);
}
/**
* 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
}
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) {
return false;
}
if (!(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();
}
}