8137333: Boundless soft caching of property map histories causes high memory pressure

Wed, 30 Sep 2015 10:09:44 +0200

author
attila
date
Wed, 30 Sep 2015 10:09:44 +0200
changeset 1554
c2147c431c29
parent 1553
dfa57c580b6a
child 1555
bf4599bb97b5

8137333: Boundless soft caching of property map histories causes high memory pressure
Reviewed-by: hannesw, sundar

src/jdk/nashorn/internal/runtime/PropertyMap.java file | annotate | diff | comparison | revisions
     1.1 --- a/src/jdk/nashorn/internal/runtime/PropertyMap.java	Mon Sep 28 18:58:52 2015 +0530
     1.2 +++ b/src/jdk/nashorn/internal/runtime/PropertyMap.java	Wed Sep 30 10:09:44 2015 +0200
     1.3 @@ -34,7 +34,9 @@
     1.4  import java.io.ObjectOutputStream;
     1.5  import java.io.Serializable;
     1.6  import java.lang.invoke.SwitchPoint;
     1.7 +import java.lang.ref.Reference;
     1.8  import java.lang.ref.SoftReference;
     1.9 +import java.lang.ref.WeakReference;
    1.10  import java.util.Arrays;
    1.11  import java.util.BitSet;
    1.12  import java.util.Collection;
    1.13 @@ -43,6 +45,7 @@
    1.14  import java.util.NoSuchElementException;
    1.15  import java.util.WeakHashMap;
    1.16  import java.util.concurrent.atomic.LongAdder;
    1.17 +import jdk.nashorn.internal.runtime.options.Options;
    1.18  import jdk.nashorn.internal.scripts.JO;
    1.19  
    1.20  /**
    1.21 @@ -55,6 +58,9 @@
    1.22   * will return a new map.
    1.23   */
    1.24  public class PropertyMap implements Iterable<Object>, Serializable {
    1.25 +    private static final int INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT =
    1.26 +            Math.max(0, Options.getIntProperty("nashorn.propertyMap.softReferenceDerivationLimit", 32));
    1.27 +
    1.28      /** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */
    1.29      private static final int NOT_EXTENSIBLE         = 0b0000_0001;
    1.30      /** Does this map contain valid array keys? */
    1.31 @@ -78,6 +84,13 @@
    1.32      /** Structure class name */
    1.33      private final String className;
    1.34  
    1.35 +    /**
    1.36 +     * Countdown of number of times this property map has been derived from another property map. When it
    1.37 +     * reaches zero, the property map will start using weak references instead of soft references to hold on
    1.38 +     * to its history elements.
    1.39 +     */
    1.40 +    private final int softReferenceDerivationLimit;
    1.41 +
    1.42      /** A reference to the expected shared prototype property map. If this is set this
    1.43       * property map should only be used if it the same as the actual prototype map. */
    1.44      private transient SharedPropertyMap sharedProtoMap;
    1.45 @@ -86,7 +99,7 @@
    1.46      private transient HashMap<String, SwitchPoint> protoSwitches;
    1.47  
    1.48      /** History of maps, used to limit map duplication. */
    1.49 -    private transient WeakHashMap<Property, SoftReference<PropertyMap>> history;
    1.50 +    private transient WeakHashMap<Property, Reference<PropertyMap>> history;
    1.51  
    1.52      /** History of prototypes, used to limit map duplication. */
    1.53      private transient WeakHashMap<ScriptObject, SoftReference<PropertyMap>> protoHistory;
    1.54 @@ -114,6 +127,7 @@
    1.55          this.fieldMaximum = fieldMaximum;
    1.56          this.spillLength  = spillLength;
    1.57          this.flags        = flags;
    1.58 +        this.softReferenceDerivationLimit = INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT;
    1.59  
    1.60          if (Context.DEBUG) {
    1.61              count.increment();
    1.62 @@ -126,7 +140,7 @@
    1.63       * @param propertyMap Existing property map.
    1.64       * @param properties  A {@link PropertyHashMap} with a new set of properties.
    1.65       */
    1.66 -    private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties, final int flags, final int fieldCount, final int spillLength) {
    1.67 +    private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties, final int flags, final int fieldCount, final int spillLength, final int softReferenceDerivationLimit) {
    1.68          this.properties   = properties;
    1.69          this.flags        = flags;
    1.70          this.spillLength  = spillLength;
    1.71 @@ -137,6 +151,7 @@
    1.72          this.listeners    = propertyMap.listeners;
    1.73          this.freeSlots    = propertyMap.freeSlots;
    1.74          this.sharedProtoMap = propertyMap.sharedProtoMap;
    1.75 +        this.softReferenceDerivationLimit = softReferenceDerivationLimit;
    1.76  
    1.77          if (Context.DEBUG) {
    1.78              count.increment();
    1.79 @@ -150,7 +165,7 @@
    1.80       * @param propertyMap Existing property map.
    1.81        */
    1.82      protected PropertyMap(final PropertyMap propertyMap) {
    1.83 -        this(propertyMap, propertyMap.properties, propertyMap.flags, propertyMap.fieldCount, propertyMap.spillLength);
    1.84 +        this(propertyMap, propertyMap.properties, propertyMap.flags, propertyMap.fieldCount, propertyMap.spillLength, propertyMap.softReferenceDerivationLimit);
    1.85      }
    1.86  
    1.87      private void writeObject(final ObjectOutputStream out) throws IOException {
    1.88 @@ -438,11 +453,7 @@
    1.89       */
    1.90      public final PropertyMap addPropertyNoHistory(final Property property) {
    1.91          propertyAdded(property, true);
    1.92 -        final PropertyHashMap newProperties = properties.immutableAdd(property);
    1.93 -        final PropertyMap newMap = new PropertyMap(this, newProperties, newFlags(property), newFieldCount(property), newSpillLength(property));
    1.94 -        newMap.updateFreeSlots(null, property);
    1.95 -
    1.96 -        return newMap;
    1.97 +        return addPropertyInternal(property);
    1.98      }
    1.99  
   1.100      /**
   1.101 @@ -457,15 +468,24 @@
   1.102          PropertyMap newMap = checkHistory(property);
   1.103  
   1.104          if (newMap == null) {
   1.105 -            final PropertyHashMap newProperties = properties.immutableAdd(property);
   1.106 -            newMap = new PropertyMap(this, newProperties, newFlags(property), newFieldCount(property), newSpillLength(property));
   1.107 -            newMap.updateFreeSlots(null, property);
   1.108 +            newMap = addPropertyInternal(property);
   1.109              addToHistory(property, newMap);
   1.110          }
   1.111  
   1.112          return newMap;
   1.113      }
   1.114  
   1.115 +    private PropertyMap deriveMap(final PropertyHashMap newProperties, final int newFlags, final int newFieldCount, final int newSpillLength) {
   1.116 +        return new PropertyMap(this, newProperties, newFlags, newFieldCount, newSpillLength, softReferenceDerivationLimit == 0 ? 0 : softReferenceDerivationLimit - 1);
   1.117 +    }
   1.118 +
   1.119 +    private PropertyMap addPropertyInternal(final Property property) {
   1.120 +        final PropertyHashMap newProperties = properties.immutableAdd(property);
   1.121 +        final PropertyMap newMap = deriveMap(newProperties, newFlags(property), newFieldCount(property), newSpillLength(property));
   1.122 +        newMap.updateFreeSlots(null, property);
   1.123 +        return newMap;
   1.124 +    }
   1.125 +
   1.126      /**
   1.127       * Remove a property from a map. Cloning or using an existing map if available.
   1.128       *
   1.129 @@ -485,13 +505,13 @@
   1.130              // If deleted property was last field or spill slot we can make it reusable by reducing field/slot count.
   1.131              // Otherwise mark it as free in free slots bitset.
   1.132              if (isSpill && slot >= 0 && slot == spillLength - 1) {
   1.133 -                newMap = new PropertyMap(this, newProperties, flags, fieldCount, spillLength - 1);
   1.134 +                newMap = deriveMap(newProperties, flags, fieldCount, spillLength - 1);
   1.135                  newMap.freeSlots = freeSlots;
   1.136              } else if (!isSpill && slot >= 0 && slot == fieldCount - 1) {
   1.137 -                newMap = new PropertyMap(this, newProperties, flags, fieldCount - 1, spillLength);
   1.138 +                newMap = deriveMap(newProperties, flags, fieldCount - 1, spillLength);
   1.139                  newMap.freeSlots = freeSlots;
   1.140              } else {
   1.141 -                newMap = new PropertyMap(this, newProperties, flags, fieldCount, spillLength);
   1.142 +                newMap = deriveMap(newProperties, flags, fieldCount, spillLength);
   1.143                  newMap.updateFreeSlots(property, null);
   1.144              }
   1.145              addToHistory(property, newMap);
   1.146 @@ -539,7 +559,7 @@
   1.147  
   1.148          // Add replaces existing property.
   1.149          final PropertyHashMap newProperties = properties.immutableReplace(oldProperty, newProperty);
   1.150 -        final PropertyMap newMap = new PropertyMap(this, newProperties, flags, fieldCount, newSpillLength);
   1.151 +        final PropertyMap newMap = deriveMap(newProperties, flags, fieldCount, newSpillLength);
   1.152  
   1.153          if (!sameType) {
   1.154              newMap.updateFreeSlots(oldProperty, newProperty);
   1.155 @@ -584,7 +604,7 @@
   1.156          final Property[] otherProperties = other.properties.getProperties();
   1.157          final PropertyHashMap newProperties = properties.immutableAdd(otherProperties);
   1.158  
   1.159 -        final PropertyMap newMap = new PropertyMap(this, newProperties, flags, fieldCount, spillLength);
   1.160 +        final PropertyMap newMap = deriveMap(newProperties, flags, fieldCount, spillLength);
   1.161          for (final Property property : otherProperties) {
   1.162              // This method is only safe to use with non-slotted, native getter/setter properties
   1.163              assert property.getSlot() == -1;
   1.164 @@ -618,7 +638,7 @@
   1.165       * @return New map with {@link #NOT_EXTENSIBLE} flag set.
   1.166       */
   1.167      PropertyMap preventExtensions() {
   1.168 -        return new PropertyMap(this, properties, flags | NOT_EXTENSIBLE, fieldCount, spillLength);
   1.169 +        return deriveMap(properties, flags | NOT_EXTENSIBLE, fieldCount, spillLength);
   1.170      }
   1.171  
   1.172      /**
   1.173 @@ -634,7 +654,7 @@
   1.174              newProperties = newProperties.immutableAdd(oldProperty.addFlags(Property.NOT_CONFIGURABLE));
   1.175          }
   1.176  
   1.177 -        return new PropertyMap(this, newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength);
   1.178 +        return deriveMap(newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength);
   1.179      }
   1.180  
   1.181      /**
   1.182 @@ -656,7 +676,7 @@
   1.183              newProperties = newProperties.immutableAdd(oldProperty.addFlags(propertyFlags));
   1.184          }
   1.185  
   1.186 -        return new PropertyMap(this, newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength);
   1.187 +        return deriveMap(newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength);
   1.188      }
   1.189  
   1.190      /**
   1.191 @@ -743,7 +763,7 @@
   1.192              history = new WeakHashMap<>();
   1.193          }
   1.194  
   1.195 -        history.put(property, new SoftReference<>(newMap));
   1.196 +        history.put(property, softReferenceDerivationLimit == 0 ? new WeakReference<>(newMap) : new SoftReference<>(newMap));
   1.197      }
   1.198  
   1.199      /**
   1.200 @@ -756,7 +776,7 @@
   1.201      private PropertyMap checkHistory(final Property property) {
   1.202  
   1.203          if (history != null) {
   1.204 -            final SoftReference<PropertyMap> ref = history.get(property);
   1.205 +            final Reference<PropertyMap> ref = history.get(property);
   1.206              final PropertyMap historicMap = ref == null ? null : ref.get();
   1.207  
   1.208              if (historicMap != null) {

mercurial