--- a/src/java.base/share/classes/java/util/Locale.java Thu Feb 08 10:06:57 2018 -0500
+++ b/src/java.base/share/classes/java/util/Locale.java Thu Feb 08 18:45:30 2018 +0100
@@ -808,17 +808,26 @@
}
static Locale getInstance(BaseLocale baseloc, LocaleExtensions extensions) {
- LocaleKey key = new LocaleKey(baseloc, extensions);
- return LOCALECACHE.get(key);
+ if (extensions == null) {
+ return LOCALECACHE.get(baseloc);
+ } else {
+ LocaleKey key = new LocaleKey(baseloc, extensions);
+ return LOCALECACHE.get(key);
+ }
}
- private static class Cache extends LocaleObjectCache<LocaleKey, Locale> {
+ private static class Cache extends LocaleObjectCache<Object, Locale> {
private Cache() {
}
@Override
- protected Locale createObject(LocaleKey key) {
- return new Locale(key.base, key.exts);
+ protected Locale createObject(Object key) {
+ if (key instanceof BaseLocale) {
+ return new Locale((BaseLocale)key, null);
+ } else {
+ LocaleKey lk = (LocaleKey)key;
+ return new Locale(lk.base, lk.exts);
+ }
}
}
--- a/src/java.base/share/classes/sun/util/locale/BaseLocale.java Thu Feb 08 10:06:57 2018 -0500
+++ b/src/java.base/share/classes/sun/util/locale/BaseLocale.java Thu Feb 08 18:45:30 2018 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -31,8 +31,8 @@
*/
package sun.util.locale;
+
import java.lang.ref.SoftReference;
-
import java.util.StringJoiner;
public final class BaseLocale {
@@ -48,26 +48,28 @@
private volatile int hash;
- // This method must be called only when creating the Locale.* constants.
- private BaseLocale(String language, String region) {
- this.language = language;
- this.script = "";
- this.region = region;
- this.variant = "";
- }
-
- private BaseLocale(String language, String script, String region, String variant) {
- this.language = (language != null) ? LocaleUtils.toLowerString(language).intern() : "";
- this.script = (script != null) ? LocaleUtils.toTitleString(script).intern() : "";
- this.region = (region != null) ? LocaleUtils.toUpperString(region).intern() : "";
- this.variant = (variant != null) ? variant.intern() : "";
+ // This method must be called with normalize = false only when creating the
+ // Locale.* constants and non-normalized BaseLocale$Keys used for lookup.
+ private BaseLocale(String language, String script, String region, String variant,
+ boolean normalize) {
+ if (normalize) {
+ this.language = LocaleUtils.toLowerString(language).intern();
+ this.script = LocaleUtils.toTitleString(script).intern();
+ this.region = LocaleUtils.toUpperString(region).intern();
+ this.variant = variant.intern();
+ } else {
+ this.language = language;
+ this.script = script;
+ this.region = region;
+ this.variant = variant;
+ }
}
// Called for creating the Locale.* constants. No argument
// validation is performed.
public static BaseLocale createInstance(String language, String region) {
- BaseLocale base = new BaseLocale(language, region);
- CACHE.put(new Key(language, region), base);
+ BaseLocale base = new BaseLocale(language, "", region, "", false);
+ CACHE.put(new Key(base), base);
return base;
}
@@ -84,7 +86,7 @@
}
}
- Key key = new Key(language, script, region, variant);
+ Key key = new Key(language, script, region, variant, false);
BaseLocale baseLocale = CACHE.get(key);
return baseLocale;
}
@@ -123,16 +125,16 @@
@Override
public String toString() {
StringJoiner sj = new StringJoiner(", ");
- if (language.length() > 0) {
+ if (!language.isEmpty()) {
sj.add("language=" + language);
}
- if (script.length() > 0) {
+ if (!script.isEmpty()) {
sj.add("script=" + script);
}
- if (region.length() > 0) {
+ if (!region.isEmpty()) {
sj.add("region=" + region);
}
- if (variant.length() > 0) {
+ if (!variant.isEmpty()) {
sj.add("variant=" + variant);
}
return sj.toString();
@@ -155,10 +157,17 @@
}
private static final class Key {
- private final SoftReference<String> lang;
- private final SoftReference<String> scrt;
- private final SoftReference<String> regn;
- private final SoftReference<String> vart;
+ /**
+ * Keep a SoftReference to the Key data if normalized (actually used
+ * as a cache key) and not initialized via the constant creation path.
+ *
+ * This allows us to avoid creating SoftReferences on lookup Keys
+ * (which are short-lived) and for Locales created via
+ * Locale#createConstant.
+ */
+ private final SoftReference<BaseLocale> holderRef;
+ private final BaseLocale holder;
+
private final boolean normalized;
private final int hash;
@@ -166,15 +175,16 @@
* Creates a Key. language and region must be normalized
* (intern'ed in the proper case).
*/
- private Key(String language, String region) {
- assert language.intern() == language
- && region.intern() == region;
-
- lang = new SoftReference<>(language);
- scrt = new SoftReference<>("");
- regn = new SoftReference<>(region);
- vart = new SoftReference<>("");
+ private Key(BaseLocale locale) {
+ this.holder = locale;
+ this.holderRef = null;
this.normalized = true;
+ String language = locale.getLanguage();
+ String region = locale.getRegion();
+ assert LocaleUtils.toLowerString(language).intern() == language
+ && LocaleUtils.toUpperString(region).intern() == region
+ && locale.getVariant() == ""
+ && locale.getScript() == "";
int h = language.hashCode();
if (region != "") {
@@ -186,51 +196,64 @@
hash = h;
}
- public Key(String language, String script, String region, String variant) {
- this(language, script, region, variant, false);
+ private Key(String language, String script, String region,
+ String variant, boolean normalize) {
+ if (language == null) {
+ language = "";
+ }
+ if (script == null) {
+ script = "";
+ }
+ if (region == null) {
+ region = "";
+ }
+ if (variant == null) {
+ variant = "";
+ }
+
+ BaseLocale locale = new BaseLocale(language, script, region, variant, normalize);
+ this.normalized = normalize;
+ if (normalized) {
+ this.holderRef = new SoftReference<>(locale);
+ this.holder = null;
+ } else {
+ this.holderRef = null;
+ this.holder = locale;
+ }
+ this.hash = hashCode(locale);
}
- private Key(String language, String script, String region,
- String variant, boolean normalized) {
+ public int hashCode() {
+ return hash;
+ }
+
+ private int hashCode(BaseLocale locale) {
int h = 0;
- if (language != null) {
- lang = new SoftReference<>(language);
- int len = language.length();
- for (int i = 0; i < len; i++) {
- h = 31*h + LocaleUtils.toLower(language.charAt(i));
- }
- } else {
- lang = new SoftReference<>("");
+ String lang = locale.getLanguage();
+ int len = lang.length();
+ for (int i = 0; i < len; i++) {
+ h = 31*h + LocaleUtils.toLower(lang.charAt(i));
}
- if (script != null) {
- scrt = new SoftReference<>(script);
- int len = script.length();
- for (int i = 0; i < len; i++) {
- h = 31*h + LocaleUtils.toLower(script.charAt(i));
- }
- } else {
- scrt = new SoftReference<>("");
+ String scrt = locale.getScript();
+ len = scrt.length();
+ for (int i = 0; i < len; i++) {
+ h = 31*h + LocaleUtils.toLower(scrt.charAt(i));
}
- if (region != null) {
- regn = new SoftReference<>(region);
- int len = region.length();
- for (int i = 0; i < len; i++) {
- h = 31*h + LocaleUtils.toLower(region.charAt(i));
- }
- } else {
- regn = new SoftReference<>("");
+ String regn = locale.getRegion();
+ len = regn.length();
+ for (int i = 0; i < len; i++) {
+ h = 31*h + LocaleUtils.toLower(regn.charAt(i));
}
- if (variant != null) {
- vart = new SoftReference<>(variant);
- int len = variant.length();
- for (int i = 0; i < len; i++) {
- h = 31*h + variant.charAt(i);
- }
- } else {
- vart = new SoftReference<>("");
+ String vart = locale.getVariant();
+ len = vart.length();
+ for (int i = 0; i < len; i++) {
+ h = 31*h + vart.charAt(i);
}
- hash = h;
- this.normalized = normalized;
+ return h;
+ }
+
+ private BaseLocale getBaseLocale() {
+ return (holder == null) ? holderRef.get() : holder;
}
@Override
@@ -238,46 +261,31 @@
if (this == obj) {
return true;
}
-
if (obj instanceof Key && this.hash == ((Key)obj).hash) {
- String tl = this.lang.get();
- String ol = ((Key)obj).lang.get();
- if (tl != null && ol != null &&
- LocaleUtils.caseIgnoreMatch(ol, tl)) {
- String ts = this.scrt.get();
- String os = ((Key)obj).scrt.get();
- if (ts != null && os != null &&
- LocaleUtils.caseIgnoreMatch(os, ts)) {
- String tr = this.regn.get();
- String or = ((Key)obj).regn.get();
- if (tr != null && or != null &&
- LocaleUtils.caseIgnoreMatch(or, tr)) {
- String tv = this.vart.get();
- String ov = ((Key)obj).vart.get();
- return (ov != null && ov.equals(tv));
- }
- }
+ BaseLocale other = ((Key) obj).getBaseLocale();
+ BaseLocale locale = this.getBaseLocale();
+ if (other != null && locale != null
+ && LocaleUtils.caseIgnoreMatch(other.getLanguage(), locale.getLanguage())
+ && LocaleUtils.caseIgnoreMatch(other.getScript(), locale.getScript())
+ && LocaleUtils.caseIgnoreMatch(other.getRegion(), locale.getRegion())
+ // variant is case sensitive in JDK!
+ && other.getVariant().equals(locale.getVariant())) {
+ return true;
}
}
return false;
}
- @Override
- public int hashCode() {
- return hash;
- }
-
public static Key normalize(Key key) {
if (key.normalized) {
return key;
}
- String lang = LocaleUtils.toLowerString(key.lang.get()).intern();
- String scrt = LocaleUtils.toTitleString(key.scrt.get()).intern();
- String regn = LocaleUtils.toUpperString(key.regn.get()).intern();
- String vart = key.vart.get().intern(); // preserve upper/lower cases
-
- return new Key(lang, scrt, regn, vart, true);
+ // Only normalized keys may be softly referencing the data holder
+ assert (key.holder != null && key.holderRef == null);
+ BaseLocale locale = key.holder;
+ return new Key(locale.getLanguage(), locale.getScript(),
+ locale.getRegion(), locale.getVariant(), true);
}
}
@@ -288,18 +296,12 @@
@Override
protected Key normalizeKey(Key key) {
- assert key.lang.get() != null &&
- key.scrt.get() != null &&
- key.regn.get() != null &&
- key.vart.get() != null;
-
return Key.normalize(key);
}
@Override
protected BaseLocale createObject(Key key) {
- return new BaseLocale(key.lang.get(), key.scrt.get(),
- key.regn.get(), key.vart.get());
+ return Key.normalize(key).getBaseLocale();
}
}
}
--- a/src/java.base/share/classes/sun/util/locale/LocaleObjectCache.java Thu Feb 08 10:06:57 2018 -0500
+++ b/src/java.base/share/classes/sun/util/locale/LocaleObjectCache.java Thu Feb 08 18:45:30 2018 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -37,8 +37,8 @@
import java.util.concurrent.ConcurrentMap;
public abstract class LocaleObjectCache<K, V> {
- private ConcurrentMap<K, CacheEntry<K, V>> map;
- private ReferenceQueue<V> queue = new ReferenceQueue<>();
+ private final ConcurrentMap<K, CacheEntry<K, V>> map;
+ private final ReferenceQueue<V> queue = new ReferenceQueue<>();
public LocaleObjectCache() {
this(16, 0.75f, 16);
@@ -57,17 +57,14 @@
value = entry.get();
}
if (value == null) {
+ key = normalizeKey(key);
V newVal = createObject(key);
- // make sure key is normalized *after* the object creation
- // so that newVal is assured to be created from a valid key.
- key = normalizeKey(key);
if (key == null || newVal == null) {
// subclass must return non-null key/value object
return null;
}
CacheEntry<K, V> newEntry = new CacheEntry<>(key, newVal, queue);
-
entry = map.putIfAbsent(key, newEntry);
if (entry == null) {
value = newVal;
@@ -92,7 +89,7 @@
private void cleanStaleEntries() {
CacheEntry<K, V> entry;
while ((entry = (CacheEntry<K, V>)queue.poll()) != null) {
- map.remove(entry.getKey());
+ map.remove(entry.getKey(), entry);
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/util/Locale/SoftKeys.java Thu Feb 08 18:45:30 2018 +0100
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/*
+ * @test
+ * @bug 8196869
+ * @summary Make sure we deal with internal Key data being cleared properly
+ * @run main/othervm -Xms16m -Xmx16m -esa SoftKeys
+ */
+import java.util.*;
+
+public class SoftKeys {
+
+ private static final char[] CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ public static void main(String[] args) {
+ // With 4 characters in "language", we'll fill up a 16M heap quickly,
+ // causing full GCs and SoftReference reclamation. Repeat at least two
+ // times to verify no NPEs appear when looking up Locale's whose
+ // softly referenced data in sun.util.locale.BaseLocale$Key might have
+ // been cleared.
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 512*1024; j++) {
+ new Locale(langForInt(j), "", "");
+ }
+ }
+ }
+
+ private static String langForInt(int val) {
+ StringBuilder buf = new StringBuilder(4);
+ buf.append(CHARS[(val >> 12) & 0xF]);
+ buf.append(CHARS[(val >> 8) & 0xF]);
+ buf.append(CHARS[(val >> 4) & 0xF]);
+ buf.append(CHARS[(val >> 0) & 0xF]);
+ return buf.toString();
+ }
+}
+