Wed, 30 Sep 2015 10:09:44 +0200
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) {