Skip to content

Commit e04e052

Browse files
committed
8239013: java.util.logging.Logger catalog cache keeps strong references to ResourceBundles
Changed the Logger catalog cache to use WeakReference<ResourceBundle> Reviewed-by: lancea, mchung, naoto
1 parent ce4e780 commit e04e052

5 files changed

Lines changed: 282 additions & 3 deletions

File tree

src/java.logging/share/classes/java/util/logging/Logger.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -447,7 +447,7 @@ ConfigurationData merge(Logger systemPeer) {
447447
private boolean anonymous;
448448

449449
// Cache to speed up behavior of findResourceBundle:
450-
private ResourceBundle catalog; // Cached resource bundle
450+
private WeakReference<ResourceBundle> catalogRef; // Cached resource bundle
451451
private String catalogName; // name associated with catalog
452452
private Locale catalogLocale; // locale associated with catalog
453453

@@ -2122,6 +2122,11 @@ public boolean getUseParentHandlers() {
21222122
return config.useParentHandlers;
21232123
}
21242124

2125+
private ResourceBundle catalog() {
2126+
WeakReference<ResourceBundle> ref = catalogRef;
2127+
return ref == null ? null : ref.get();
2128+
}
2129+
21252130
/**
21262131
* Private utility method to map a resource bundle name to an
21272132
* actual resource bundle, using a simple one-entry cache.
@@ -2161,13 +2166,14 @@ private synchronized ResourceBundle findResourceBundle(String name,
21612166

21622167
Locale currentLocale = Locale.getDefault();
21632168
final LoggerBundle lb = loggerBundle;
2169+
ResourceBundle catalog = catalog();
21642170

21652171
// Normally we should hit on our simple one entry cache.
21662172
if (lb.userBundle != null &&
21672173
name.equals(lb.resourceBundleName)) {
21682174
return lb.userBundle;
21692175
} else if (catalog != null && currentLocale.equals(catalogLocale)
2170-
&& name.equals(catalogName)) {
2176+
&& name.equals(catalogName)) {
21712177
return catalog;
21722178
}
21732179

@@ -2187,6 +2193,7 @@ private synchronized ResourceBundle findResourceBundle(String name,
21872193
try {
21882194
Module mod = cl.getUnnamedModule();
21892195
catalog = RbAccess.RB_ACCESS.getBundle(name, currentLocale, mod);
2196+
catalogRef = new WeakReference<>(catalog);
21902197
catalogName = name;
21912198
catalogLocale = currentLocale;
21922199
return catalog;
@@ -2214,6 +2221,7 @@ private synchronized ResourceBundle findResourceBundle(String name,
22142221
// with the module's loader this time.
22152222
catalog = ResourceBundle.getBundle(name, currentLocale,
22162223
moduleCL);
2224+
catalogRef = new WeakReference<>(catalog);
22172225
catalogName = name;
22182226
catalogLocale = currentLocale;
22192227
return catalog;
@@ -2231,6 +2239,7 @@ private synchronized ResourceBundle findResourceBundle(String name,
22312239
try {
22322240
// Use the caller's module
22332241
catalog = RbAccess.RB_ACCESS.getBundle(name, currentLocale, callerModule);
2242+
catalogRef = new WeakReference<>(catalog);
22342243
catalogName = name;
22352244
catalogLocale = currentLocale;
22362245
return catalog;
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import java.io.IOException;
25+
import java.lang.ref.PhantomReference;
26+
import java.lang.ref.Reference;
27+
import java.lang.ref.ReferenceQueue;
28+
import java.net.URL;
29+
import java.net.URLClassLoader;
30+
import java.nio.file.Files;
31+
import java.nio.file.Path;
32+
import java.nio.file.StandardCopyOption;
33+
import java.util.ArrayList;
34+
import java.util.List;
35+
import java.util.ResourceBundle;
36+
import java.util.concurrent.CopyOnWriteArrayList;
37+
import java.util.logging.Handler;
38+
import java.util.logging.LogRecord;
39+
import java.util.logging.Logger;
40+
41+
/**
42+
* @test
43+
* @bug 8239013
44+
* @summary This test verifies that the Logger cache does
45+
* not keep a strong reference on dynamically loaded resource
46+
* bundles
47+
* @build BundleTest MyBundle LoggingApp
48+
* @run main/othervm -Xmx64M -Djava.util.logging.config.file=logging.properties BundleTest
49+
*/
50+
public class BundleTest {
51+
52+
// My handler is used to get at the published LogRecords
53+
public static class MyHandler extends Handler {
54+
final List<LogRecord> records = new CopyOnWriteArrayList<>();
55+
@Override
56+
public void publish(LogRecord record) {
57+
records.add(record);
58+
}
59+
@Override
60+
public void flush() { }
61+
@Override
62+
public void close() throws SecurityException { }
63+
}
64+
65+
public static void main(String[] args) throws Exception {
66+
// copy classes and resource files
67+
List<URL> classes = setUp();
68+
69+
// create an URL class loader that contains a copy of
70+
// LoggingApp and MyBundle classes
71+
URLClassLoader loader = new URLClassLoader(classes.toArray(new URL[0]), null);
72+
Class<?> appClass = Class.forName(LoggingApp.class.getName(), true, loader);
73+
Class<?> bundleClass = Class.forName(MyBundle.class.getName(), true, loader);
74+
if (bundleClass.getClassLoader() != loader) {
75+
throw new AssertionError("bundleClass not loaded by URL loader");
76+
}
77+
78+
// Invoke LoggingApp.logger() which will create a logger
79+
// that uses a "MyBundle" bundle, and then log a message
80+
// to force the logger to load up an instance of the
81+
// MyBundle class in its catalog cache.
82+
Logger logger;
83+
ReferenceQueue<Object> queue = new ReferenceQueue<>();
84+
Reference<Object> loaderRef = new PhantomReference<>(loader, queue);
85+
MyHandler handler = new MyHandler();
86+
Thread currentThread = Thread.currentThread();
87+
ClassLoader context = currentThread.getContextClassLoader();
88+
currentThread.setContextClassLoader(loader);
89+
try {
90+
logger = (Logger) appClass.getMethod("logger").invoke(null);
91+
logger.addHandler(handler);
92+
logger.fine("foo");
93+
ResourceBundle rb = handler.records.get(0).getResourceBundle();
94+
// verify that the class of the resource bundle is the
95+
// class loaded earlier by 'loader'
96+
if (rb.getClass() != bundleClass) {
97+
throw new AssertionError("unexpected loader for: " + rb.getClass());
98+
}
99+
// At this point the logger has a reference to an instance
100+
// of the MyBundle class loaded by 'loader' in its catalog cache.
101+
// This is demonstrated by the presence of that bundle in the
102+
// LogRecord.
103+
} finally {
104+
currentThread.setContextClassLoader(context);
105+
}
106+
107+
// cleanup all things that might reference 'loader'
108+
appClass = bundleClass = null;
109+
loader = null;
110+
handler.records.clear();
111+
112+
// now try to trigger a full GC to force the cleanup
113+
// of soft caches. If the logger has a strong reference
114+
// to MyBundle, this will eventually cause an
115+
// OutOfMemoryError, and the test will fail.
116+
Reference<?> ref;
117+
System.gc();
118+
List<byte[]> memory = new ArrayList<>();
119+
boolean stop = false;
120+
System.out.println("Waiting for URL loader to be GC'ed");
121+
while ((ref = queue.remove(100)) == null) {
122+
if (stop) break;
123+
try {
124+
// eat memory to trigger cleaning of SoftReference
125+
memory.add(new byte[1024*1024]);
126+
System.out.printf("Total memory added: %s Mb%n", memory.size());
127+
} catch (OutOfMemoryError oome) {
128+
stop = true;
129+
}
130+
}
131+
if (stop) {
132+
System.out.println("no more memory...");
133+
}
134+
135+
// Verify that loader was GC'ed
136+
if (ref != loaderRef) {
137+
throw new AssertionError("Loader was not GC'ed");
138+
}
139+
System.out.println("Loader was GC'ed");
140+
Reference.reachabilityFence(logger);
141+
}
142+
143+
144+
static String file(Class<?> type) {
145+
return type.getSimpleName() + ".class";
146+
}
147+
148+
public static List<URL> setUp() throws IOException {
149+
String classes = System.getProperty("test.classes", "build");
150+
String cwd = System.getProperty("user.dir", ".");
151+
String sources = System.getProperty("test.src", "src");
152+
for (var type : List.of(LoggingApp.class, MyBundle.class)) {
153+
var from = Path.of(classes, file(type));
154+
var to = Path.of(cwd, file(type));
155+
Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING);
156+
}
157+
Files.copy(Path.of(sources, "logging.properties"),
158+
Path.of(cwd, "logging.properties"),
159+
StandardCopyOption.REPLACE_EXISTING);
160+
return List.of(Path.of(cwd).toUri().toURL());
161+
}
162+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import java.util.concurrent.locks.Lock;
25+
import java.util.concurrent.locks.ReentrantLock;
26+
import java.util.logging.Logger;
27+
28+
public class LoggingApp {
29+
private static final Lock lock = new ReentrantLock();
30+
private static volatile Logger logger;
31+
public static Logger logger() {
32+
Logger logger = LoggingApp.logger;
33+
if (logger != null) return logger;
34+
lock.lock();
35+
try {
36+
if ((logger = LoggingApp.logger) != null) return logger;
37+
LoggingApp.logger = logger =
38+
Logger.getLogger("com.example", "MyBundle");
39+
} finally {
40+
lock.unlock();
41+
}
42+
return logger;
43+
}
44+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
import java.util.Collections;
24+
import java.util.Enumeration;
25+
import java.util.ResourceBundle;
26+
import java.util.Map;
27+
import java.util.concurrent.ConcurrentHashMap;
28+
29+
public class MyBundle extends ResourceBundle {
30+
Map<String, String> mapping = new ConcurrentHashMap<>();
31+
32+
/**
33+
* Gets an object for the given key from this resource bundle.
34+
* Returns null if this resource bundle does not contain an
35+
* object for the given key.
36+
*
37+
* @param key the key for the desired object
38+
* @throws NullPointerException if {@code key} is {@code null}
39+
* @return the object for the given key, or null
40+
*/
41+
protected Object handleGetObject(String key) {
42+
return mapping.computeIfAbsent(key,
43+
(k) -> k + "-" + System.identityHashCode(this.getClass().getClassLoader()));
44+
}
45+
46+
/**
47+
* Returns an enumeration of the keys.
48+
*
49+
* @return an {@code Enumeration} of the keys contained in
50+
* this {@code ResourceBundle} and its parent bundles.
51+
*/
52+
public Enumeration<String> getKeys() {
53+
return Collections.enumeration(mapping.keySet());
54+
}
55+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
handlers= java.util.logging.ConsoleHandler
2+
.level= INFO
3+
4+
# Limit the message that are printed on the console to INFO and above.
5+
java.util.logging.ConsoleHandler.level = ALL
6+
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
7+
8+
com.example.level = ALL
9+
com.example.handlers = java.util.logging.ConsoleHandler

0 commit comments

Comments
 (0)