diff --git a/vespajlib/abi-spec.json b/vespajlib/abi-spec.json index a8b21461ef95..de020bf85471 100644 --- a/vespajlib/abi-spec.json +++ b/vespajlib/abi-spec.json @@ -932,6 +932,21 @@ ], "fields" : [ ] }, + "com.yahoo.tensor.Label" : { + "superClass" : "java.lang.Object", + "interfaces" : [ ], + "attributes" : [ + "public", + "interface", + "abstract" + ], + "methods" : [ + "public abstract long asNumeric()", + "public abstract java.lang.String asString()", + "public abstract boolean isEqualTo(com.yahoo.tensor.Label)" + ], + "fields" : [ ] + }, "com.yahoo.tensor.MappedTensor$Builder" : { "superClass" : "java.lang.Object", "interfaces" : [ @@ -1077,6 +1092,7 @@ ], "methods" : [ "public void (int)", + "public com.yahoo.tensor.PartialAddress$Builder add(java.lang.String, com.yahoo.tensor.Label)", "public com.yahoo.tensor.PartialAddress$Builder add(java.lang.String, long)", "public com.yahoo.tensor.PartialAddress$Builder add(java.lang.String, java.lang.String)", "public com.yahoo.tensor.PartialAddress build()" @@ -1091,8 +1107,10 @@ ], "methods" : [ "public java.lang.String dimension(int)", + "public com.yahoo.tensor.Label objectLabel(java.lang.String)", "public long numericLabel(java.lang.String)", "public java.lang.String label(java.lang.String)", + "public com.yahoo.tensor.Label objectLabel(int)", "public java.lang.String label(int)", "public int size()", "public com.yahoo.tensor.TensorAddress asAddress(com.yahoo.tensor.TensorType)", @@ -1281,6 +1299,7 @@ "public void (com.yahoo.tensor.TensorType)", "public com.yahoo.tensor.TensorAddress$Builder add(java.lang.String)", "public com.yahoo.tensor.TensorAddress$Builder add(java.lang.String, java.lang.String)", + "public com.yahoo.tensor.TensorAddress$Builder add(java.lang.String, com.yahoo.tensor.Label)", "public com.yahoo.tensor.TensorAddress$Builder add(java.lang.String, int)", "public com.yahoo.tensor.TensorAddress$Builder add(java.lang.String, long)", "public com.yahoo.tensor.TensorAddress$Builder copy()", @@ -1317,6 +1336,7 @@ "public static varargs com.yahoo.tensor.TensorAddress of(long[])", "public static varargs com.yahoo.tensor.TensorAddress of(int[])", "public abstract int size()", + "public abstract com.yahoo.tensor.Label objectLabel(int)", "public abstract java.lang.String label(int)", "public abstract long numericLabel(int)", "public abstract com.yahoo.tensor.TensorAddress withLabel(int, long)", @@ -3827,14 +3847,14 @@ "public static java.lang.String toMessageString(java.lang.Throwable)", "public static java.util.Optional findCause(java.lang.Throwable, java.lang.Class)", "public static void uncheck(com.yahoo.yolean.Exceptions$RunnableThrowingIOException)", - "public static void uncheckInterrupted(com.yahoo.yolean.Exceptions$RunnableThrowingInterruptedException)", - "public static void uncheckInterruptedAndRestoreFlag(com.yahoo.yolean.Exceptions$RunnableThrowingInterruptedException)", "public static varargs void uncheck(com.yahoo.yolean.Exceptions$RunnableThrowingIOException, java.lang.String, java.lang.String[])", "public static void uncheckAndIgnore(com.yahoo.yolean.Exceptions$RunnableThrowingIOException, java.lang.Class)", "public static java.util.function.Function uncheck(com.yahoo.yolean.Exceptions$FunctionThrowingIOException)", "public static java.lang.Object uncheck(com.yahoo.yolean.Exceptions$SupplierThrowingIOException)", "public static varargs java.lang.Object uncheck(com.yahoo.yolean.Exceptions$SupplierThrowingIOException, java.lang.String, java.lang.String[])", "public static java.lang.Object uncheckAndIgnore(com.yahoo.yolean.Exceptions$SupplierThrowingIOException, java.lang.Class)", + "public static void uncheckInterrupted(com.yahoo.yolean.Exceptions$RunnableThrowingInterruptedException)", + "public static void uncheckInterruptedAndRestoreFlag(com.yahoo.yolean.Exceptions$RunnableThrowingInterruptedException)", "public static java.lang.Object uncheckInterrupted(com.yahoo.yolean.Exceptions$SupplierThrowingInterruptedException)", "public static java.lang.RuntimeException throwUnchecked(java.lang.Throwable)" ], diff --git a/vespajlib/src/main/java/com/yahoo/tensor/Label.java b/vespajlib/src/main/java/com/yahoo/tensor/Label.java new file mode 100644 index 000000000000..724e6fbbdb2d --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/tensor/Label.java @@ -0,0 +1,16 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.tensor; + +/** + * A label for a tensor dimension. + * It handles both mapped dimensions with string labels and indexed dimensions with numeric labels. + * For mapped dimensions, a negative numeric label is assigned by LabelCache. + * For indexed dimension, the index itself is used as a positive numeric label. + * + * @author glebashnik + */ +public interface Label { + long asNumeric(); + String asString(); + boolean isEqualTo(Label label); +} diff --git a/vespajlib/src/main/java/com/yahoo/tensor/PartialAddress.java b/vespajlib/src/main/java/com/yahoo/tensor/PartialAddress.java index 8852bcd1ff3a..18fa1575a0a2 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/PartialAddress.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/PartialAddress.java @@ -1,7 +1,8 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.tensor; -import com.yahoo.tensor.impl.Label; +import com.yahoo.tensor.impl.LabelCache; +import com.yahoo.tensor.impl.TensorAddressAny; /** * An address to a subset of a tensors' cells, specifying a label for some, but not necessarily all, of the tensors @@ -18,7 +19,7 @@ public class PartialAddress { // Two arrays which contains corresponding dimension:label pairs. // The sizes of these are always equal. private final String[] dimensionNames; - private final long[] labels; + private final Label[] labels; private PartialAddress(Builder builder) { this.dimensionNames = builder.dimensionNames; @@ -31,31 +32,44 @@ public String dimension(int i) { return dimensionNames[i]; } - /** Returns the numeric label of this dimension, or -1 if no label is specified for it */ - public long numericLabel(String dimensionName) { + + /** Returns the label object of this dimension, or -1 if no label is specified for it */ + public Label objectLabel(String dimensionName) { for (int i = 0; i < dimensionNames.length; i++) if (dimensionNames[i].equals(dimensionName)) return labels[i]; - return Tensor.invalidIndex; + + return LabelCache.INVALID_INDEX_LABEL; + } + + /** Returns the numeric label of this dimension, or -1 if no label is specified for it */ + public long numericLabel(String dimensionName) { + return objectLabel(dimensionName).asNumeric(); } - /** Returns the label of this dimension, or null if no label is specified for it */ + /** Returns the string label of this dimension, or null if no label is specified for it */ public String label(String dimensionName) { - for (int i = 0; i < dimensionNames.length; i++) - if (dimensionNames[i].equals(dimensionName)) - return Label.fromNumber(labels[i]); - return null; + return objectLabel(dimensionName).asString(); } /** - * Returns the label at position i + * Returns label object at position i * * @throws IllegalArgumentException if i is out of bounds */ - public String label(int i) { + public Label objectLabel(int i) { if (i >= size()) throw new IllegalArgumentException("No label at position " + i + " in " + this); - return Label.fromNumber(labels[i]); + return labels[i]; + } + + /** + * Returns string label at position i + * + * @throws IllegalArgumentException if i is out of bounds + */ + public String label(int i) { + return objectLabel(i).asString(); } public int size() { return dimensionNames.length; } @@ -65,14 +79,14 @@ public String label(int i) { public TensorAddress asAddress(TensorType type) { if (type.rank() != size()) throw new IllegalArgumentException(type + " has a different rank than " + this); - long[] numericLabels = new long[labels.length]; + Label[] labels = new Label[this.labels.length]; for (int i = 0; i < type.dimensions().size(); i++) { - long label = numericLabel(type.dimensions().get(i).name()); - if (label == Tensor.invalidIndex) + Label label = objectLabel(type.dimensions().get(i).name()); + if (label.isEqualTo(LabelCache.INVALID_INDEX_LABEL)) throw new IllegalArgumentException(type + " dimension names does not match " + this); - numericLabels[i] = label; + labels[i] = label; } - return TensorAddress.of(numericLabels); + return TensorAddressAny.ofUnsafe(labels); } @Override @@ -88,24 +102,31 @@ public String toString() { public static class Builder { private String[] dimensionNames; - private long[] labels; + private Label[] labels; private int index = 0; public Builder(int size) { dimensionNames = new String[size]; - labels = new long[size]; + labels = new Label[size]; } - public Builder add(String dimensionName, long label) { + public Builder add(String dimensionName, Label label) { dimensionNames[index] = dimensionName; labels[index] = label; index++; return this; } + public Builder add(String dimensionName, long label) { + dimensionNames[index] = dimensionName; + labels[index] = LabelCache.GLOBAL.getOrCreateLabel(label); + index++; + return this; + } + public Builder add(String dimensionName, String label) { dimensionNames[index] = dimensionName; - labels[index] = Label.toNumber(label); + labels[index] = LabelCache.GLOBAL.getOrCreateLabel(label); index++; return this; } diff --git a/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java b/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java index 15c20e1a883f..1a22021a3321 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java @@ -20,7 +20,6 @@ import com.yahoo.tensor.functions.Softmax; import com.yahoo.tensor.functions.XwPlusB; import com.yahoo.tensor.functions.Expand; -import com.yahoo.tensor.impl.Label; import java.util.ArrayList; import java.util.Iterator; @@ -626,7 +625,8 @@ public CellBuilder label(String dimension, String label) { public TensorType type() { return tensorBuilder.type(); } public CellBuilder label(String dimension, long label) { - return label(dimension, Label.fromNumber(label)); + addressBuilder.add(dimension, label); + return this; } public Builder value(double cellValue) { diff --git a/vespajlib/src/main/java/com/yahoo/tensor/TensorAddress.java b/vespajlib/src/main/java/com/yahoo/tensor/TensorAddress.java index c96a8decd161..f1f97a25a2d5 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/TensorAddress.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/TensorAddress.java @@ -1,7 +1,7 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.tensor; -import com.yahoo.tensor.impl.Label; +import com.yahoo.tensor.impl.LabelCache; import com.yahoo.tensor.impl.TensorAddressAny; import java.util.Arrays; @@ -15,15 +15,15 @@ * @author bratseth */ public abstract class TensorAddress implements Comparable { - + public static TensorAddress of(String[] labels) { return TensorAddressAny.of(labels); } - + public static TensorAddress ofLabels(String... labels) { return TensorAddressAny.of(labels); } - + public static TensorAddress of(long... labels) { return TensorAddressAny.of(labels); } @@ -31,17 +31,24 @@ public static TensorAddress of(long... labels) { public static TensorAddress of(int... labels) { return TensorAddressAny.of(labels); } - + /** Returns the number of labels in this */ public abstract int size(); + /** + * Returns the i'th label object in this + * + * @throws IllegalArgumentException if there is no label at this index + */ + public abstract Label objectLabel(int i); + /** * Returns the i'th label in this * * @throws IllegalArgumentException if there is no label at this index */ public abstract String label(int i); - + /** * Returns the i'th label in this as a long. * Prefer this if you know that this is a numeric address, but not otherwise. @@ -49,7 +56,7 @@ public static TensorAddress of(int... labels) { * @throws IllegalArgumentException if there is no label at this index */ public abstract long numericLabel(int i); - + public abstract TensorAddress withLabel(int labelIndex, long label); public final boolean isEmpty() { return size() == 0; } @@ -58,7 +65,7 @@ public static TensorAddress of(int... labels) { public int compareTo(TensorAddress other) { // TODO: Formal issue (only): Ordering with different address sizes for (int i = 0; i < size(); i++) { - int elementComparison = this.label(i).compareTo(other.label(i)); + int elementComparison = this.label(i).compareTo(other.label(i)); // TODO: Faster with numeric labels? if (elementComparison != 0) return elementComparison; } return 0; @@ -104,25 +111,25 @@ public static String labelToString(String label) { /** Returns an address with only some of the dimension. Ordering will also be according to indexMap */ public TensorAddress partialCopy(int[] indexMap) { - long[] labels = new long[indexMap.length]; + Label[] labels = new Label[indexMap.length]; for (int i = 0; i < labels.length; ++i) { - labels[i] = numericLabel(indexMap[i]); + labels[i] = objectLabel(indexMap[i]); } return TensorAddressAny.ofUnsafe(labels); } /** Creates a complete address by taking the mapped dimensions of this and adding the indexed from the indexedPart */ public TensorAddress fullAddressOf(List dimensions, int[] indexedPart) { - long[] labels = new long[dimensions.size()]; + Label[] labels = new Label[dimensions.size()]; int mappedIndex = 0; int indexedIndex = 0; for (int i = 0; i < labels.length; i++) { TensorType.Dimension d = dimensions.get(i); if (d.isIndexed()) { - labels[i] = indexedPart[indexedIndex]; + labels[i] = LabelCache.GLOBAL.getOrCreateLabel(indexedPart[indexedIndex]); indexedIndex++; } else { - labels[i] = numericLabel(mappedIndex); + labels[i] = objectLabel(mappedIndex); mappedIndex++; } } @@ -143,7 +150,7 @@ public TensorAddress mappedPartialAddress(TensorType mappedType, List labels[out++] = leftOnly.numericLabel(a++); - case right -> labels[out++] = rightOnly.numericLabel(b++); - case both -> labels[out++] = match.numericLabel(m++); - case concat -> labels[out++] = concatDimIdx; + case left -> labels[out++] = leftOnly.objectLabel(a++); + case right -> labels[out++] = rightOnly.objectLabel(b++); + case both -> labels[out++] = match.objectLabel(m++); + case concat -> labels[out++] = LabelCache.GLOBAL.getOrCreateLabel(concatDimIdx); default -> throw new IllegalArgumentException("cannot handle: " + how); } } @@ -400,8 +402,8 @@ Tensor merge(CellVectorMapMap a, CellVectorMapMap b) { CellVectorMapMap decompose(Tensor input, SplitHow how) { var iter = input.cellIterator(); - long[] commonLabels = new long[(int)how.numCommon()]; - long[] separateLabels = new long[(int)how.numSeparate()]; + Label[] commonLabels = new Label[(int)how.numCommon()]; + Label[] separateLabels = new Label[(int)how.numSeparate()]; CellVectorMapMap result = new CellVectorMapMap(); while (iter.hasNext()) { var cell = iter.next(); @@ -411,8 +413,8 @@ CellVectorMapMap decompose(Tensor input, SplitHow how) { int separateIdx = 0; for (int i = 0; i < how.handleDims.size(); i++) { switch (how.handleDims.get(i)) { - case common -> commonLabels[commonIdx++] = addr.numericLabel(i); - case separate -> separateLabels[separateIdx++] = addr.numericLabel(i); + case common -> commonLabels[commonIdx++] = addr.objectLabel(i); + case separate -> separateLabels[separateIdx++] = addr.objectLabel(i); case concat -> ccDimIndex = addr.numericLabel(i); default -> throw new IllegalArgumentException("cannot handle: " + how.handleDims.get(i)); } diff --git a/vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java b/vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java index 9f6624752d11..c4215b32236b 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java @@ -12,6 +12,8 @@ import com.yahoo.tensor.evaluation.EvaluationContext; import com.yahoo.tensor.evaluation.Name; import com.yahoo.tensor.evaluation.TypeContext; +import com.yahoo.tensor.Label; +import com.yahoo.tensor.impl.LabelCache; import com.yahoo.tensor.impl.TensorAddressAny; import java.util.ArrayList; @@ -281,7 +283,7 @@ private static PartialAddress partialAddress(TensorType addressType, TensorAddre for (int i = 0; i < addressType.dimensions().size(); i++) { String dimension = addressType.dimensions().get(i).name(); if (retainDimensions.contains(dimension)) - builder.add(dimension, address.numericLabel(i)); + builder.add(dimension, address.objectLabel(i)); } return builder.build(); } @@ -376,8 +378,8 @@ static int[] mapIndexes(TensorType fromType, TensorType toType) { private static TensorAddress joinAddresses(TensorAddress a, int[] aToIndexes, TensorAddress b, int[] bToIndexes, TensorType joinedType) { - long[] joinedLabels = new long[joinedType.dimensions().size()]; - Arrays.fill(joinedLabels, Tensor.invalidIndex); + Label[] joinedLabels = new Label[joinedType.dimensions().size()]; + Arrays.fill(joinedLabels, LabelCache.INVALID_INDEX_LABEL); mapContent(a, joinedLabels, aToIndexes); boolean compatible = mapContent(b, joinedLabels, bToIndexes); if ( ! compatible) return null; @@ -390,11 +392,11 @@ private static TensorAddress joinAddresses(TensorAddress a, int[] aToIndexes, Te * @return true if the mapping was successful, false if one of the destination positions was * occupied by a different value */ - private static boolean mapContent(TensorAddress from, long[] to, int[] indexMap) { + private static boolean mapContent(TensorAddress from, Label[] to, int[] indexMap) { for (int i = 0, size = from.size(); i < size; i++) { int toIndex = indexMap[i]; - long label = from.numericLabel(i); - if (to[toIndex] != Tensor.invalidIndex && to[toIndex] != label) + Label label = from.objectLabel(i); + if (!to[toIndex].isEqualTo(LabelCache.INVALID_INDEX_LABEL) && !to[toIndex].isEqualTo(label)) return false; to[toIndex] = label; } diff --git a/vespajlib/src/main/java/com/yahoo/tensor/functions/MapSubspaces.java b/vespajlib/src/main/java/com/yahoo/tensor/functions/MapSubspaces.java index 93a101909a2f..b812dd81a4db 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/functions/MapSubspaces.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/functions/MapSubspaces.java @@ -97,9 +97,9 @@ public Tensor evaluate(EvaluationContext context) { for (int i = 0; i < inputType.dimensions().size(); i++) { var dim = inputType.dimensions().get(i); if (dim.isMapped()) { - mapAddrBuilder.add(dim.name(), fullAddr.numericLabel(i)); + mapAddrBuilder.add(dim.name(), fullAddr.objectLabel(i)); } else { - idxAddrBuilder.add(dim.name(), fullAddr.numericLabel(i)); + idxAddrBuilder.add(dim.name(), fullAddr.objectLabel(i)); } } var mapAddr = mapAddrBuilder.build(); @@ -122,11 +122,11 @@ public Tensor evaluate(EvaluationContext context) { var addrBuilder = new TensorAddress.Builder(outputType); for (int i = 0; i < inputTypeMapped.dimensions().size(); i++) { var dim = inputTypeMapped.dimensions().get(i); - addrBuilder.add(dim.name(), mappedAddr.numericLabel(i)); + addrBuilder.add(dim.name(), mappedAddr.objectLabel(i)); } for (int i = 0; i < denseOutputDims.size(); i++) { var dim = denseOutputDims.get(i); - addrBuilder.add(dim.name(), denseAddr.numericLabel(i)); + addrBuilder.add(dim.name(), denseAddr.objectLabel(i)); } builder.cell(addrBuilder.build(), cell.getValue()); } diff --git a/vespajlib/src/main/java/com/yahoo/tensor/functions/Slice.java b/vespajlib/src/main/java/com/yahoo/tensor/functions/Slice.java index 8f831611a0aa..85cf5c8f25a9 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/functions/Slice.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/functions/Slice.java @@ -2,6 +2,7 @@ package com.yahoo.tensor.functions; import com.yahoo.api.annotations.Beta; +import com.yahoo.tensor.Label; import com.yahoo.tensor.PartialAddress; import com.yahoo.tensor.Tensor; import com.yahoo.tensor.TensorAddress; @@ -118,8 +119,8 @@ private PartialAddress subspaceToAddress(TensorType type, EvaluationContext string2Enum = new ConcurrentHashMap<>(); - - // Index 0 is unused, that is a valid positive number - // 1(-1) is reserved for the Tensor.INVALID_INDEX - private static volatile String[] uniqueStrings = {"UNIQUE_UNUSED_MAGIC", "Tensor.INVALID_INDEX"}; - private static int numUniqeStrings = 2; - - private static String[] createSmallIndexesAsStrings(int count) { - String[] asStrings = new String[count]; - for (int i = 0; i < count; i++) { - asStrings[i] = String.valueOf(i); - } - return asStrings; - } - - private static long addNewUniqueString(String s) { - synchronized (string2Enum) { - if (numUniqeStrings >= uniqueStrings.length) { - uniqueStrings = Arrays.copyOf(uniqueStrings, uniqueStrings.length*2); - } - uniqueStrings[numUniqeStrings] = s; - return -numUniqeStrings++; - } - } - - private static String asNumericString(long index) { - return ((index >= 0) && (index < SMALL_INDEXES.length)) ? SMALL_INDEXES[(int)index] : String.valueOf(index); - } - - private static boolean validNumericIndex(String s) { - if (s.isEmpty() || ((s.length() > 1) && (s.charAt(0) == '0'))) return false; - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if ((c < '0') || (c > '9')) return false; - } - return true; - } - - public static long toNumber(String s) { - if (s == null) { return Tensor.invalidIndex; } - try { - if (validNumericIndex(s)) { - return Long.parseLong(s, 10); - } - } catch (NumberFormatException e) { - } - return string2Enum.computeIfAbsent(s, Label::addNewUniqueString); - } - - public static String fromNumber(long v) { - if (v >= 0) { - return asNumericString(v); - } else { - if (v == Tensor.invalidIndex) { return null; } - int index = -Convert.safe2Int(v); - return uniqueStrings[index]; - } - } - -} diff --git a/vespajlib/src/main/java/com/yahoo/tensor/impl/LabelCache.java b/vespajlib/src/main/java/com/yahoo/tensor/impl/LabelCache.java new file mode 100644 index 000000000000..abbab9271a2e --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/tensor/impl/LabelCache.java @@ -0,0 +1,196 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.tensor.impl; + +import com.google.common.util.concurrent.Striped; +import com.yahoo.tensor.Label; +import com.yahoo.tensor.Tensor; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; + +/** + * Cache for string labels so they can be mapped to unique numeric keys. + * It uses weak references for automatic cleanup of unused labels. + * + * @author baldersheim, glebashnik + */ +public class LabelCache { + // Global cache used as default. + public static final LabelCache GLOBAL = new LabelCache(32, 1000); + // Label for invalid index. + public static final Label INVALID_INDEX_LABEL = new LabelImpl(Tensor.invalidIndex, null); + + // Stores string and numeric keys to clean the cache after Label is garbage collected. + static class LabelWeakReference extends WeakReference