changeset 13110:445d56c343c7

8140364: JEP 264 Platform Logger API and Service Implementation Summary: Initial implementation for JEP 264 Platform Logger API and Service Reviewed-by: mchung, psandoz, rriggs
author dfuchs
date Fri, 20 Nov 2015 19:26:16 +0100
parents 957e4e29ff28
children 74bc089000c8 559b626b0117 f859879b02b9 94838afd5e5b a917e5045a38
files src/java.base/share/classes/java/lang/RuntimePermission.java src/java.base/share/classes/java/lang/System.java src/java.base/share/classes/jdk/internal/logger/AbstractLoggerWrapper.java src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java src/java.base/share/classes/jdk/internal/logger/DefaultLoggerFinder.java src/java.base/share/classes/jdk/internal/logger/LazyLoggers.java src/java.base/share/classes/jdk/internal/logger/LocalizedLoggerWrapper.java src/java.base/share/classes/jdk/internal/logger/LoggerFinderLoader.java src/java.base/share/classes/jdk/internal/logger/LoggerWrapper.java src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java src/java.base/share/classes/jdk/internal/logger/package-info.java src/java.base/share/classes/sun/util/logging/LoggingProxy.java src/java.base/share/classes/sun/util/logging/LoggingSupport.java src/java.base/share/classes/sun/util/logging/PlatformLogger.java src/java.desktop/share/classes/sun/font/FontUtilities.java src/java.logging/share/classes/META-INF/services/jdk.internal.logger.DefaultLoggerFinder src/java.logging/share/classes/java/util/logging/LogManager.java src/java.logging/share/classes/java/util/logging/LogRecord.java src/java.logging/share/classes/java/util/logging/Logger.java src/java.logging/share/classes/java/util/logging/LoggingProxyImpl.java src/java.logging/share/classes/java/util/logging/SimpleFormatter.java src/java.logging/share/classes/sun/util/logging/internal/LoggingProviderImpl.java src/java.logging/share/classes/sun/util/logging/internal/package-info.java src/java.management/share/classes/java/lang/management/DefaultPlatformMBeanProvider.java src/java.management/share/classes/sun/management/ManagementFactoryHelper.java test/java/lang/System/Logger/Level/LoggerLevelTest.java test/java/lang/System/Logger/custom/AccessSystemLogger.java test/java/lang/System/Logger/custom/CustomLoggerTest.java test/java/lang/System/Logger/custom/META-INF/services/java.lang.System$LoggerFinder test/java/lang/System/Logger/default/AccessSystemLogger.java test/java/lang/System/Logger/default/DefaultLoggerTest.java test/java/lang/System/Logger/interface/LoggerInterfaceTest.java test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/AccessSystemLogger.java test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinder.java test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinderTest.java test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/CustomSystemClassLoader.java test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/TestLoggerFinder.java test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/AccessSystemLogger.java test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/DefaultLoggerFinderTest.java test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/AccessSystemLogger.java test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/BaseDefaultLoggerFinderTest.java test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/CustomSystemClassLoader.java test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/BaseLoggerBridgeTest.java test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/CustomSystemClassLoader.java test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/BasePlatformLoggerTest.java test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/CustomSystemClassLoader.java test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/META-INF/services/java.lang.System$LoggerFinder test/java/lang/System/LoggerFinder/internal/BootstrapLogger/BootstrapLoggerTest.java test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/CustomSystemClassLoader.java test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/LoggerBridgeTest.java test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/AccessSystemLogger.java test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/CustomSystemClassLoader.java test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/LoggerFinderLoaderTest.java test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/META-INF/services/java.lang.System$LoggerFinder test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/CustomSystemClassLoader.java test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/PlatformLoggerBridgeTest.java test/java/lang/System/LoggerFinder/internal/api/LoggerFinderAPITest.java test/java/lang/System/LoggerFinder/internal/backend/LoggerFinderBackendTest.java test/java/lang/System/LoggerFinder/internal/backend/META-INF/services/java.lang.System$LoggerFinder test/java/lang/System/LoggerFinder/internal/backend/SystemClassLoader.java test/java/lang/System/LoggerFinder/jdk/DefaultLoggerBridgeTest/DefaultLoggerBridgeTest.java test/java/lang/System/LoggerFinder/jdk/DefaultPlatformLoggerTest/DefaultPlatformLoggerTest.java test/java/util/logging/LoggerSubclass.java test/sun/util/logging/PlatformLoggerTest.java
diffstat 69 files changed, 20108 insertions(+), 829 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/lang/RuntimePermission.java	Fri Nov 20 15:34:12 2015 +0100
+++ b/src/java.base/share/classes/java/lang/RuntimePermission.java	Fri Nov 20 19:26:16 2015 +0100
@@ -348,6 +348,19 @@
  *   {@code java.util.spi.LocaleServiceProvider}</a> for more
  *   information.</td>
  * </tr>
+ *
+ * <tr>
+ *   <td>loggerFinder</td>
+ *   <td>This {@code RuntimePermission} is required to be granted to
+ *   classes which subclass or call methods on
+ *   {@code java.lang.System.LoggerFinder}. The permission is
+ *   checked during invocation of the abstract base class constructor, as
+ *   well as on the invocation of its public methods.
+ *   This permission ensures trust in classes which provide loggers
+ *   to system classes.</td>
+ *   <td>See {@link java.lang.System.LoggerFinder java.lang.System.LoggerFinder}
+ *   for more information.</td>
+ * </tr>
  * </table>
  *
  * @implNote
--- a/src/java.base/share/classes/java/lang/System.java	Fri Nov 20 15:34:12 2015 +0100
+++ b/src/java.base/share/classes/java/lang/System.java	Fri Nov 20 19:26:16 2015 +0100
@@ -30,13 +30,14 @@
 import java.security.AccessControlContext;
 import java.util.Properties;
 import java.util.PropertyPermission;
-import java.util.StringTokenizer;
 import java.util.Map;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
-import java.security.AllPermission;
 import java.nio.channels.Channel;
 import java.nio.channels.spi.SelectorProvider;
+import java.util.Objects;
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
 import sun.nio.ch.Interruptible;
 import sun.reflect.CallerSensitive;
 import sun.reflect.Reflection;
@@ -45,6 +46,9 @@
 import jdk.internal.HotSpotIntrinsicCandidate;
 import jdk.internal.misc.JavaLangAccess;;
 import jdk.internal.misc.SharedSecrets;;
+import jdk.internal.logger.LoggerFinderLoader;
+import jdk.internal.logger.LazyLoggers;
+import jdk.internal.logger.LocalizedLoggerWrapper;
 
 /**
  * The <code>System</code> class contains several useful class fields
@@ -944,6 +948,648 @@
     }
 
     /**
+     * {@code System.Logger} instances log messages that will be
+     * routed to the underlying logging framework the {@link System.LoggerFinder
+     * LoggerFinder} uses.
+     * <p>
+     * {@code System.Logger} instances are typically obtained from
+     * the {@link java.lang.System System} class, by calling
+     * {@link java.lang.System#getLogger(java.lang.String) System.getLogger(loggerName)}
+     * or {@link java.lang.System#getLogger(java.lang.String, java.util.ResourceBundle)
+     * System.getLogger(loggerName, bundle)}.
+     *
+     * @see java.lang.System#getLogger(java.lang.String)
+     * @see java.lang.System#getLogger(java.lang.String, java.util.ResourceBundle)
+     * @see java.lang.System.LoggerFinder
+     *
+     * @since 9
+     *
+     */
+    public interface Logger {
+
+        /**
+         * System {@linkplain Logger loggers} levels.
+         * <p>
+         * A level has a {@linkplain #getName() name} and {@linkplain
+         * #getSeverity() severity}.
+         * Level values are {@link #ALL}, {@link #TRACE}, {@link #DEBUG},
+         * {@link #INFO}, {@link #WARNING}, {@link #ERROR}, {@link #OFF},
+         * by order of increasing severity.
+         * <br>
+         * {@link #ALL} and {@link #OFF}
+         * are simple markers with severities mapped respectively to
+         * {@link java.lang.Integer#MIN_VALUE Integer.MIN_VALUE} and
+         * {@link java.lang.Integer#MAX_VALUE Integer.MAX_VALUE}.
+         * <p>
+         * <b>Severity values and Mapping to {@code java.util.logging.Level}.</b>
+         * <p>
+         * {@linkplain System.Logger.Level System logger levels} are mapped to
+         * {@linkplain java.util.logging.Level  java.util.logging levels}
+         * of corresponding severity.
+         * <br>The mapping is as follows:
+         * <br><br>
+         * <table border="1">
+         * <caption>System.Logger Severity Level Mapping</caption>
+         * <tr><td><b>System.Logger Levels</b></td>
+         * <td>{@link Logger.Level#ALL ALL}</td>
+         * <td>{@link Logger.Level#TRACE TRACE}</td>
+         * <td>{@link Logger.Level#DEBUG DEBUG}</td>
+         * <td>{@link Logger.Level#INFO INFO}</td>
+         * <td>{@link Logger.Level#WARNING WARNING}</td>
+         * <td>{@link Logger.Level#ERROR ERROR}</td>
+         * <td>{@link Logger.Level#OFF OFF}</td>
+         * </tr>
+         * <tr><td><b>java.util.logging Levels</b></td>
+         * <td>{@link java.util.logging.Level#ALL ALL}</td>
+         * <td>{@link java.util.logging.Level#FINER FINER}</td>
+         * <td>{@link java.util.logging.Level#FINE FINE}</td>
+         * <td>{@link java.util.logging.Level#INFO INFO}</td>
+         * <td>{@link java.util.logging.Level#WARNING WARNING}</td>
+         * <td>{@link java.util.logging.Level#SEVERE SEVERE}</td>
+         * <td>{@link java.util.logging.Level#OFF OFF}</td>
+         * </tr>
+         * </table>
+         *
+         * @since 9
+         *
+         * @see java.lang.System.LoggerFinder
+         * @see java.lang.System.Logger
+         */
+        public enum Level {
+
+            // for convenience, we're reusing java.util.logging.Level int values
+            // the mapping logic in sun.util.logging.PlatformLogger depends
+            // on this.
+            /**
+             * A marker to indicate that all levels are enabled.
+             * This level {@linkplain #getSeverity() severity} is
+             * {@link Integer#MIN_VALUE}.
+             */
+            ALL(Integer.MIN_VALUE),  // typically mapped to/from j.u.l.Level.ALL
+            /**
+             * {@code TRACE} level: usually used to log diagnostic information.
+             * This level {@linkplain #getSeverity() severity} is
+             * {@code 400}.
+             */
+            TRACE(400),   // typically mapped to/from j.u.l.Level.FINER
+            /**
+             * {@code DEBUG} level: usually used to log debug information traces.
+             * This level {@linkplain #getSeverity() severity} is
+             * {@code 500}.
+             */
+            DEBUG(500),   // typically mapped to/from j.u.l.Level.FINEST/FINE/CONFIG
+            /**
+             * {@code INFO} level: usually used to log information messages.
+             * This level {@linkplain #getSeverity() severity} is
+             * {@code 800}.
+             */
+            INFO(800),    // typically mapped to/from j.u.l.Level.INFO
+            /**
+             * {@code WARNING} level: usually used to log warning messages.
+             * This level {@linkplain #getSeverity() severity} is
+             * {@code 900}.
+             */
+            WARNING(900), // typically mapped to/from j.u.l.Level.WARNING
+            /**
+             * {@code ERROR} level: usually used to log error messages.
+             * This level {@linkplain #getSeverity() severity} is
+             * {@code 1000}.
+             */
+            ERROR(1000),  // typically mapped to/from j.u.l.Level.SEVERE
+            /**
+             * A marker to indicate that all levels are disabled.
+             * This level {@linkplain #getSeverity() severity} is
+             * {@link Integer#MAX_VALUE}.
+             */
+            OFF(Integer.MAX_VALUE);  // typically mapped to/from j.u.l.Level.OFF
+
+            private final int severity;
+
+            private Level(int severity) {
+                this.severity = severity;
+            }
+
+            /**
+             * Returns the name of this level.
+             * @return this level {@linkplain #name()}.
+             */
+            public final String getName() {
+                return name();
+            }
+
+            /**
+             * Returns the severity of this level.
+             * A higher severity means a more severe condition.
+             * @return this level severity.
+             */
+            public final int getSeverity() {
+                return severity;
+            }
+        }
+
+        /**
+         * Returns the name of this logger.
+         *
+         * @return the logger name.
+         */
+        public String getName();
+
+        /**
+         * Checks if a message of the given level would be logged by
+         * this logger.
+         *
+         * @param level the log message level.
+         * @return {@code true} if the given log message level is currently
+         *         being logged.
+         *
+         * @throws NullPointerException if {@code level} is {@code null}.
+         */
+        public boolean isLoggable(Level level);
+
+        /**
+         * Logs a message.
+         *
+         * @implSpec The default implementation for this method calls
+         * {@code this.log(level, (ResourceBundle)null, msg, (Object[])null);}
+         *
+         * @param level the log message level.
+         * @param msg the string message (or a key in the message catalog, if
+         * this logger is a {@link
+         * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class)
+         * localized logger}); can be {@code null}.
+         *
+         * @throws NullPointerException if {@code level} is {@code null}.
+         */
+        public default void log(Level level, String msg) {
+            log(level, (ResourceBundle) null, msg, (Object[]) null);
+        }
+
+        /**
+         * Logs a lazily supplied message.
+         * <p>
+         * If the logger is currently enabled for the given log message level
+         * then a message is logged that is the result produced by the
+         * given supplier function.  Otherwise, the supplier is not operated on.
+         *
+         * @implSpec When logging is enabled for the given level, the default
+         * implementation for this method calls
+         * {@code this.log(level, (ResourceBundle)null, msgSupplier.get(), (Object[])null);}
+         *
+         * @param level the log message level.
+         * @param msgSupplier a supplier function that produces a message.
+         *
+         * @throws NullPointerException if {@code level} is {@code null},
+         *         or {@code msgSupplier} is {@code null}.
+         */
+        public default void log(Level level, Supplier<String> msgSupplier) {
+            Objects.requireNonNull(msgSupplier);
+            if (isLoggable(Objects.requireNonNull(level))) {
+                log(level, (ResourceBundle) null, msgSupplier.get(), (Object[]) null);
+            }
+        }
+
+        /**
+         * Logs a message produced from the given object.
+         * <p>
+         * If the logger is currently enabled for the given log message level then
+         * a message is logged that, by default, is the result produced from
+         * calling  toString on the given object.
+         * Otherwise, the object is not operated on.
+         *
+         * @implSpec When logging is enabled for the given level, the default
+         * implementation for this method calls
+         * {@code this.log(level, (ResourceBundle)null, obj.toString(), (Object[])null);}
+         *
+         * @param level the log message level.
+         * @param obj the object to log.
+         *
+         * @throws NullPointerException if {@code level} is {@code null}, or
+         *         {@code obj} is {@code null}.
+         */
+        public default void log(Level level, Object obj) {
+            Objects.requireNonNull(obj);
+            if (isLoggable(Objects.requireNonNull(level))) {
+                this.log(level, (ResourceBundle) null, obj.toString(), (Object[]) null);
+            }
+        }
+
+        /**
+         * Logs a message associated with a given throwable.
+         *
+         * @implSpec The default implementation for this method calls
+         * {@code this.log(level, (ResourceBundle)null, msg, thrown);}
+         *
+         * @param level the log message level.
+         * @param msg the string message (or a key in the message catalog, if
+         * this logger is a {@link
+         * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class)
+         * localized logger}); can be {@code null}.
+         * @param thrown a {@code Throwable} associated with the log message;
+         *        can be {@code null}.
+         *
+         * @throws NullPointerException if {@code level} is {@code null}.
+         */
+        public default void log(Level level, String msg, Throwable thrown) {
+            this.log(level, null, msg, thrown);
+        }
+
+        /**
+         * Logs a lazily supplied message associated with a given throwable.
+         * <p>
+         * If the logger is currently enabled for the given log message level
+         * then a message is logged that is the result produced by the
+         * given supplier function.  Otherwise, the supplier is not operated on.
+         *
+         * @implSpec When logging is enabled for the given level, the default
+         * implementation for this method calls
+         * {@code this.log(level, (ResourceBundle)null, msgSupplier.get(), thrown);}
+         *
+         * @param level one of the log message level identifiers.
+         * @param msgSupplier a supplier function that produces a message.
+         * @param thrown a {@code Throwable} associated with log message;
+         *               can be {@code null}.
+         *
+         * @throws NullPointerException if {@code level} is {@code null}, or
+         *                               {@code msgSupplier} is {@code null}.
+         */
+        public default void log(Level level, Supplier<String> msgSupplier,
+                Throwable thrown) {
+            Objects.requireNonNull(msgSupplier);
+            if (isLoggable(Objects.requireNonNull(level))) {
+                this.log(level, null, msgSupplier.get(), thrown);
+            }
+        }
+
+        /**
+         * Logs a message with an optional list of parameters.
+         *
+         * @implSpec The default implementation for this method calls
+         * {@code this.log(level, (ResourceBundle)null, format, params);}
+         *
+         * @param level one of the log message level identifiers.
+         * @param format the string message format in {@link
+         * java.text.MessageFormat} format, (or a key in the message
+         * catalog, if this logger is a {@link
+         * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class)
+         * localized logger}); can be {@code null}.
+         * @param params an optional list of parameters to the message (may be
+         * none).
+         *
+         * @throws NullPointerException if {@code level} is {@code null}.
+         */
+        public default void log(Level level, String format, Object... params) {
+            this.log(level, null, format, params);
+        }
+
+        /**
+         * Logs a localized message associated with a given throwable.
+         * <p>
+         * If the given resource bundle is non-{@code null},  the {@code msg}
+         * string is localized using the given resource bundle.
+         * Otherwise the {@code msg} string is not localized.
+         *
+         * @param level the log message level.
+         * @param bundle a resource bundle to localize {@code msg}; can be
+         * {@code null}.
+         * @param msg the string message (or a key in the message catalog,
+         *            if {@code bundle} is not {@code null}); can be {@code null}.
+         * @param thrown a {@code Throwable} associated with the log message;
+         *        can be {@code null}.
+         *
+         * @throws NullPointerException if {@code level} is {@code null}.
+         */
+        public void log(Level level, ResourceBundle bundle, String msg,
+                Throwable thrown);
+
+        /**
+         * Logs a message with resource bundle and an optional list of
+         * parameters.
+         * <p>
+         * If the given resource bundle is non-{@code null},  the {@code format}
+         * string is localized using the given resource bundle.
+         * Otherwise the {@code format} string is not localized.
+         *
+         * @param level the log message level.
+         * @param bundle a resource bundle to localize {@code format}; can be
+         * {@code null}.
+         * @param format the string message format in {@link
+         * java.text.MessageFormat} format, (or a key in the message
+         * catalog if {@code bundle} is not {@code null}); can be {@code null}.
+         * @param params an optional list of parameters to the message (may be
+         * none).
+         *
+         * @throws NullPointerException if {@code level} is {@code null}.
+         */
+        public void log(Level level, ResourceBundle bundle, String format,
+                Object... params);
+
+
+    }
+
+    /**
+     * The {@code LoggerFinder} service is responsible for creating, managing,
+     * and configuring loggers to the underlying framework it uses.
+     * <p>
+     * A logger finder is a concrete implementation of this class that has a
+     * zero-argument constructor and implements the abstract methods defined
+     * by this class.
+     * The loggers returned from a logger finder are capable of routing log
+     * messages to the logging backend this provider supports.
+     * A given invocation of the Java Runtime maintains a single
+     * system-wide LoggerFinder instance that is loaded as follows:
+     * <ul>
+     *    <li>First it finds any custom {@code LoggerFinder} provider
+     *        using the {@link java.util.ServiceLoader} facility with the
+     *        {@linkplain ClassLoader#getSystemClassLoader() system class
+     *        loader}.</li>
+     *    <li>If no {@code LoggerFinder} provider is found, the system default
+     *        {@code LoggerFinder} implementation will be used.</li>
+     * </ul>
+     * <p>
+     * An application can replace the logging backend
+     * <i>even when the java.logging module is present</i>, by simply providing
+     * and declaring an implementation of the {@link LoggerFinder} service.
+     * <p>
+     * <b>Default Implementation</b>
+     * <p>
+     * The system default {@code LoggerFinder} implementation uses
+     * {@code java.util.logging} as the backend framework when the
+     * {@code java.logging} module is present.
+     * It returns a {@linkplain System.Logger logger} instance
+     * that will route log messages to a {@link java.util.logging.Logger
+     * java.util.logging.Logger}. Otherwise, if {@code java.logging} is not
+     * present, the default implementation will return a simple logger
+     * instance that will route log messages of {@code INFO} level and above to
+     * the console ({@code System.err}).
+     * <p>
+     * <b>Logging Configuration</b>
+     * <p>
+     * {@linkplain Logger Logger} instances obtained from the
+     * {@code LoggerFinder} factory methods are not directly configurable by
+     * the application. Configuration is the responsibility of the underlying
+     * logging backend, and usually requires using APIs specific to that backend.
+     * <p>For the default {@code LoggerFinder} implementation
+     * using {@code java.util.logging} as its backend, refer to
+     * {@link java.util.logging java.util.logging} for logging configuration.
+     * For the default {@code LoggerFinder} implementation returning simple loggers
+     * when the {@code java.logging} module is absent, the configuration
+     * is implementation dependent.
+     * <p>
+     * Usually an application that uses a logging framework will log messages
+     * through a logger facade defined (or supported) by that framework.
+     * Applications that wish to use an external framework should log
+     * through the facade associated with that framework.
+     * <p>
+     * A system class that needs to log messages will typically obtain
+     * a {@link System.Logger} instance to route messages to the logging
+     * framework selected by the application.
+     * <p>
+     * Libraries and classes that only need loggers to produce log messages
+     * should not attempt to configure loggers by themselves, as that
+     * would make them dependent from a specific implementation of the
+     * {@code LoggerFinder} service.
+     * <p>
+     * In addition, when a security manager is present, loggers provided to
+     * system classes should not be directly configurable through the logging
+     * backend without requiring permissions.
+     * <br>
+     * It is the responsibility of the provider of
+     * the concrete {@code LoggerFinder} implementation to ensure that
+     * these loggers are not configured by untrusted code without proper
+     * permission checks, as configuration performed on such loggers usually
+     * affects all applications in the same Java Runtime.
+     * <p>
+     * <b>Message Levels and Mapping to backend levels</b>
+     * <p>
+     * A logger finder is responsible for mapping from a {@code
+     * System.Logger.Level} to a level supported by the logging backend it uses.
+     * <br>The default LoggerFinder using {@code java.util.logging} as the backend
+     * maps {@code System.Logger} levels to
+     * {@linkplain java.util.logging.Level java.util.logging} levels
+     * of corresponding severity - as described in {@link Logger.Level
+     * Logger.Level}.
+     *
+     * @see java.lang.System
+     * @see java.lang.System.Logger
+     *
+     * @since 9
+     */
+    public static abstract class LoggerFinder {
+        /**
+         * The {@code RuntimePermission("loggerFinder")} is
+         * necessary to subclass and instantiate the {@code LoggerFinder} class,
+         * as well as to obtain loggers from an instance of that class.
+         */
+        static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+
+        /**
+         * Creates a new instance of {@code LoggerFinder}.
+         *
+         * @implNote It is recommended that a {@code LoggerFinder} service
+         *   implementation does not perform any heavy initialization in its
+         *   constructor, in order to avoid possible risks of deadlock or class
+         *   loading cycles during the instantiation of the service provider.
+         *
+         * @throws SecurityException if a security manager is present and its
+         *         {@code checkPermission} method doesn't allow the
+         *         {@code RuntimePermission("loggerFinder")}.
+         */
+        protected LoggerFinder() {
+            this(checkPermission());
+        }
+
+        private LoggerFinder(Void unused) {
+            // nothing to do.
+        }
+
+        private static Void checkPermission() {
+            final SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(LOGGERFINDER_PERMISSION);
+            }
+            return null;
+        }
+
+        /**
+         * Returns an instance of {@link Logger Logger}
+         * for the given {@code caller}.
+         *
+         * @param name the name of the logger.
+         * @param caller the class for which the logger is being requested;
+         *               can be {@code null}.
+         *
+         * @return a {@link Logger logger} suitable for the given caller's
+         *         use.
+         * @throws NullPointerException if {@code name} is {@code null} or
+         *        {@code caller} is {@code null}.
+         * @throws SecurityException if a security manager is present and its
+         *         {@code checkPermission} method doesn't allow the
+         *         {@code RuntimePermission("loggerFinder")}.
+         */
+        public abstract Logger getLogger(String name, /* Module */ Class<?> caller);
+
+        /**
+         * Returns a localizable instance of {@link Logger Logger}
+         * for the given {@code caller}.
+         * The returned logger will use the provided resource bundle for
+         * message localization.
+         *
+         * @implSpec By default, this method calls {@link
+         * #getLogger(java.lang.String, java.lang.Class)
+         * this.getLogger(name, caller)} to obtain a logger, then wraps that
+         * logger in a {@link Logger} instance where all methods that do not
+         * take a {@link ResourceBundle} as parameter are redirected to one
+         * which does - passing the given {@code bundle} for
+         * localization. So for instance, a call to {@link
+         * Logger#log(Level, String) Logger.log(Level.INFO, msg)}
+         * will end up as a call to {@link
+         * Logger#log(Level, ResourceBundle, String, Object...)
+         * Logger.log(Level.INFO, bundle, msg, (Object[])null)} on the wrapped
+         * logger instance.
+         * Note however that by default, string messages returned by {@link
+         * java.util.function.Supplier Supplier&lt;String&gt;} will not be
+         * localized, as it is assumed that such strings are messages which are
+         * already constructed, rather than keys in a resource bundle.
+         * <p>
+         * An implementation of {@code LoggerFinder} may override this method,
+         * for example, when the underlying logging backend provides its own
+         * mechanism for localizing log messages, then such a
+         * {@code LoggerFinder} would be free to return a logger
+         * that makes direct use of the mechanism provided by the backend.
+         *
+         * @param name    the name of the logger.
+         * @param bundle  a resource bundle; can be {@code null}.
+         * @param caller the class for which the logger is being requested.
+         * @return an instance of {@link Logger Logger}  which will use the
+         * provided resource bundle for message localization.
+         *
+         * @throws NullPointerException if {@code name} is {@code null} or
+         *         {@code caller} is {@code null}.
+         * @throws SecurityException if a security manager is present and its
+         *         {@code checkPermission} method doesn't allow the
+         *         {@code RuntimePermission("loggerFinder")}.
+         */
+        public Logger getLocalizedLogger(String name, ResourceBundle bundle,
+                                          /* Module */ Class<?> caller) {
+            return new LocalizedLoggerWrapper<>(getLogger(name, caller), bundle);
+        }
+
+        /**
+         * Returns the {@code LoggerFinder} instance. There is one
+         * single system-wide {@code LoggerFinder} instance in
+         * the Java Runtime.  See the class specification of how the
+         * {@link LoggerFinder LoggerFinder} implementation is located and
+         * loaded.
+
+         * @return the {@link LoggerFinder LoggerFinder} instance.
+         * @throws SecurityException if a security manager is present and its
+         *         {@code checkPermission} method doesn't allow the
+         *         {@code RuntimePermission("loggerFinder")}.
+         */
+        public static LoggerFinder getLoggerFinder() {
+            final SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(LOGGERFINDER_PERMISSION);
+            }
+            return accessProvider();
+        }
+
+
+        private static volatile LoggerFinder service;
+        static LoggerFinder accessProvider() {
+            // We do not need to synchronize: LoggerFinderLoader will
+            // always return the same instance, so if we don't have it,
+            // just fetch it again.
+            if (service == null) {
+                PrivilegedAction<LoggerFinder> pa =
+                        () -> LoggerFinderLoader.getLoggerFinder();
+                service = AccessController.doPrivileged(pa, null,
+                        LOGGERFINDER_PERMISSION);
+            }
+            return service;
+        }
+
+    }
+
+
+    /**
+     * Returns an instance of {@link Logger Logger} for the caller's
+     * use.
+     *
+     * @implSpec
+     * Instances returned by this method route messages to loggers
+     * obtained by calling {@link LoggerFinder#getLogger(java.lang.String, java.lang.Class)
+     * LoggerFinder.getLogger(name, caller)}.
+     *
+     * @apiNote
+     * This method may defer calling the {@link
+     * LoggerFinder#getLogger(java.lang.String, java.lang.Class)
+     * LoggerFinder.getLogger} method to create an actual logger supplied by
+     * the logging backend, for instance, to allow loggers to be obtained during
+     * the system initialization time.
+     *
+     * @param name the name of the logger.
+     * @return an instance of {@link Logger} that can be used by the calling
+     *         class.
+     * @throws NullPointerException if {@code name} is {@code null}.
+     */
+    @CallerSensitive
+    public static Logger getLogger(String name) {
+        Objects.requireNonNull(name);
+        final Class<?> caller = Reflection.getCallerClass();
+        return LazyLoggers.getLogger(name, caller);
+    }
+
+    /**
+     * Returns a localizable instance of {@link Logger
+     * Logger} for the caller's use.
+     * The returned logger will use the provided resource bundle for message
+     * localization.
+     *
+     * @implSpec
+     * The returned logger will perform message localization as specified
+     * by {@link LoggerFinder#getLocalizedLogger(java.lang.String,
+     * java.util.ResourceBundle, java.lang.Class)
+     * LoggerFinder.getLocalizedLogger(name, bundle, caller}.
+     *
+     * @apiNote
+     * This method is intended to be used after the system is fully initialized.
+     * This method may trigger the immediate loading and initialization
+     * of the {@link LoggerFinder} service, which may cause issues if the
+     * Java Runtime is not ready to initialize the concrete service
+     * implementation yet.
+     * System classes which may be loaded early in the boot sequence and
+     * need to log localized messages should create a logger using
+     * {@link #getLogger(java.lang.String)} and then use the log methods that
+     * take a resource bundle as parameter.
+     *
+     * @param name    the name of the logger.
+     * @param bundle  a resource bundle.
+     * @return an instance of {@link Logger} which will use the provided
+     * resource bundle for message localization.
+     * @throws NullPointerException if {@code name} is {@code null} or
+     *         {@code bundle} is {@code null}.
+     */
+    @CallerSensitive
+    public static Logger getLogger(String name, ResourceBundle bundle) {
+        final ResourceBundle rb = Objects.requireNonNull(bundle);
+        Objects.requireNonNull(name);
+        final Class<?> caller = Reflection.getCallerClass();
+        final SecurityManager sm = System.getSecurityManager();
+        // We don't use LazyLoggers if a resource bundle is specified.
+        // Bootstrap sensitive classes in the JDK do not use resource bundles
+        // when logging. This could be revisited later, if it needs to.
+        if (sm != null) {
+            return AccessController.doPrivileged((PrivilegedAction<Logger>)
+                    () -> LoggerFinder.accessProvider().getLocalizedLogger(name, rb, caller),
+                    null,
+                    LoggerFinder.LOGGERFINDER_PERMISSION);
+        }
+        return LoggerFinder.accessProvider().getLocalizedLogger(name, rb, caller);
+    }
+
+    /**
      * Terminates the currently running Java Virtual Machine. The
      * argument serves as a status code; by convention, a nonzero status
      * code indicates abnormal termination.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/logger/AbstractLoggerWrapper.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+package jdk.internal.logger;
+
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * An implementation of {@link System.Logger System.Logger}
+ * that redirects all calls to a wrapped instance of {@link
+ * System.Logger System.Logger}
+ *
+ * @param <L> Type of the wrapped Logger: {@code Logger} or
+ *        an extension of that interface.
+ *
+ */
+abstract class AbstractLoggerWrapper<L extends Logger>
+    implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge {
+
+    AbstractLoggerWrapper() { }
+
+    abstract L wrapped();
+
+    abstract PlatformLogger.Bridge platformProxy();
+
+    L getWrapped() {
+        return wrapped();
+    }
+
+    @Override
+    public final String getName() {
+        return wrapped().getName();
+    }
+
+    // -----------------------------------------------------------------
+    // Generic methods taking a Level as parameter
+    // -----------------------------------------------------------------
+
+
+    @Override
+    public boolean isLoggable(Level level) {
+        return wrapped().isLoggable(level);
+    }
+
+    @Override
+    public void log(Level level, String msg) {
+        wrapped().log(level, msg);
+    }
+
+    @Override
+    public void log(Level level,
+                    Supplier<String> msgSupplier) {
+        wrapped().log(level, msgSupplier);
+    }
+
+    @Override
+    public void log(Level level, Object obj) {
+        wrapped().log(level, obj);
+    }
+
+    @Override
+    public void log(Level level,
+                   String msg, Throwable thrown) {
+        wrapped().log(level, msg, thrown);
+    }
+
+    @Override
+    public void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
+        wrapped().log(level, msgSupplier, thrown);
+    }
+
+    @Override
+    public void log(Level level,
+                    String format, Object... params) {
+        wrapped().log(level, format, params);
+    }
+
+    @Override
+    public void log(Level level, ResourceBundle bundle,
+                    String key, Throwable thrown) {
+        wrapped().log(level, bundle, key, thrown);
+    }
+
+    @Override
+    public void log(Level level, ResourceBundle bundle,
+                    String format, Object... params) {
+        wrapped().log(level, bundle, format, params);
+    }
+
+    // ---------------------------------------------------------
+    // Methods from PlatformLogger.Bridge
+    // ---------------------------------------------------------
+
+    @Override
+    public boolean isLoggable(PlatformLogger.Level level) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null) return isLoggable(level.systemLevel());
+        else return platformProxy.isLoggable(level);
+    }
+
+    @Override
+    public boolean isEnabled() {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        return platformProxy == null || platformProxy.isEnabled();
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, String msg) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null)  {
+            wrapped().log(level.systemLevel(), msg);
+        } else {
+            platformProxy.log(level, msg);
+        }
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, String msg, Throwable thrown) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null)  {
+            wrapped().log(level.systemLevel(), msg, thrown);
+        } else {
+            platformProxy.log(level, msg, thrown);
+        }
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, String msg, Object... params) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null)  {
+            wrapped().log(level.systemLevel(), msg, params);
+        } else {
+            platformProxy.log(level, msg, params);
+        }
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, Supplier<String> msgSupplier) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null)  {
+            wrapped().log(level.systemLevel(),msgSupplier);
+        } else {
+            platformProxy.log(level,msgSupplier);
+        }
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, Throwable thrown,
+                    Supplier<String> msgSupplier) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null)  {
+            wrapped().log(level.systemLevel(), msgSupplier, thrown);
+        } else {
+            platformProxy.log(level, thrown, msgSupplier);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+                     String sourceMethod, String msg) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null)  {
+            if (sourceClass == null && sourceMethod == null) { // best effort
+                wrapped().log(level.systemLevel(), msg);
+            } else {
+                Level systemLevel = level.systemLevel();
+                Logger wrapped = wrapped();
+                if (wrapped.isLoggable(systemLevel)) {
+                    sourceClass  = sourceClass  == null ? "" : sourceClass;
+                    sourceMethod = sourceMethod == null ? "" : sourceMethod;
+                    msg = msg == null ? "" : msg;
+                    wrapped.log(systemLevel, String.format("[%s %s] %s",
+                            sourceClass, sourceMethod, msg));
+                }
+            }
+        } else {
+            platformProxy.logp(level, sourceClass, sourceMethod, msg);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+                     String sourceMethod, Supplier<String> msgSupplier) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null) { // best effort
+            if (sourceClass == null && sourceMethod == null) {
+                wrapped().log(level.systemLevel(), msgSupplier);
+            } else {
+                Level systemLevel = level.systemLevel();
+                Logger wrapped = wrapped();
+                if (wrapped.isLoggable(systemLevel)) {
+                    final String sClass  = sourceClass  == null ? "" : sourceClass;
+                    final String sMethod = sourceMethod == null ? "" : sourceMethod;
+                    wrapped.log(systemLevel, () -> String.format("[%s %s] %s",
+                            sClass, sMethod, msgSupplier.get()));
+                }
+            }
+        } else {
+            platformProxy.logp(level, sourceClass, sourceMethod, msgSupplier);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+                     String sourceMethod, String msg, Object... params) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null) { // best effort
+            if (sourceClass == null && sourceMethod == null) {
+                wrapped().log(level.systemLevel(), msg, params);
+            } else {
+                Level systemLevel = level.systemLevel();
+                Logger wrapped = wrapped();
+                if (wrapped.isLoggable(systemLevel)) {
+                    sourceClass  = sourceClass  == null ? "" : sourceClass;
+                    sourceMethod = sourceMethod == null ? "" : sourceMethod;
+                    msg = msg == null ? "" : msg;
+                    wrapped.log(systemLevel, String.format("[%s %s] %s",
+                            sourceClass, sourceMethod, msg), params);
+                }
+            }
+        } else {
+            platformProxy.logp(level, sourceClass, sourceMethod, msg, params);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+                     String sourceMethod, String msg, Throwable thrown) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null) { // best effort
+            if (sourceClass == null && sourceMethod == null) {
+                wrapped().log(level.systemLevel(), msg, thrown);
+            } else {
+                Level systemLevel = level.systemLevel();
+                Logger wrapped = wrapped();
+                if (wrapped.isLoggable(systemLevel)) {
+                    sourceClass  = sourceClass  == null ? "" : sourceClass;
+                    sourceMethod = sourceMethod == null ? "" : sourceMethod;
+                    msg = msg == null ? "" : msg;
+                    wrapped.log(systemLevel, String.format("[%s %s] %s",
+                            sourceClass, sourceMethod, msg), thrown);
+                }
+            }
+        } else {
+            platformProxy.logp(level, sourceClass, sourceMethod, msg, thrown);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+                     String sourceMethod, Throwable thrown,
+                     Supplier<String> msgSupplier) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null)  { // best effort
+            if (sourceClass == null && sourceMethod == null) {
+                wrapped().log(level.systemLevel(), msgSupplier, thrown);
+            } else {
+                Level systemLevel = level.systemLevel();
+                Logger wrapped = wrapped();
+                if (wrapped.isLoggable(systemLevel)) {
+                    final String sClass  = sourceClass  == null ? "" : sourceClass;
+                    final String sMethod = sourceMethod == null ? "" : sourceMethod;
+                    wrapped.log(systemLevel,  () -> String.format("[%s %s] %s",
+                            sClass, sMethod, msgSupplier.get()), thrown);
+                }
+            }
+        } else {
+            platformProxy.logp(level, sourceClass, sourceMethod,
+                               thrown, msgSupplier);
+        }
+    }
+
+    @Override
+    public void logrb(PlatformLogger.Level level, String sourceClass,
+                      String sourceMethod, ResourceBundle bundle,
+                      String msg, Object... params) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null)  { // best effort
+            if (bundle != null || sourceClass == null && sourceMethod == null) {
+                wrapped().log(level.systemLevel(), bundle, msg, params);
+            } else {
+                Level systemLevel = level.systemLevel();
+                Logger wrapped = wrapped();
+                if (wrapped.isLoggable(systemLevel)) {
+                    sourceClass  = sourceClass  == null ? "" : sourceClass;
+                    sourceMethod = sourceMethod == null ? "" : sourceMethod;
+                    msg = msg == null ? "" : msg;
+                    wrapped.log(systemLevel, bundle, String.format("[%s %s] %s",
+                            sourceClass, sourceMethod, msg), params);
+                }
+            }
+        } else {
+            platformProxy.logrb(level, sourceClass, sourceMethod,
+                    bundle, msg, params);
+        }
+    }
+
+    @Override
+    public void logrb(PlatformLogger.Level level, String sourceClass,
+                      String sourceMethod, ResourceBundle bundle, String msg,
+                      Throwable thrown) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null)  { // best effort
+            if (bundle != null || sourceClass == null && sourceMethod == null) {
+                wrapped().log(level.systemLevel(), bundle, msg, thrown);
+            } else {
+                Level systemLevel = level.systemLevel();
+                Logger wrapped = wrapped();
+                if (wrapped.isLoggable(systemLevel)) {
+                    sourceClass  = sourceClass  == null ? "" : sourceClass;
+                    sourceMethod = sourceMethod == null ? "" : sourceMethod;
+                    msg = msg == null ? "" : msg;
+                    wrapped.log(systemLevel, bundle, String.format("[%s %s] %s",
+                            sourceClass, sourceMethod, msg), thrown);
+                }
+            }
+        } else {
+            platformProxy.logrb(level, sourceClass, sourceMethod, bundle,
+                                msg, thrown);
+        }
+    }
+
+    @Override
+    public void logrb(PlatformLogger.Level level, ResourceBundle bundle,
+                      String msg, Throwable thrown) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null)  {
+            wrapped().log(level.systemLevel(), bundle, msg, thrown);
+        } else {
+            platformProxy.logrb(level, bundle, msg, thrown);
+        }
+    }
+
+    @Override
+    public void logrb(PlatformLogger.Level level, ResourceBundle bundle,
+                      String msg, Object... params) {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        if (platformProxy == null)  {
+            wrapped().log(level.systemLevel(), bundle, msg, params);
+        } else {
+            platformProxy.logrb(level, bundle, msg, params);
+        }
+    }
+
+
+    @Override
+    public LoggerConfiguration getLoggerConfiguration() {
+        final PlatformLogger.Bridge platformProxy = platformProxy();
+        return platformProxy == null ? null
+               : PlatformLogger.ConfigurableBridge
+                       .getLoggerConfiguration(platformProxy);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,1074 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+package jdk.internal.logger;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.ServiceLoader;
+import java.util.function.BooleanSupplier;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import sun.misc.InnocuousThread;
+import sun.misc.VM;
+import sun.util.logging.PlatformLogger;
+import jdk.internal.logger.LazyLoggers.LazyLoggerAccessor;
+
+/**
+ * The BootstrapLogger class handles all the logic needed by Lazy Loggers
+ * to delay the creation of System.Logger instances until the VM is booted.
+ * By extension - it also contains the logic that will delay the creation
+ * of JUL Loggers until the LogManager is initialized by the application, in
+ * the common case where JUL is the default and there is no custom JUL
+ * configuration.
+ *
+ * A BootstrapLogger instance is both a Logger and a
+ * PlatformLogger.Bridge instance, which will put all Log messages in a queue
+ * until the VM is booted.
+ * Once the VM is booted, it obtain the real System.Logger instance from the
+ * LoggerFinder and flushes the message to the queue.
+ *
+ * There are a few caveat:
+ *  - the queue may not be flush until the next message is logged after
+ *    the VM is booted
+ *  - while the BootstrapLogger is active, the default implementation
+ *    for all convenience methods is used
+ *  - PlatformLogger.setLevel calls are ignored
+ *
+ *
+ */
+public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
+        PlatformLogger.ConfigurableBridge {
+
+    // We use the BootstrapExecutors class to submit delayed messages
+    // to an independent InnocuousThread which will ensure that
+    // delayed log events will be clearly identified as messages that have
+    // been delayed during the boot sequence.
+    private static class BootstrapExecutors implements ThreadFactory {
+
+        // Maybe that should be made configurable with system properties.
+        static final long KEEP_EXECUTOR_ALIVE_SECONDS = 30;
+
+        // The BootstrapMessageLoggerTask is a Runnable which keeps
+        // a hard ref to the ExecutorService that owns it.
+        // This ensure that the ExecutorService is not gc'ed until the thread
+        // has stopped running.
+        private static class BootstrapMessageLoggerTask implements Runnable {
+            ExecutorService owner;
+            Runnable run;
+            public BootstrapMessageLoggerTask(ExecutorService owner, Runnable r) {
+                this.owner = owner;
+                this.run = r;
+            }
+            @Override
+            public void run() {
+                try {
+                    run.run();
+                } finally {
+                    owner = null; // allow the ExecutorService to be gced.
+                }
+            }
+        }
+
+        private static volatile WeakReference<ExecutorService> executorRef;
+        private static ExecutorService getExecutor() {
+            WeakReference<ExecutorService> ref = executorRef;
+            ExecutorService executor = ref == null ? null : ref.get();
+            if (executor != null) return executor;
+            synchronized (BootstrapExecutors.class) {
+                ref = executorRef;
+                executor = ref == null ? null : ref.get();
+                if (executor == null) {
+                    executor = new ThreadPoolExecutor(0, 1,
+                            KEEP_EXECUTOR_ALIVE_SECONDS, TimeUnit.SECONDS,
+                            new LinkedBlockingQueue<>(), new BootstrapExecutors());
+                }
+                // The executor service will be elligible for gc
+                // KEEP_EXECUTOR_ALIVE_SECONDS seconds (30s)
+                // after the execution of its last pending task.
+                executorRef = new WeakReference<>(executor);
+                return executorRef.get();
+            }
+        }
+
+        @Override
+        public Thread newThread(Runnable r) {
+            ExecutorService owner = getExecutor();
+            Thread thread = AccessController.doPrivileged(new PrivilegedAction<Thread>() {
+                @Override
+                public Thread run() {
+                    Thread t = new InnocuousThread(new BootstrapMessageLoggerTask(owner, r));
+                    t.setName("BootstrapMessageLoggerTask-"+t.getName());
+                    return t;
+                }
+            }, null, new RuntimePermission("enableContextClassLoaderOverride"));
+            thread.setDaemon(true);
+            return thread;
+        }
+
+        static void submit(Runnable r) {
+            getExecutor().execute(r);
+        }
+
+        // This is used by tests.
+        static void join(Runnable r) {
+            try {
+                getExecutor().submit(r).get();
+            } catch (InterruptedException | ExecutionException ex) {
+                // should not happen
+                throw new RuntimeException(ex);
+            }
+        }
+
+        // This is used by tests.
+        static void awaitPendingTasks() {
+            WeakReference<ExecutorService> ref = executorRef;
+            ExecutorService executor = ref == null ? null : ref.get();
+            if (ref == null) {
+                synchronized(BootstrapExecutors.class) {
+                    ref = executorRef;
+                    executor = ref == null ? null : ref.get();
+                }
+            }
+            if (executor != null) {
+                // since our executor uses a FIFO and has a single thread
+                // then awaiting the execution of its pending tasks can be done
+                // simply by registering a new task and waiting until it
+                // completes. This of course would not work if we were using
+                // several threads, but we don't.
+                join(()->{});
+            }
+        }
+
+        // This is used by tests.
+        static boolean isAlive() {
+            WeakReference<ExecutorService> ref = executorRef;
+            ExecutorService executor = ref == null ? null : ref.get();
+            if (executor != null) return true;
+            synchronized (BootstrapExecutors.class) {
+                ref = executorRef;
+                executor = ref == null ? null : ref.get();
+                return executor != null;
+            }
+        }
+
+        // The pending log event queue. The first event is the head, and
+        // new events are added at the tail
+        static LogEvent head, tail;
+
+        static void enqueue(LogEvent event) {
+            if (event.next != null) return;
+            synchronized (BootstrapExecutors.class) {
+                if (event.next != null) return;
+                event.next = event;
+                if (tail == null) {
+                    head = tail = event;
+                } else {
+                    tail.next = event;
+                    tail = event;
+                }
+            }
+        }
+
+        static void flush() {
+            LogEvent event;
+            // drain the whole queue
+            synchronized(BootstrapExecutors.class) {
+                event = head;
+                head = tail = null;
+            }
+            while(event != null) {
+                LogEvent.log(event);
+                synchronized(BootstrapExecutors.class) {
+                    LogEvent prev = event;
+                    event = (event.next == event ? null : event.next);
+                    prev.next = null;
+                }
+            }
+        }
+    }
+
+    // The accessor in which this logger is temporarily set.
+    final LazyLoggerAccessor holder;
+
+    BootstrapLogger(LazyLoggerAccessor holder) {
+        this.holder = holder;
+    }
+
+    // Temporary data object storing log events
+    // It would be nice to use a Consumer<Logger> instead of a LogEvent.
+    // This way we could simply do things like:
+    //    push((logger) -> logger.log(level, msg));
+    // Unfortunately, if we come to here it means we are in the bootsraping
+    // phase where using lambdas is not safe yet - so we have to use a
+    // a data object instead...
+    //
+    static final class LogEvent {
+        // only one of these two levels should be non null
+        final Level level;
+        final PlatformLogger.Level platformLevel;
+        final BootstrapLogger bootstrap;
+
+        final ResourceBundle bundle;
+        final String msg;
+        final Throwable thrown;
+        final Object[] params;
+        final Supplier<String> msgSupplier;
+        final String sourceClass;
+        final String sourceMethod;
+        final long timeMillis;
+        final long nanoAdjustment;
+
+        // because logging a message may entail calling toString() on
+        // the parameters etc... we need to store the context of the
+        // caller who logged the message - so that we can reuse it when
+        // we finally log the message.
+        final AccessControlContext acc;
+
+        // The next event in the queue
+        LogEvent next;
+
+        private LogEvent(BootstrapLogger bootstrap, Level level,
+                ResourceBundle bundle, String msg,
+                Throwable thrown, Object[] params) {
+            this.acc = AccessController.getContext();
+            this.timeMillis = System.currentTimeMillis();
+            this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis);
+            this.level = level;
+            this.platformLevel = null;
+            this.bundle = bundle;
+            this.msg = msg;
+            this.msgSupplier = null;
+            this.thrown = thrown;
+            this.params = params;
+            this.sourceClass = null;
+            this.sourceMethod = null;
+            this.bootstrap = bootstrap;
+        }
+
+        private LogEvent(BootstrapLogger bootstrap, Level level,
+                Supplier<String> msgSupplier,
+                Throwable thrown, Object[] params) {
+            this.acc = AccessController.getContext();
+            this.timeMillis = System.currentTimeMillis();
+            this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis);
+            this.level = level;
+            this.platformLevel = null;
+            this.bundle = null;
+            this.msg = null;
+            this.msgSupplier = msgSupplier;
+            this.thrown = thrown;
+            this.params = params;
+            this.sourceClass = null;
+            this.sourceMethod = null;
+            this.bootstrap = bootstrap;
+        }
+
+        private LogEvent(BootstrapLogger bootstrap,
+                PlatformLogger.Level platformLevel,
+                String sourceClass, String sourceMethod,
+                ResourceBundle bundle, String msg,
+                Throwable thrown, Object[] params) {
+            this.acc = AccessController.getContext();
+            this.timeMillis = System.currentTimeMillis();
+            this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis);
+            this.level = null;
+            this.platformLevel = platformLevel;
+            this.bundle = bundle;
+            this.msg = msg;
+            this.msgSupplier = null;
+            this.thrown = thrown;
+            this.params = params;
+            this.sourceClass = sourceClass;
+            this.sourceMethod = sourceMethod;
+            this.bootstrap = bootstrap;
+        }
+
+        private LogEvent(BootstrapLogger bootstrap,
+                PlatformLogger.Level platformLevel,
+                String sourceClass, String sourceMethod,
+                Supplier<String> msgSupplier,
+                Throwable thrown, Object[] params) {
+            this.acc = AccessController.getContext();
+            this.timeMillis = System.currentTimeMillis();
+            this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis);
+            this.level = null;
+            this.platformLevel = platformLevel;
+            this.bundle = null;
+            this.msg = null;
+            this.msgSupplier = msgSupplier;
+            this.thrown = thrown;
+            this.params = params;
+            this.sourceClass = sourceClass;
+            this.sourceMethod = sourceMethod;
+            this.bootstrap = bootstrap;
+        }
+
+        // Log this message in the given logger. Do not call directly.
+        // Use LogEvent.log(LogEvent, logger) instead.
+        private void log(Logger logger) {
+            assert platformLevel == null && level != null;
+            //new Exception("logging delayed message").printStackTrace();
+            if (msgSupplier != null) {
+                if (thrown != null) {
+                    logger.log(level, msgSupplier, thrown);
+                } else {
+                    logger.log(level, msgSupplier);
+                }
+            } else {
+                // BootstrapLoggers are never localized so we can safely
+                // use the method that takes a ResourceBundle parameter
+                // even when that resource bundle is null.
+                if (thrown != null) {
+                    logger.log(level, bundle, msg, thrown);
+                } else {
+                    logger.log(level, bundle, msg, params);
+                }
+            }
+        }
+
+        // Log this message in the given logger.  Do not call directly.
+        // Use LogEvent.doLog(LogEvent, logger) instead.
+        private void log(PlatformLogger.Bridge logger) {
+            assert platformLevel != null && level == null;
+            if (sourceClass == null) {
+                if (msgSupplier != null) {
+                    if (thrown != null) {
+                        logger.log(platformLevel, thrown, msgSupplier);
+                    } else {
+                        logger.log(platformLevel, msgSupplier);
+                    }
+                } else {
+                    // BootstrapLoggers are never localized so we can safely
+                    // use the method that takes a ResourceBundle parameter
+                    // even when that resource bundle is null.
+                    if (thrown != null) {
+                        logger.logrb(platformLevel, bundle, msg, thrown);
+                    } else {
+                        logger.logrb(platformLevel, bundle, msg, params);
+                    }
+                }
+            } else {
+                if (msgSupplier != null) {
+                    if (thrown != null) {
+                        logger.log(platformLevel, sourceClass, sourceMethod, thrown, msgSupplier);
+                    } else {
+                        logger.log(platformLevel,sourceClass, sourceMethod,  msgSupplier);
+                    }
+                } else {
+                    // BootstrapLoggers are never localized so we can safely
+                    // use the method that takes a ResourceBundle parameter
+                    // even when that resource bundle is null.
+                    if (thrown != null) {
+                        logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, thrown);
+                    } else {
+                        logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, params);
+                    }
+                }
+            }
+        }
+
+        // non default methods from Logger interface
+        static LogEvent valueOf(BootstrapLogger bootstrap, Level level,
+                ResourceBundle bundle, String key, Throwable thrown) {
+            return new LogEvent(Objects.requireNonNull(bootstrap),
+                                Objects.requireNonNull(level), bundle, key,
+                                thrown, null);
+        }
+        static LogEvent valueOf(BootstrapLogger bootstrap, Level level,
+                ResourceBundle bundle, String format, Object[] params) {
+            return new LogEvent(Objects.requireNonNull(bootstrap),
+                                Objects.requireNonNull(level), bundle, format,
+                                null, params);
+        }
+        static LogEvent valueOf(BootstrapLogger bootstrap, Level level,
+                                Supplier<String> msgSupplier, Throwable thrown) {
+            return new LogEvent(Objects.requireNonNull(bootstrap),
+                    Objects.requireNonNull(level),
+                    Objects.requireNonNull(msgSupplier), thrown, null);
+        }
+        static LogEvent valueOf(BootstrapLogger bootstrap, Level level,
+                                Supplier<String> msgSupplier) {
+            return new LogEvent(Objects.requireNonNull(bootstrap),
+                                Objects.requireNonNull(level),
+                                Objects.requireNonNull(msgSupplier), null, null);
+        }
+        static void log(LogEvent log, Logger logger) {
+            final SecurityManager sm = System.getSecurityManager();
+            // not sure we can actually use lambda here. We may need to create
+            // an anonymous class. Although if we reach here, then it means
+            // the VM is booted.
+            if (sm == null || log.acc == null) {
+                BootstrapExecutors.submit(() -> log.log(logger));
+            } else {
+                BootstrapExecutors.submit(() ->
+                    AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
+                        log.log(logger); return null;
+                    }, log.acc));
+            }
+        }
+
+        // non default methods from PlatformLogger.Bridge interface
+        static LogEvent valueOf(BootstrapLogger bootstrap,
+                                PlatformLogger.Level level, String msg) {
+            return new LogEvent(Objects.requireNonNull(bootstrap),
+                                Objects.requireNonNull(level), null, null, null,
+                                msg, null, null);
+        }
+        static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+                                String msg, Throwable thrown) {
+            return new LogEvent(Objects.requireNonNull(bootstrap),
+                    Objects.requireNonNull(level), null, null, null, msg, thrown, null);
+        }
+        static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+                                String msg, Object[] params) {
+            return new LogEvent(Objects.requireNonNull(bootstrap),
+                    Objects.requireNonNull(level), null, null, null, msg, null, params);
+        }
+        static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+                                Supplier<String> msgSupplier) {
+            return new LogEvent(Objects.requireNonNull(bootstrap),
+                    Objects.requireNonNull(level), null, null, msgSupplier, null, null);
+        }
+        static LogEvent vaueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+                               Supplier<String> msgSupplier,
+                               Throwable thrown) {
+            return new LogEvent(Objects.requireNonNull(bootstrap),
+                                Objects.requireNonNull(level), null, null,
+                                msgSupplier, thrown, null);
+        }
+        static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+                                String sourceClass, String sourceMethod,
+                                ResourceBundle bundle, String msg, Object[] params) {
+            return new LogEvent(Objects.requireNonNull(bootstrap),
+                                Objects.requireNonNull(level), sourceClass,
+                                sourceMethod, bundle, msg, null, params);
+        }
+        static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+                                String sourceClass, String sourceMethod,
+                                ResourceBundle bundle, String msg, Throwable thrown) {
+            return new LogEvent(Objects.requireNonNull(bootstrap),
+                                Objects.requireNonNull(level), sourceClass,
+                                sourceMethod, bundle, msg, thrown, null);
+        }
+        static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+                                String sourceClass, String sourceMethod,
+                                Supplier<String> msgSupplier, Throwable thrown) {
+            return new LogEvent(Objects.requireNonNull(bootstrap),
+                                Objects.requireNonNull(level), sourceClass,
+                                sourceMethod, msgSupplier, thrown, null);
+        }
+        static void log(LogEvent log, PlatformLogger.Bridge logger) {
+            final SecurityManager sm = System.getSecurityManager();
+            if (sm == null || log.acc == null) {
+                log.log(logger);
+            } else {
+                // not sure we can actually use lambda here. We may need to create
+                // an anonymous class. Although if we reach here, then it means
+                // the VM is booted.
+                AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
+                    log.log(logger); return null;
+                }, log.acc);
+            }
+        }
+
+        static void log(LogEvent event) {
+            event.bootstrap.flush(event);
+        }
+
+    }
+
+    // Push a log event at the end of the pending LogEvent queue.
+    void push(LogEvent log) {
+        BootstrapExecutors.enqueue(log);
+        // if the queue has been flushed just before we entered
+        // the synchronized block we need to flush it again.
+        checkBootstrapping();
+    }
+
+    // Flushes the queue of pending LogEvents to the logger.
+    void flush(LogEvent event) {
+        assert event.bootstrap == this;
+        if (event.platformLevel != null) {
+            PlatformLogger.Bridge concrete = holder.getConcretePlatformLogger(this);
+            LogEvent.log(event, concrete);
+        } else {
+            Logger concrete = holder.getConcreteLogger(this);
+            LogEvent.log(event, concrete);
+        }
+    }
+
+    /**
+     * The name of this logger. This is the name of the actual logger for which
+     * this logger acts as a temporary proxy.
+     * @return The logger name.
+     */
+    @Override
+    public String getName() {
+        return holder.name;
+    }
+
+    /**
+     * Check whether the VM is still bootstrapping, and if not, arranges
+     * for this logger's holder to create the real logger and flush the
+     * pending event queue.
+     * @return true if the VM is still bootstrapping.
+     */
+    boolean checkBootstrapping() {
+        if (isBooted()) {
+            BootstrapExecutors.flush();
+            return false;
+        }
+        return true;
+    }
+
+    // ----------------------------------
+    // Methods from Logger
+    // ----------------------------------
+
+    @Override
+    public boolean isLoggable(Level level) {
+        if (checkBootstrapping()) {
+            return level.getSeverity() >= Level.INFO.getSeverity();
+        } else {
+            final Logger spi = holder.wrapped();
+            return spi.isLoggable(level);
+        }
+    }
+
+    @Override
+    public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, bundle, key, thrown));
+        } else {
+            final Logger spi = holder.wrapped();
+            spi.log(level, bundle, key, thrown);
+        }
+    }
+
+    @Override
+    public void log(Level level, ResourceBundle bundle, String format, Object... params) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, bundle, format, params));
+        } else {
+            final Logger spi = holder.wrapped();
+            spi.log(level, bundle, format, params);
+        }
+    }
+
+    @Override
+    public void log(Level level, String msg, Throwable thrown) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, null, msg, thrown));
+        } else {
+            final Logger spi = holder.wrapped();
+            spi.log(level, msg, thrown);
+        }
+    }
+
+    @Override
+    public void log(Level level, String format, Object... params) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, null, format, params));
+        } else {
+            final Logger spi = holder.wrapped();
+            spi.log(level, format, params);
+        }
+    }
+
+    @Override
+    public void log(Level level, Supplier<String> msgSupplier) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, msgSupplier));
+        } else {
+            final Logger spi = holder.wrapped();
+            spi.log(level, msgSupplier);
+        }
+    }
+
+    @Override
+    public void log(Level level, Object obj) {
+        if (checkBootstrapping()) {
+            Logger.super.log(level, obj);
+        } else {
+            final Logger spi = holder.wrapped();
+            spi.log(level, obj);
+        }
+    }
+
+    @Override
+    public void log(Level level, String msg) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, null, msg, (Object[])null));
+        } else {
+            final Logger spi = holder.wrapped();
+            spi.log(level, msg);
+        }
+    }
+
+    @Override
+    public void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, msgSupplier, thrown));
+        } else {
+            final Logger spi = holder.wrapped();
+            spi.log(level, msgSupplier, thrown);
+        }
+    }
+
+    // ----------------------------------
+    // Methods from PlatformLogger.Bridge
+    // ----------------------------------
+
+    @Override
+    public boolean isLoggable(PlatformLogger.Level level) {
+        if (checkBootstrapping()) {
+            return level.intValue() >= PlatformLogger.Level.INFO.intValue();
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            return spi.isLoggable(level);
+        }
+    }
+
+    @Override
+    public boolean isEnabled() {
+        if (checkBootstrapping()) {
+            return true;
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            return spi.isEnabled();
+        }
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, String msg) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, msg));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.log(level, msg);
+        }
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, String msg, Throwable thrown) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, msg, thrown));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.log(level, msg, thrown);
+        }
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, String msg, Object... params) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, msg, params));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.log(level, msg, params);
+        }
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, Supplier<String> msgSupplier) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, msgSupplier));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.log(level, msgSupplier);
+        }
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, Throwable thrown,
+            Supplier<String> msgSupplier) {
+        if (checkBootstrapping()) {
+            push(LogEvent.vaueOf(this, level, msgSupplier, thrown));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.log(level, thrown, msgSupplier);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+            String sourceMethod, String msg) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null,
+                    msg, (Object[])null));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.logp(level, sourceClass, sourceMethod, msg);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+            String sourceMethod, Supplier<String> msgSupplier) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, msgSupplier, null));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.logp(level, sourceClass, sourceMethod, msgSupplier);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+            String sourceMethod, String msg, Object... params) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, msg, params));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.logp(level, sourceClass, sourceMethod, msg, params);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+            String sourceMethod, String msg, Throwable thrown) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, msg, thrown));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.logp(level, sourceClass, sourceMethod, msg, thrown);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+            String sourceMethod, Throwable thrown, Supplier<String> msgSupplier) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, msgSupplier, thrown));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.logp(level, sourceClass, sourceMethod, thrown, msgSupplier);
+        }
+    }
+
+    @Override
+    public void logrb(PlatformLogger.Level level, String sourceClass,
+            String sourceMethod, ResourceBundle bundle, String msg, Object... params) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, bundle, msg, params));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.logrb(level, sourceClass, sourceMethod, bundle, msg, params);
+        }
+    }
+
+    @Override
+    public void logrb(PlatformLogger.Level level, String sourceClass,
+            String sourceMethod, ResourceBundle bundle, String msg, Throwable thrown) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, bundle, msg, thrown));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.logrb(level, sourceClass, sourceMethod, bundle, msg, thrown);
+        }
+    }
+
+    @Override
+    public void logrb(PlatformLogger.Level level, ResourceBundle bundle,
+            String msg, Object... params) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, null, null, bundle, msg, params));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.logrb(level, bundle, msg, params);
+        }
+    }
+
+    @Override
+    public void logrb(PlatformLogger.Level level, ResourceBundle bundle, String msg, Throwable thrown) {
+        if (checkBootstrapping()) {
+            push(LogEvent.valueOf(this, level, null, null, bundle, msg, thrown));
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            spi.logrb(level, bundle, msg, thrown);
+        }
+    }
+
+    @Override
+    public LoggerConfiguration getLoggerConfiguration() {
+        if (checkBootstrapping()) {
+            // This practically means that PlatformLogger.setLevel()
+            // calls will be ignored if the VM is still bootstrapping. We could
+            // attempt to fix that but is it worth it?
+            return PlatformLogger.ConfigurableBridge.super.getLoggerConfiguration();
+        } else {
+            final PlatformLogger.Bridge spi = holder.platform();
+            return PlatformLogger.ConfigurableBridge.getLoggerConfiguration(spi);
+        }
+    }
+
+    // This BooleanSupplier is a hook for tests - so that we can simulate
+    // what would happen before the VM is booted.
+    private static volatile BooleanSupplier isBooted;
+    public static boolean isBooted() {
+        if (isBooted != null) return isBooted.getAsBoolean();
+        else return VM.isBooted();
+    }
+
+    // A bit of black magic. We try to find out the nature of the logging
+    // backend without actually loading it.
+    private static enum LoggingBackend {
+        // There is no LoggerFinder and JUL is not present
+        NONE(true),
+
+        // There is no LoggerFinder, but we have found a
+        // JdkLoggerFinder installed (which means JUL is present),
+        // and we haven't found any custom configuration for JUL.
+        // Until LogManager is initialized we can use a simple console
+        // logger.
+        JUL_DEFAULT(false),
+
+        // Same as above, except that we have found a custom configuration
+        // for JUL. We cannot use the simple console logger in this case.
+        JUL_WITH_CONFIG(true),
+
+        // We have found a custom LoggerFinder.
+        CUSTOM(true);
+
+        final boolean useLoggerFinder;
+        private LoggingBackend(boolean useLoggerFinder) {
+            this.useLoggerFinder = useLoggerFinder;
+        }
+    };
+
+    // The purpose of this class is to delay the initialization of
+    // the detectedBackend field until it is actually read.
+    // We do not want this field to get initialized if VM.isBooted() is false.
+    private static final class DetectBackend {
+        static final LoggingBackend detectedBackend;
+        static {
+            detectedBackend = AccessController.doPrivileged(new PrivilegedAction<LoggingBackend>() {
+                    @Override
+                    public LoggingBackend run() {
+                        final Iterator<LoggerFinder> iterator =
+                            ServiceLoader.load(LoggerFinder.class, ClassLoader.getSystemClassLoader())
+                            .iterator();
+                        if (iterator.hasNext()) {
+                            return LoggingBackend.CUSTOM; // Custom Logger Provider is registered
+                        }
+                        // No custom logger provider: we will be using the default
+                        // backend.
+                        final Iterator<DefaultLoggerFinder> iterator2 =
+                            ServiceLoader.loadInstalled(DefaultLoggerFinder.class)
+                            .iterator();
+                        if (iterator2.hasNext()) {
+                            // LoggingProviderImpl is registered. The default
+                            // implementation is java.util.logging
+                            String cname = System.getProperty("java.util.logging.config.class");
+                            String fname = System.getProperty("java.util.logging.config.file");
+                            return (cname != null || fname != null)
+                                ? LoggingBackend.JUL_WITH_CONFIG
+                                : LoggingBackend.JUL_DEFAULT;
+                        } else {
+                            // SimpleLogger is used
+                            return LoggingBackend.NONE;
+                        }
+                    }
+                });
+
+        }
+    }
+
+    // We will use temporary SimpleConsoleLoggers if
+    // the logging backend is JUL, there is no custom config,
+    // and the LogManager has not been initialized yet.
+    private static  boolean useTemporaryLoggers() {
+        // being paranoid: this should already have been checked
+        if (!isBooted()) return true;
+        return DetectBackend.detectedBackend == LoggingBackend.JUL_DEFAULT
+                && !logManagerConfigured;
+    }
+
+    // We will use lazy loggers if:
+    //    - the VM is not yet booted
+    //    - the logging backend is a custom backend
+    //    - the logging backend is JUL, there is no custom config,
+    //      and the LogManager has not been initialized yet.
+    public static synchronized boolean useLazyLoggers() {
+        return !BootstrapLogger.isBooted()
+                || DetectBackend.detectedBackend == LoggingBackend.CUSTOM
+                || useTemporaryLoggers();
+    }
+
+    // Called by LazyLoggerAccessor. This method will determine whether
+    // to create a BootstrapLogger (if the VM is not yet booted),
+    // a SimpleConsoleLogger (if JUL is the default backend and there
+    // is no custom JUL configuration and LogManager is not yet initialized),
+    // or a logger returned by the loaded LoggerFinder (all other cases).
+    static Logger getLogger(LazyLoggerAccessor accessor) {
+        if (!BootstrapLogger.isBooted()) {
+            return new BootstrapLogger(accessor);
+        } else {
+            boolean temporary = useTemporaryLoggers();
+            if (temporary) {
+                // JUL is the default backend, there is no custom configuration,
+                // LogManager has not been used.
+                synchronized(BootstrapLogger.class) {
+                    if (useTemporaryLoggers()) {
+                        return makeTemporaryLogger(accessor);
+                    }
+                }
+            }
+            // Already booted. Return the real logger.
+            return accessor.createLogger();
+        }
+    }
+
+
+    // If the backend is JUL, and there is no custom configuration, and
+    // nobody has attempted to call LogManager.getLogManager() yet, then
+    // we can temporarily substitute JUL Logger with SimpleConsoleLoggers,
+    // which avoids the cost of actually loading up the LogManager...
+    // The TemporaryLoggers class has the logic to create such temporary
+    // loggers, and to possibly replace them with real JUL loggers if
+    // someone calls LogManager.getLogManager().
+    static final class TemporaryLoggers implements
+            Function<LazyLoggerAccessor, SimpleConsoleLogger> {
+
+        // all accesses must be synchronized on the outer BootstrapLogger.class
+        final Map<LazyLoggerAccessor, SimpleConsoleLogger> temporaryLoggers =
+                new HashMap<>();
+
+        // all accesses must be synchronized on the outer BootstrapLogger.class
+        // The temporaryLoggers map will be cleared when LogManager is initialized.
+        boolean cleared;
+
+        @Override
+        // all accesses must be synchronized on the outer BootstrapLogger.class
+        public SimpleConsoleLogger apply(LazyLoggerAccessor t) {
+            if (cleared) throw new IllegalStateException("LoggerFinder already initialized");
+            return SimpleConsoleLogger.makeSimpleLogger(t.getLoggerName(), true);
+        }
+
+        // all accesses must be synchronized on the outer BootstrapLogger.class
+        SimpleConsoleLogger get(LazyLoggerAccessor a) {
+            if (cleared) throw new IllegalStateException("LoggerFinder already initialized");
+            return temporaryLoggers.computeIfAbsent(a, this);
+        }
+
+        // all accesses must be synchronized on the outer BootstrapLogger.class
+        Map<LazyLoggerAccessor, SimpleConsoleLogger> drainTemporaryLoggers() {
+            if (temporaryLoggers.isEmpty()) return null;
+            if (cleared) throw new IllegalStateException("LoggerFinder already initialized");
+            final Map<LazyLoggerAccessor, SimpleConsoleLogger> accessors = new HashMap<>(temporaryLoggers);
+            temporaryLoggers.clear();
+            cleared = true;
+            return accessors;
+        }
+
+        static void resetTemporaryLoggers(Map<LazyLoggerAccessor, SimpleConsoleLogger> accessors) {
+            // When the backend is JUL we want to force the creation of
+            // JUL loggers here: some tests are expecting that the
+            // PlatformLogger will create JUL loggers as soon as the
+            // LogManager is initialized.
+            //
+            // If the backend is not JUL then we can delay the re-creation
+            // of the wrapped logger until they are next accessed.
+            //
+            final LoggingBackend detectedBackend = DetectBackend.detectedBackend;
+            final boolean lazy = detectedBackend != LoggingBackend.JUL_DEFAULT
+                    && detectedBackend != LoggingBackend.JUL_WITH_CONFIG;
+            for (Map.Entry<LazyLoggerAccessor, SimpleConsoleLogger> a : accessors.entrySet()) {
+                a.getKey().release(a.getValue(), !lazy);
+            }
+        }
+
+        // all accesses must be synchronized on the outer BootstrapLogger.class
+        static final TemporaryLoggers INSTANCE = new TemporaryLoggers();
+    }
+
+    static synchronized Logger makeTemporaryLogger(LazyLoggerAccessor a) {
+        // accesses to TemporaryLoggers is synchronized on BootstrapLogger.class
+        return TemporaryLoggers.INSTANCE.get(a);
+    }
+
+    private static volatile boolean logManagerConfigured;
+
+    private static synchronized Map<LazyLoggerAccessor, SimpleConsoleLogger>
+         releaseTemporaryLoggers() {
+        // first check whether there's a chance that we have used
+        // temporary loggers; Will be false if logManagerConfigured is already
+        // true.
+        final boolean clearTemporaryLoggers = useTemporaryLoggers();
+
+        // then sets the flag that tells that the log manager is configured
+        logManagerConfigured = true;
+
+        // finally replace all temporary loggers by real JUL loggers
+        if (clearTemporaryLoggers) {
+            // accesses to TemporaryLoggers is synchronized on BootstrapLogger.class
+            return TemporaryLoggers.INSTANCE.drainTemporaryLoggers();
+        } else {
+            return null;
+        }
+    }
+
+    public static void redirectTemporaryLoggers() {
+        // This call is synchronized on BootstrapLogger.class.
+        final Map<LazyLoggerAccessor, SimpleConsoleLogger> accessors =
+                releaseTemporaryLoggers();
+
+        // We will now reset the logger accessors, triggering the
+        // (possibly lazy) replacement of any temporary logger by the
+        // real logger returned from the loaded LoggerFinder.
+        if (accessors != null) {
+            TemporaryLoggers.resetTemporaryLoggers(accessors);
+        }
+
+        BootstrapExecutors.flush();
+    }
+
+    // Hook for tests which need to wait until pending messages
+    // are processed.
+    static void awaitPendingTasks() {
+        BootstrapExecutors.awaitPendingTasks();
+    }
+    static boolean isAlive() {
+        return BootstrapExecutors.isAlive();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/logger/DefaultLoggerFinder.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+package jdk.internal.logger;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.ref.ReferenceQueue;
+import java.util.Collection;
+import java.util.ResourceBundle;
+
+/**
+ * Internal Service Provider Interface (SPI) that makes it possible to use
+ * {@code java.util.logging} as backend when the {@link
+ * sun.util.logging.internal.LoggingProviderImpl
+ * sun.util.logging.internal.LoggingProviderImpl} is present.
+ * <p>
+ * The JDK default implementation of the {@link LoggerFinder} will
+ * attempt to locate and load an {@linkplain
+ * java.util.ServiceLoader#loadInstalled(java.lang.Class) installed}
+ * implementation of the {@code DefaultLoggerFinder}. If {@code java.util.logging}
+ * is present, this will usually resolve to an instance of {@link
+ * sun.util.logging.internal.LoggingProviderImpl sun.util.logging.internal.LoggingProviderImpl}.
+ * Otherwise, if no concrete service provider is declared for
+ * {@code DefaultLoggerFinder}, the default implementation provided by this class
+ * will be used.
+ * <p>
+ * When the {@link sun.util.logging.internal.LoggingProviderImpl
+ * sun.util.logging.internal.LoggingProviderImpl} is not present then the
+ * default implementation provided by this class is to use a simple logger
+ * that will log messages whose level is INFO and above to the console.
+ * These simple loggers are not configurable.
+ * <p>
+ * When configuration is needed, an application should either link with
+ * {@code java.util.logging} - and use the {@code java.util.logging} for
+ * configuration, or link with {@link LoggerFinder another implementation}
+ * of the {@link LoggerFinder}
+ * that provides the necessary configuration.
+ *
+ * @apiNote Programmers are not expected to call this class directly.
+ * Instead they should rely on the static methods defined by {@link
+ * java.lang.System java.lang.System} or {@link sun.util.logging.PlatformLogger
+ * sun.util.logging.PlatformLogger}.
+ *
+ * @see java.lang.System.LoggerFinder
+ * @see jdk.internal.logger
+ * @see sun.util.logging.internal
+ *
+ */
+public class DefaultLoggerFinder extends LoggerFinder {
+
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+
+    /**
+     * Creates a new instance of DefaultLoggerFinder.
+     * @throws SecurityException if the calling code does not have the
+     * {@code RuntimePermission("loggerFinder")}
+     */
+    protected DefaultLoggerFinder() {
+        this(checkPermission());
+    }
+
+    private DefaultLoggerFinder(Void unused) {
+        // nothing to do.
+    }
+
+    private static Void checkPermission() {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(LOGGERFINDER_PERMISSION);
+        }
+        return null;
+    }
+
+    // SharedLoggers is a default cache of loggers used when JUL is not
+    // present - in that case we use instances of SimpleConsoleLogger which
+    // cannot be directly configure through public APIs.
+    //
+    // We can therefore afford to simply maintain two domains - one for the
+    // system, and one for the application.
+    //
+    static final class SharedLoggers {
+        private final Map<String, Reference<Logger>> loggers =
+                new HashMap<>();
+        private final ReferenceQueue<Logger> queue = new ReferenceQueue<>();
+
+        synchronized Logger get(Function<String, Logger> loggerSupplier, final String name) {
+            Reference<? extends Logger> ref = loggers.get(name);
+            Logger w = ref == null ? null :  ref.get();
+            if (w == null) {
+                w = loggerSupplier.apply(name);
+                loggers.put(name, new WeakReference<>(w, queue));
+            }
+
+            // Remove stale mapping...
+            Collection<Reference<Logger>> values = null;
+            while ((ref = queue.poll()) != null) {
+                if (values == null) values = loggers.values();
+                values.remove(ref);
+            }
+            return w;
+        }
+
+
+        final static SharedLoggers system = new SharedLoggers();
+        final static SharedLoggers application = new SharedLoggers();
+    }
+
+    @Override
+    public final Logger getLogger(String name,  /* Module */ Class<?> caller) {
+        checkPermission();
+        return demandLoggerFor(name, caller);
+    }
+
+    @Override
+    public final Logger getLocalizedLogger(String name, ResourceBundle bundle,
+                                           /* Module */  Class<?> caller) {
+        return super.getLocalizedLogger(name, bundle, caller);
+    }
+
+
+
+    /**
+     * Returns a {@link Logger logger} suitable for the caller usage.
+     *
+     * @implSpec The default implementation for this method is to return a
+     *    simple logger that will print all messages of INFO level and above
+     *    to the console. That simple logger is not configurable.
+     *
+     * @param name The name of the logger.
+     * @param caller The class on behalf of which the logger is created.
+     * @return A {@link Logger logger} suitable for the application usage.
+     * @throws SecurityException if the calling code does not have the
+     * {@code RuntimePermission("loggerFinder")}.
+     */
+    protected Logger demandLoggerFor(String name, /* Module */ Class<?> caller) {
+        checkPermission();
+        if (caller.getClassLoader() == null) {
+            return SharedLoggers.system.get(SimpleConsoleLogger::makeSimpleLogger, name);
+        } else {
+            return SharedLoggers.application.get(SimpleConsoleLogger::makeSimpleLogger, name);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/logger/LazyLoggers.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+package jdk.internal.logger;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.function.BiFunction;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+import sun.misc.VM;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * This class is a factory for Lazy Loggers; only system loggers can be
+ * Lazy Loggers.
+ */
+public final class LazyLoggers {
+
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+
+    private LazyLoggers() {
+        throw new InternalError();
+    }
+
+    /**
+     * This class is used to hold the factories that a Lazy Logger will use
+     * to create (or map) its wrapped logger.
+     * @param <L> {@link Logger} or a subclass of {@link Logger}.
+     */
+    private static final class LazyLoggerFactories<L extends Logger> {
+
+        /**
+         * A factory method to create an SPI logger.
+         * Usually, this will be something like LazyLoggers::getSystemLogger.
+         */
+        final BiFunction<String, Class<?>, L> loggerSupplier;
+
+
+        public LazyLoggerFactories(BiFunction<String, Class<?>, L> loggerSupplier) {
+            this(Objects.requireNonNull(loggerSupplier),
+                 (Void)null);
+        }
+
+        private LazyLoggerFactories(BiFunction<String, Class<?>, L> loggerSupplier,
+                          Void unused) {
+            this.loggerSupplier = loggerSupplier;
+        }
+
+    }
+
+    static interface LoggerAccessor {
+        /**
+         * The logger name.
+         * @return The name of the logger that is / will be lazily created.
+         */
+        public String getLoggerName();
+
+        /**
+         * Returns the wrapped logger object.
+         * @return the wrapped logger object.
+         */
+        public Logger wrapped();
+
+        /**
+         * A PlatformLogger.Bridge view of the wrapped logger object.
+         * @return A PlatformLogger.Bridge view of the wrapped logger object.
+         */
+        public PlatformLogger.Bridge platform();
+    }
+
+    /**
+     * The LazyLoggerAccessor class holds all the logic that delays the creation
+     * of the SPI logger until such a time that the VM is booted and the logger
+     * is actually used for logging.
+     *
+     * This class uses the services of the BootstrapLogger class to instantiate
+     * temporary loggers if appropriate.
+     */
+    static final class LazyLoggerAccessor implements LoggerAccessor {
+
+        // The factories that will be used to create the logger lazyly
+        final LazyLoggerFactories<? extends Logger> factories;
+
+        // We need to pass the actual caller when creating the logger.
+        private final WeakReference<Class<?>> callerRef;
+
+        // The name of the logger that will be created lazyly
+        final String name;
+        // The plain logger SPI object - null until it is accessed for the
+        // first time.
+        private volatile Logger w;
+        // A PlatformLogger.Bridge view of w.
+        private volatile PlatformLogger.Bridge p;
+
+
+        private LazyLoggerAccessor(String name,
+                                   LazyLoggerFactories<? extends Logger> factories,
+                                   Class<?> caller) {
+            this(Objects.requireNonNull(name), Objects.requireNonNull(factories),
+                    Objects.requireNonNull(caller), null);
+        }
+
+        private LazyLoggerAccessor(String name,
+                                   LazyLoggerFactories<? extends Logger> factories,
+                                   Class<?> caller, Void unused) {
+            this.name = name;
+            this.factories = factories;
+            this.callerRef = new WeakReference<Class<?>>(caller);
+        }
+
+        /**
+         * The logger name.
+         * @return The name of the logger that is / will be lazily created.
+         */
+        @Override
+        public String getLoggerName() {
+            return name;
+        }
+
+        // must be called in synchronized block
+        // set wrapped logger if not set
+        private void setWrappedIfNotSet(Logger wrapped) {
+            if (w == null) {
+                w = wrapped;
+            }
+        }
+
+        /**
+         * Returns the logger SPI object, creating it if 'w' is still null.
+         * @return the logger SPI object.
+         */
+        public Logger wrapped() {
+            Logger wrapped = w;
+            if (wrapped != null) return wrapped;
+            // Wrapped logger not created yet: create it.
+            // BootstrapLogger has the logic to decide whether to invoke the
+            // SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger)
+            // logger.
+            wrapped = BootstrapLogger.getLogger(this);
+            synchronized(this) {
+                // if w has already been in between, simply drop 'wrapped'.
+                setWrappedIfNotSet(wrapped);
+                return w;
+            }
+        }
+
+        /**
+         * A PlatformLogger.Bridge view of the wrapped logger.
+         * @return A PlatformLogger.Bridge view of the wrapped logger.
+         */
+        public PlatformLogger.Bridge platform() {
+            // We can afford to return the platform view of the previous
+            // logger - if that view is not null.
+            // Because that view will either be the BootstrapLogger, which
+            // will redirect to the new wrapper properly, or the temporary
+            // logger - which in effect is equivalent to logging something
+            // just before the application initialized LogManager.
+            PlatformLogger.Bridge platform = p;
+            if (platform != null) return platform;
+            synchronized (this) {
+                if (w != null) {
+                    if (p == null) p = PlatformLogger.Bridge.convert(w);
+                    return p;
+                }
+            }
+            // If we reach here it means that the wrapped logger may not
+            // have been created yet: attempt to create it.
+            // BootstrapLogger has the logic to decide whether to invoke the
+            // SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger)
+            // logger.
+            final Logger wrapped = BootstrapLogger.getLogger(this);
+            synchronized(this) {
+                // if w has already been set, simply drop 'wrapped'.
+                setWrappedIfNotSet(wrapped);
+                if (p == null) p = PlatformLogger.Bridge.convert(w);
+                return p;
+            }
+        }
+
+        /**
+         * Makes this accessor release a temporary logger.
+         * This method is called
+         * by BootstrapLogger when JUL is the default backend and LogManager
+         * is initialized, in order to replace temporary SimpleConsoleLoggers by
+         * real JUL loggers. See BootstrapLogger for more details.
+         * If {@code replace} is {@code true}, then this method will force
+         * the accessor to eagerly recreate its wrapped logger.
+         * Note: passing {@code replace=false} is no guarantee that the
+         * method will not actually replace the released logger.
+         * @param temporary The temporary logger too be released.
+         * @param replace   Whether the released logger should be eagerly
+         *                  replaced.
+         */
+        void release(SimpleConsoleLogger temporary, boolean replace) {
+            PlatformLogger.ConfigurableBridge.LoggerConfiguration conf =
+                PlatformLogger.ConfigurableBridge.getLoggerConfiguration(temporary);
+            PlatformLogger.Level level = conf != null
+                    ? conf.getPlatformLevel()
+                    : null;
+            synchronized (this) {
+                if (this.w == temporary) {
+                    this.w = null; this.p = null;
+                }
+            }
+            PlatformLogger.Bridge platform =  replace || level != null
+                    ? this.platform() : null;
+
+            if (level != null) {
+                conf = (platform != null && platform != temporary)
+                        ? PlatformLogger.ConfigurableBridge.getLoggerConfiguration(platform)
+                        : null;
+                if (conf != null) conf.setPlatformLevel(level);
+            }
+        }
+
+        /**
+         * Replace 'w' by the real SPI logger and flush the log messages pending
+         * in the temporary 'bootstrap' Logger. Called by BootstrapLogger when
+         * this accessor's bootstrap logger is accessed and BootstrapLogger
+         * notices that the VM is no longer booting.
+         * @param bootstrap This accessor's bootstrap logger (usually this is 'w').
+         */
+        Logger getConcreteLogger(BootstrapLogger bootstrap) {
+            assert VM.isBooted();
+            synchronized(this) {
+                // another thread may have already invoked flush()
+                if (this.w == bootstrap) {
+                    this.w = null; this.p = null;
+                }
+            }
+            return this.wrapped();
+        }
+
+        PlatformLogger.Bridge getConcretePlatformLogger(BootstrapLogger bootstrap) {
+            assert VM.isBooted();
+            synchronized(this) {
+                // another thread may have already invoked flush()
+                if (this.w == bootstrap) {
+                    this.w = null; this.p = null;
+                }
+            }
+            return this.platform();
+        }
+
+        // Creates the wrapped logger by invoking the SPI.
+        Logger createLogger() {
+            final Class<?> caller = callerRef.get();
+            if (caller == null) {
+                throw new IllegalStateException("The class for which this logger"
+                        + " was created has been garbage collected");
+            }
+            return this.factories.loggerSupplier.apply(name, caller);
+        }
+
+        /**
+         * Creates a new lazy logger accessor for the named logger. The given
+         * factories will be use when it becomes necessary to actually create
+         * the logger.
+         * @param <T> An interface that extends {@link Logger}.
+         * @param name The logger name.
+         * @param factories The factories that should be used to create the
+         *                  wrapped logger.
+         * @return A new LazyLoggerAccessor.
+         */
+        public static LazyLoggerAccessor makeAccessor(String name,
+                LazyLoggerFactories<? extends Logger> factories, Class<?> caller) {
+                return new LazyLoggerAccessor(name, factories, caller);
+        }
+
+    }
+
+    /**
+     * An implementation of {@link Logger} that redirects all calls to a wrapped
+     * instance of {@code Logger}.
+     */
+    private static class LazyLoggerWrapper
+        extends AbstractLoggerWrapper<Logger> {
+
+        final LoggerAccessor loggerAccessor;
+
+        public LazyLoggerWrapper(LazyLoggerAccessor loggerSinkSupplier) {
+            this(Objects.requireNonNull(loggerSinkSupplier), (Void)null);
+        }
+
+        private LazyLoggerWrapper(LazyLoggerAccessor loggerSinkSupplier,
+                Void unused) {
+            this.loggerAccessor = loggerSinkSupplier;
+        }
+
+        @Override
+        final Logger wrapped() {
+            return loggerAccessor.wrapped();
+        }
+
+        @Override
+        PlatformLogger.Bridge platformProxy() {
+            return loggerAccessor.platform();
+        }
+
+    }
+
+    // Do not expose this outside of this package.
+    private static volatile LoggerFinder provider = null;
+    private static LoggerFinder accessLoggerFinder() {
+        if (provider == null) {
+            // no need to lock: it doesn't matter if we call
+            // getLoggerFinder() twice - since LoggerFinder already caches
+            // the result.
+            // This is just an optimization to avoid the cost of calling
+            // doPrivileged every time.
+            final SecurityManager sm = System.getSecurityManager();
+            provider = sm == null ? LoggerFinder.getLoggerFinder() :
+                AccessController.doPrivileged(
+                        (PrivilegedAction<LoggerFinder>)LoggerFinder::getLoggerFinder);
+        }
+        return provider;
+    }
+
+    // Avoid using lambda here as lazy loggers could be created early
+    // in the bootstrap sequence...
+    private static final BiFunction<String, Class<?>, Logger> loggerSupplier =
+           new BiFunction<>() {
+        @Override
+        public Logger apply(String name, Class<?> caller) {
+            return LazyLoggers.getLoggerFromFinder(name, caller);
+        }
+    };
+
+    private static final LazyLoggerFactories<Logger> factories =
+           new LazyLoggerFactories<>(loggerSupplier);
+
+
+
+    // A concrete implementation of Logger that delegates to a  System.Logger,
+    // but only creates the System.Logger instance lazily when it's used for
+    // the first time.
+    // The JdkLazyLogger uses a LazyLoggerAccessor objects, which relies
+    // on the logic embedded in BootstrapLogger to avoid loading the concrete
+    // logger provider until the VM has finished booting.
+    //
+    private static final class JdkLazyLogger extends LazyLoggerWrapper {
+        JdkLazyLogger(String name, Class<?> caller) {
+            this(LazyLoggerAccessor.makeAccessor(name, factories, caller),
+                 (Void)null);
+        }
+        private JdkLazyLogger(LazyLoggerAccessor holder, Void unused) {
+            super(holder);
+        }
+    }
+
+    /**
+     * Gets a logger from the LoggerFinder. Creates the actual concrete
+     * logger.
+     * @param name    name of the logger
+     * @param caller  class on behalf of which the logger is created
+     * @return  The logger returned by the LoggerFinder.
+     */
+    static Logger getLoggerFromFinder(String name, Class<?> caller) {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm == null) {
+            return accessLoggerFinder().getLogger(name, caller);
+        } else {
+            return AccessController.doPrivileged((PrivilegedAction<Logger>)
+                    () -> {return accessLoggerFinder().getLogger(name, caller);},
+                    null, LOGGERFINDER_PERMISSION);
+        }
+    }
+
+    /**
+     * Returns a (possibly lazy) Logger for the caller.
+     *
+     * @param name the logger name
+     * @param caller The class on behalf of which the logger is created.
+     *               If the caller is not loaded from the Boot ClassLoader,
+     *               the LoggerFinder is accessed and the logger returned
+     *               by {@link LoggerFinder#getLogger(java.lang.String, java.lang.Class)}
+     *               is returned to the caller directly.
+     *               Otherwise, the logger returned by
+     *               {@link #getLazyLogger(java.lang.String, java.lang.Class)}
+     *               is returned to the caller.
+     *
+     * @return  a (possibly lazy) Logger instance.
+     */
+    public static final Logger getLogger(String name, Class<?> caller) {
+        if (caller.getClassLoader() == null) {
+            return getLazyLogger(name, caller);
+        } else {
+            return getLoggerFromFinder(name, caller);
+        }
+    }
+
+    /**
+     * Returns a (possibly lazy) Logger suitable for system classes.
+     * Whether the returned logger is lazy or not depend on the result
+     * returned by {@link BootstrapLogger#useLazyLoggers()}.
+     *
+     * @param name the logger name
+     * @param caller the class on behalf of which the logger is created.
+     * @return  a (possibly lazy) Logger instance.
+     */
+    public static final Logger getLazyLogger(String name, Class<?> caller) {
+
+        // BootstrapLogger has the logic to determine whether a LazyLogger
+        // should be used. Usually, it is worth it only if:
+        //   - the VM is not yet booted
+        //   - or, the backend is JUL and there is no configuration
+        //   - or, the backend is a custom backend, as we don't know what
+        //     that is going to load...
+        // So if for instance the VM is booted and we use JUL with a custom
+        // configuration, we're not going to delay the creation of loggers...
+        final boolean useLazyLogger = BootstrapLogger.useLazyLoggers();
+        if (useLazyLogger) {
+            return new JdkLazyLogger(name, caller);
+        } else {
+            // Directly invoke the LoggerFinder.
+            return getLoggerFromFinder(name, caller);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/logger/LocalizedLoggerWrapper.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+
+package jdk.internal.logger;
+
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+
+/**
+ * This implementation of {@link Logger} redirects all logging method
+ * calls to calls to {@code log(Level, String,  ResourceBundle, ...)}
+ * methods, passing the Logger's ResourceBundle as parameter.
+ * So for instance a call to {@link Logger#log(Level, String)
+ * log(Level.INFO, msg)} will be redirected
+ * to a call to {@link #log(java.lang.System.Logger.Level,
+ * java.util.ResourceBundle, java.lang.String, java.lang.Object...)
+ * this.log(Level.INFO, this.bundle, msg, (Object[]) null)}.
+ * <p>
+ * Note that methods that take a {@link Supplier Supplier&lt;String&gt;}
+ * or an Object are not redirected. It is assumed that a string returned
+ * by a {@code Supplier<String>} is already localized, or cannot be localized.
+ *
+ * @param <L> Type of the wrapped Logger: {@code Logger} or an
+ *        extension of the {@code Logger} interface.
+ */
+public class LocalizedLoggerWrapper<L extends Logger> extends LoggerWrapper<L> {
+
+    private final ResourceBundle bundle;
+
+    public LocalizedLoggerWrapper(L wrapped, ResourceBundle bundle) {
+        super(wrapped);
+        this.bundle = bundle;
+    }
+
+    public final ResourceBundle getBundle() {
+        return bundle;
+    }
+
+    // We assume that messages returned by Supplier<String> and Object are
+    // either already localized or not localizable. To be evaluated.
+
+    // -----------------------------------------------------------------
+    // Generic methods taking a Level as parameter
+    // -----------------------------------------------------------------
+
+    @Override
+    public final void log(Level level, String msg) {
+        log(level, bundle, msg, (Object[]) null);
+    }
+
+    @Override
+    public final void log(Level level,
+                   String msg, Throwable thrown) {
+        log(level, bundle, msg, thrown);
+    }
+
+    @Override
+    public final void log(Level level,
+                    String format, Object... params) {
+        log(level, bundle, format, params);
+    }
+
+    @Override
+    public final void log(Level level, Object obj) {
+        wrapped.log(level, obj);
+    }
+
+    @Override
+    public final void log(Level level, Supplier<String> msgSupplier) {
+        wrapped.log(level, msgSupplier);
+    }
+
+    @Override
+    public final void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
+        wrapped.log(level, msgSupplier, thrown);
+    }
+
+    @Override
+    public final void log(Level level, ResourceBundle bundle, String format, Object... params) {
+        wrapped.log(level, bundle, format, params);
+    }
+
+    @Override
+    public final void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
+        wrapped.log(level, bundle, key, thrown);
+    }
+
+    @Override
+    public final boolean isLoggable(Level level) {
+        return wrapped.isLoggable(level);
+    }
+
+    // Override methods from PlatformLogger.Bridge that don't take a
+    // resource bundle...
+
+    @Override
+    public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+            String key) {
+        logrb(level, sourceClass, sourceMethod, bundle, key, (Object[]) null);
+    }
+
+    @Override
+    public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+            String key, Throwable thrown) {
+        logrb(level, sourceClass, sourceMethod, bundle, key, thrown);
+    }
+
+    @Override
+    public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+            String key, Object... params) {
+        logrb(level, sourceClass, sourceMethod, bundle, key, params);
+    }
+
+    @Override
+    public final void log(sun.util.logging.PlatformLogger.Level level, String msg, Throwable thrown) {
+        logrb(level, bundle, msg, thrown);
+    }
+
+    @Override
+    public final void log(sun.util.logging.PlatformLogger.Level level, String msg) {
+        logrb(level, bundle, msg, (Object[]) null);
+    }
+
+    @Override
+    public final void log(sun.util.logging.PlatformLogger.Level level, String format, Object... params) {
+        logrb(level, bundle, format, params);
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/logger/LoggerFinderLoader.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+package jdk.internal.logger;
+
+import java.io.FilePermission;
+import java.security.AccessController;
+import java.security.Permission;
+import java.security.PrivilegedAction;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+import sun.security.util.SecurityConstants;
+
+/**
+ * Helper class used to load the {@link java.lang.System.LoggerFinder}.
+ */
+public final class LoggerFinderLoader {
+    private static volatile System.LoggerFinder service;
+    private static final Object lock = new int[0];
+    static final Permission CLASSLOADER_PERMISSION =
+            SecurityConstants.GET_CLASSLOADER_PERMISSION;
+    static final Permission READ_PERMISSION =
+            new FilePermission("<<ALL FILES>>",
+                    SecurityConstants.FILE_READ_ACTION);
+    public static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+
+    // This is used to control how the LoggerFinderLoader handles
+    // errors when instantiating the LoggerFinder provider.
+    // ERROR => throws ServiceConfigurationError
+    // WARNING => Do not fail, use plain default (simple logger) implementation,
+    //            prints warning on console. (this is the default)
+    // DEBUG => Do not fail, use plain default (simple logger) implementation,
+    //          prints warning and exception stack trace on console.
+    // QUIET => Do not fail and stay silent.
+    private static enum ErrorPolicy { ERROR, WARNING, DEBUG, QUIET };
+
+    // This class is static and cannot be instantiated.
+    private LoggerFinderLoader() {
+        throw new InternalError("LoggerFinderLoader cannot be instantiated");
+    }
+
+
+    // Return the loaded LoggerFinder, or load it if not already loaded.
+    private static System.LoggerFinder service() {
+        if (service != null) return service;
+        synchronized(lock) {
+            if (service != null) return service;
+            service = loadLoggerFinder();
+        }
+        // Since the LoggerFinder is already loaded - we can stop using
+        // temporary loggers.
+        BootstrapLogger.redirectTemporaryLoggers();
+        return service;
+    }
+
+    // Get configuration error policy
+    private static ErrorPolicy configurationErrorPolicy() {
+        final PrivilegedAction<String> getConfigurationErrorPolicy =
+                () -> System.getProperty("jdk.logger.finder.error");
+        String errorPolicy = AccessController.doPrivileged(getConfigurationErrorPolicy);
+        if (errorPolicy == null || errorPolicy.isEmpty()) {
+            return ErrorPolicy.WARNING;
+        }
+        try {
+            return ErrorPolicy.valueOf(errorPolicy.toUpperCase(Locale.ROOT));
+        } catch (IllegalArgumentException x) {
+            return ErrorPolicy.WARNING;
+        }
+    }
+
+    // Whether multiple provider should be considered as an error.
+    // This is further submitted to the configuration error policy.
+    private static boolean ensureSingletonProvider() {
+        final PrivilegedAction<Boolean> ensureSingletonProvider =
+                () -> Boolean.getBoolean("jdk.logger.finder.singleton");
+        return AccessController.doPrivileged(ensureSingletonProvider);
+    }
+
+    private static Iterator<System.LoggerFinder> findLoggerFinderProviders() {
+        final Iterator<System.LoggerFinder> iterator;
+        if (System.getSecurityManager() == null) {
+            iterator = ServiceLoader.load(System.LoggerFinder.class,
+                        ClassLoader.getSystemClassLoader()).iterator();
+        } else {
+            final PrivilegedAction<Iterator<System.LoggerFinder>> pa =
+                    () -> ServiceLoader.load(System.LoggerFinder.class,
+                        ClassLoader.getSystemClassLoader()).iterator();
+            iterator = AccessController.doPrivileged(pa, null,
+                        LOGGERFINDER_PERMISSION, CLASSLOADER_PERMISSION,
+                        READ_PERMISSION);
+        }
+        return iterator;
+    }
+
+    // Loads the LoggerFinder using ServiceLoader. If no LoggerFinder
+    // is found returns the default (possibly JUL based) implementation
+    private static System.LoggerFinder loadLoggerFinder() {
+        System.LoggerFinder result;
+        try {
+            // Iterator iterates with the access control context stored
+            // at ServiceLoader creation time.
+            final Iterator<System.LoggerFinder> iterator =
+                    findLoggerFinderProviders();
+            if (iterator.hasNext()) {
+                result = iterator.next();
+                if (iterator.hasNext() && ensureSingletonProvider()) {
+                    throw new ServiceConfigurationError(
+                            "More than on LoggerFinder implementation");
+                }
+            } else {
+                result = loadDefaultImplementation();
+            }
+        } catch (Error | RuntimeException x) {
+            // next caller will get the plain default impl (not linked
+            // to java.util.logging)
+            service = result = new DefaultLoggerFinder();
+            ErrorPolicy errorPolicy = configurationErrorPolicy();
+            if (errorPolicy == ErrorPolicy.ERROR) {
+                // rethrow any exception as a ServiceConfigurationError.
+                if (x instanceof Error) {
+                    throw x;
+                } else {
+                    throw new ServiceConfigurationError(
+                        "Failed to instantiate LoggerFinder provider; Using default.", x);
+                }
+            } else if (errorPolicy != ErrorPolicy.QUIET) {
+                // if QUIET just silently use the plain default impl
+                // otherwise, log a warning, possibly adding the exception
+                // stack trace (if DEBUG is specified).
+                SimpleConsoleLogger logger =
+                        new SimpleConsoleLogger("jdk.internal.logger", false);
+                logger.log(System.Logger.Level.WARNING,
+                        "Failed to instantiate LoggerFinder provider; Using default.");
+                if (errorPolicy == ErrorPolicy.DEBUG) {
+                    logger.log(System.Logger.Level.WARNING,
+                        "Exception raised trying to instantiate LoggerFinder", x);
+                }
+            }
+        }
+        return result;
+    }
+
+    private static System.LoggerFinder loadDefaultImplementation() {
+        final SecurityManager sm = System.getSecurityManager();
+        final Iterator<DefaultLoggerFinder> iterator;
+        if (sm == null) {
+            iterator = ServiceLoader.loadInstalled(DefaultLoggerFinder.class).iterator();
+        } else {
+            // We use limited do privileged here - the minimum set of
+            // permissions required to 'see' the META-INF/services resources
+            // seems to be CLASSLOADER_PERMISSION and READ_PERMISSION.
+            // Note that do privileged is required because
+            // otherwise the SecurityManager will prevent the ServiceLoader
+            // from seeing the installed provider.
+            PrivilegedAction<Iterator<DefaultLoggerFinder>> pa = () ->
+                    ServiceLoader.loadInstalled(DefaultLoggerFinder.class).iterator();
+            iterator = AccessController.doPrivileged(pa, null,
+                    LOGGERFINDER_PERMISSION, CLASSLOADER_PERMISSION,
+                    READ_PERMISSION);
+        }
+        DefaultLoggerFinder result = null;
+        try {
+            // Iterator iterates with the access control context stored
+            // at ServiceLoader creation time.
+            if (iterator.hasNext()) {
+                result = iterator.next();
+            }
+        } catch (RuntimeException x) {
+            throw new ServiceConfigurationError(
+                    "Failed to instantiate default LoggerFinder", x);
+        }
+        if (result == null) {
+            result = new DefaultLoggerFinder();
+        }
+        return result;
+    }
+
+    public static System.LoggerFinder getLoggerFinder() {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(LOGGERFINDER_PERMISSION);
+        }
+        return service();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/logger/LoggerWrapper.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+
+package jdk.internal.logger;
+
+import java.util.Objects;
+import java.lang.System.Logger;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * An implementation of {@link Logger} that redirects all calls to a wrapped
+ instance of Logger.
+ *
+ * @param <L> Type of the wrapped Logger: {@code Logger} or an
+ *            extension of that interface.
+ */
+public class LoggerWrapper<L extends Logger> extends AbstractLoggerWrapper<L> {
+
+    final L wrapped;
+    final PlatformLogger.Bridge platformProxy;
+
+    public LoggerWrapper(L wrapped) {
+        this(Objects.requireNonNull(wrapped), (Void)null);
+    }
+
+    LoggerWrapper(L wrapped, Void unused) {
+        this.wrapped = wrapped;
+        this.platformProxy = (wrapped instanceof PlatformLogger.Bridge) ?
+            (PlatformLogger.Bridge) wrapped : null;
+    }
+
+    @Override
+    public final L wrapped() {
+        return wrapped;
+    }
+
+    @Override
+    public final PlatformLogger.Bridge platformProxy() {
+        return platformProxy;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,486 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+package jdk.internal.logger;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.time.ZonedDateTime;
+import java.util.ResourceBundle;
+import java.util.function.Function;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.function.Supplier;
+import jdk.internal.misc.JavaLangAccess;
+import jdk.internal.misc.SharedSecrets;
+import sun.util.logging.PlatformLogger;
+import sun.util.logging.PlatformLogger.ConfigurableBridge.LoggerConfiguration;
+
+/**
+ * A simple console logger to emulate the behavior of JUL loggers when
+ * in the default configuration. SimpleConsoleLoggers are also used when
+ * JUL is not present and no DefaultLoggerFinder is installed.
+ */
+public class SimpleConsoleLogger extends LoggerConfiguration
+    implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge {
+
+    static final PlatformLogger.Level DEFAULT_LEVEL = PlatformLogger.Level.INFO;
+
+    final String name;
+    volatile PlatformLogger.Level  level;
+    final boolean usePlatformLevel;
+    SimpleConsoleLogger(String name, boolean usePlatformLevel) {
+        this.name = name;
+        this.usePlatformLevel = usePlatformLevel;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    private Enum<?> logLevel(PlatformLogger.Level level) {
+        return usePlatformLevel ? level : level.systemLevel();
+    }
+
+    private Enum<?> logLevel(Level level) {
+        return usePlatformLevel ? PlatformLogger.toPlatformLevel(level) : level;
+    }
+
+    // ---------------------------------------------------
+    //                 From Logger
+    // ---------------------------------------------------
+
+    @Override
+    public boolean isLoggable(Level level) {
+        return isLoggable(PlatformLogger.toPlatformLevel(level));
+    }
+
+    @Override
+    public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
+        if (isLoggable(level)) {
+            if (bundle != null) {
+                key = bundle.getString(key);
+            }
+            publish(getCallerInfo(), logLevel(level), key, thrown);
+        }
+    }
+
+    @Override
+    public void log(Level level, ResourceBundle bundle, String format, Object... params) {
+        if (isLoggable(level)) {
+            if (bundle != null) {
+                format = bundle.getString(format);
+            }
+            publish(getCallerInfo(), logLevel(level), format, params);
+        }
+    }
+
+    // ---------------------------------------------------
+    //             From PlatformLogger.Bridge
+    // ---------------------------------------------------
+
+    @Override
+    public boolean isLoggable(PlatformLogger.Level level) {
+        final PlatformLogger.Level effectiveLevel =  effectiveLevel();
+        return level != PlatformLogger.Level.OFF
+                && level.ordinal() >= effectiveLevel.ordinal();
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return level != PlatformLogger.Level.OFF;
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, String msg) {
+        if (isLoggable(level)) {
+            publish(getCallerInfo(), logLevel(level), msg);
+        }
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, String msg, Throwable thrown) {
+        if (isLoggable(level)) {
+            publish(getCallerInfo(), logLevel(level), msg, thrown);
+        }
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, String msg, Object... params) {
+        if (isLoggable(level)) {
+            publish(getCallerInfo(), logLevel(level), msg, params);
+        }
+    }
+
+    private PlatformLogger.Level effectiveLevel() {
+        if (level == null) return DEFAULT_LEVEL;
+        return level;
+    }
+
+    @Override
+    public PlatformLogger.Level getPlatformLevel() {
+        return level;
+    }
+
+    @Override
+    public void setPlatformLevel(PlatformLogger.Level newLevel) {
+        level = newLevel;
+    }
+
+    @Override
+    public LoggerConfiguration getLoggerConfiguration() {
+        return this;
+    }
+
+    /**
+     * Default platform logging support - output messages to System.err -
+     * equivalent to ConsoleHandler with SimpleFormatter.
+     */
+    static PrintStream outputStream() {
+        return System.err;
+    }
+
+    // Returns the caller's class and method's name; best effort
+    // if cannot infer, return the logger's name.
+    private String getCallerInfo() {
+        String sourceClassName = null;
+        String sourceMethodName = null;
+
+        JavaLangAccess access = SharedSecrets.getJavaLangAccess();
+        Throwable throwable = new Throwable();
+        int depth = access.getStackTraceDepth(throwable);
+
+        String logClassName = "sun.util.logging.PlatformLogger";
+        String simpleLoggerClassName = "jdk.internal.logger.SimpleConsoleLogger";
+        boolean lookingForLogger = true;
+        for (int ix = 0; ix < depth; ix++) {
+            // Calling getStackTraceElement directly prevents the VM
+            // from paying the cost of building the entire stack frame.
+            final StackTraceElement frame =
+                access.getStackTraceElement(throwable, ix);
+            final String cname = frame.getClassName();
+            if (lookingForLogger) {
+                // Skip all frames until we have found the first logger frame.
+                if (cname.equals(logClassName) || cname.equals(simpleLoggerClassName)) {
+                    lookingForLogger = false;
+                }
+            } else {
+                if (skipLoggingFrame(cname)) continue;
+                if (!cname.equals(logClassName) && !cname.equals(simpleLoggerClassName)) {
+                    // We've found the relevant frame.
+                    sourceClassName = cname;
+                    sourceMethodName = frame.getMethodName();
+                    break;
+                }
+            }
+        }
+
+        if (sourceClassName != null) {
+            return sourceClassName + " " + sourceMethodName;
+        } else {
+            return name;
+        }
+    }
+
+    private String getCallerInfo(String sourceClassName, String sourceMethodName) {
+        if (sourceClassName == null) return name;
+        if (sourceMethodName == null) return sourceClassName;
+        return sourceClassName + " " + sourceMethodName;
+    }
+
+    private String toString(Throwable thrown) {
+        String throwable = "";
+        if (thrown != null) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            pw.println();
+            thrown.printStackTrace(pw);
+            pw.close();
+            throwable = sw.toString();
+        }
+        return throwable;
+    }
+
+    private synchronized String format(Enum<?> level,
+            String msg, Throwable thrown, String callerInfo) {
+
+        ZonedDateTime zdt = ZonedDateTime.now();
+        String throwable = toString(thrown);
+
+        return String.format(Formatting.formatString,
+                         zdt,
+                         callerInfo,
+                         name,
+                         level.name(),
+                         msg,
+                         throwable);
+    }
+
+    // publish accepts both PlatformLogger Levels and LoggerFinder Levels.
+    private void publish(String callerInfo, Enum<?> level, String msg) {
+        outputStream().print(format(level, msg, null, callerInfo));
+    }
+    // publish accepts both PlatformLogger Levels and LoggerFinder Levels.
+    private void publish(String callerInfo, Enum<?> level, String msg, Throwable thrown) {
+        outputStream().print(format(level, msg, thrown, callerInfo));
+    }
+    // publish accepts both PlatformLogger Levels and LoggerFinder Levels.
+    private void publish(String callerInfo, Enum<?> level, String msg, Object... params) {
+        msg = params == null || params.length == 0 ? msg
+                : Formatting.formatMessage(msg, params);
+        outputStream().print(format(level, msg, null, callerInfo));
+    }
+
+    public static SimpleConsoleLogger makeSimpleLogger(String name, boolean usePlatformLevel) {
+        return new SimpleConsoleLogger(name, usePlatformLevel);
+    }
+
+    public static SimpleConsoleLogger makeSimpleLogger(String name) {
+        return new SimpleConsoleLogger(name, false);
+    }
+
+    public static String getSimpleFormat(Function<String, String> defaultPropertyGetter) {
+        return Formatting.getSimpleFormat(defaultPropertyGetter);
+    }
+
+    public static boolean skipLoggingFrame(String cname) {
+        return Formatting.skipLoggingFrame(cname);
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, Supplier<String> msgSupplier) {
+        if (isLoggable(level)) {
+            publish(getCallerInfo(), logLevel(level), msgSupplier.get());
+        }
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, Throwable thrown,
+            Supplier<String> msgSupplier) {
+        if (isLoggable(level)) {
+            publish(getCallerInfo(), logLevel(level), msgSupplier.get(), thrown);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+            String sourceMethod, String msg) {
+        if (isLoggable(level)) {
+            publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+            String sourceMethod, Supplier<String> msgSupplier) {
+        if (isLoggable(level)) {
+            publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get());
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod,
+            String msg, Object... params) {
+        if (isLoggable(level)) {
+            publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+            String sourceMethod, String msg, Throwable thrown) {
+        if (isLoggable(level)) {
+            publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown);
+        }
+    }
+
+    @Override
+    public void logp(PlatformLogger.Level level, String sourceClass,
+            String sourceMethod, Throwable thrown, Supplier<String> msgSupplier) {
+        if (isLoggable(level)) {
+            publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get(), thrown);
+        }
+    }
+
+    @Override
+    public void logrb(PlatformLogger.Level level, String sourceClass,
+            String sourceMethod, ResourceBundle bundle, String key, Object... params) {
+        if (isLoggable(level)) {
+            String msg = bundle == null ? key : bundle.getString(key);
+            publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params);
+        }
+    }
+
+    @Override
+    public void logrb(PlatformLogger.Level level, String sourceClass,
+            String sourceMethod, ResourceBundle bundle, String key, Throwable thrown) {
+        if (isLoggable(level)) {
+            String msg = bundle == null ? key : bundle.getString(key);
+            publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown);
+        }
+    }
+
+    @Override
+    public void logrb(PlatformLogger.Level level, ResourceBundle bundle,
+            String key, Object... params) {
+        if (isLoggable(level)) {
+            String msg = bundle == null ? key : bundle.getString(key);
+            publish(getCallerInfo(), logLevel(level), msg, params);
+        }
+    }
+
+    @Override
+    public void logrb(PlatformLogger.Level level, ResourceBundle bundle,
+            String key, Throwable thrown) {
+        if (isLoggable(level)) {
+            String msg = bundle == null ? key : bundle.getString(key);
+            publish(getCallerInfo(), logLevel(level), msg, thrown);
+        }
+    }
+
+    private static final class Formatting {
+        static final String DEFAULT_FORMAT =
+            "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n";
+        static final String FORMAT_PROP_KEY =
+            "java.util.logging.SimpleFormatter.format";
+        static final String formatString = getSimpleFormat(null);
+
+        // Make it easier to wrap Logger...
+        static private final String[] skips;
+        static {
+            String additionalPkgs = AccessController.doPrivileged(
+                (PrivilegedAction<String>)
+                () -> System.getProperty("jdk.logger.packages"));
+            skips = additionalPkgs == null ? new String[0] : additionalPkgs.split(",");
+
+        }
+
+        static boolean skipLoggingFrame(String cname) {
+            // skip logging/logger infrastructure
+
+            // fast escape path: all the prefixes below start with 's' or 'j' and
+            // have more than 12 characters.
+            char c = cname.length() < 12 ? 0 : cname.charAt(0);
+            if (c == 's') {
+                // skip internal machinery classes
+                if (cname.startsWith("sun.util.logging."))   return true;
+                if (cname.startsWith("sun.reflect."))        return true;
+                if (cname.startsWith("sun.rmi.runtime.Log")) return true;
+            } else if (c == 'j') {
+                // Message delayed at Bootstrap: no need to go further up.
+                if (cname.startsWith("jdk.internal.logger.BootstrapLogger$LogEvent")) return false;
+                // skip public machinery classes
+                if (cname.startsWith("jdk.internal.logger."))          return true;
+                if (cname.startsWith("java.util.logging."))            return true;
+                if (cname.startsWith("java.lang.System$Logger"))       return true;
+                if (cname.startsWith("java.lang.reflect."))            return true;
+                if (cname.startsWith("java.lang.invoke.MethodHandle")) return true;
+                if (cname.startsWith("java.lang.invoke.LambdaForm"))    return true;
+                if (cname.startsWith("java.security.AccessController")) return true;
+            }
+
+            // check additional prefixes if any are specified.
+            if (skips.length > 0) {
+                for (int i=0; i<skips.length; i++) {
+                    if (!skips[i].isEmpty() && cname.startsWith(skips[i])) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        static String getSimpleFormat(Function<String, String> defaultPropertyGetter) {
+            // Using a lambda here causes
+            //    jdk/test/java/lang/invoke/lambda/LogGeneratedClassesTest.java
+            // to fail - because that test has a testcase which somehow references
+            // PlatformLogger and counts the number of generated lambda classes
+            // So we explicitely use new PrivilegedAction<String> here.
+            String format =
+                AccessController.doPrivileged(new PrivilegedAction<String>() {
+                @Override
+                public String run() {
+                    return System.getProperty(FORMAT_PROP_KEY);
+                }
+            });
+            if (format == null && defaultPropertyGetter != null) {
+                format = defaultPropertyGetter.apply(FORMAT_PROP_KEY);
+            }
+            if (format != null) {
+                try {
+                    // validate the user-defined format string
+                    String.format(format, ZonedDateTime.now(), "", "", "", "", "");
+                } catch (IllegalArgumentException e) {
+                    // illegal syntax; fall back to the default format
+                    format = DEFAULT_FORMAT;
+                }
+            } else {
+                format = DEFAULT_FORMAT;
+            }
+            return format;
+        }
+
+
+        // Copied from java.util.logging.Formatter.formatMessage
+        static String formatMessage(String format, Object... parameters) {
+            // Do the formatting.
+            try {
+                if (parameters == null || parameters.length == 0) {
+                    // No parameters.  Just return format string.
+                    return format;
+                }
+                // Is it a java.text style format?
+                // Ideally we could match with
+                // Pattern.compile("\\{\\d").matcher(format).find())
+                // However the cost is 14% higher, so we cheaply check for
+                //
+                boolean isJavaTestFormat = false;
+                final int len = format.length();
+                for (int i=0; i<len-2; i++) {
+                    final char c = format.charAt(i);
+                    if (c == '{') {
+                        final int d = format.charAt(i+1);
+                        if (d >= '0' && d <= '9') {
+                            isJavaTestFormat = true;
+                            break;
+                        }
+                    }
+                }
+                if (isJavaTestFormat) {
+                    return java.text.MessageFormat.format(format, parameters);
+                }
+                return format;
+            } catch (Exception ex) {
+                // Formatting failed: use format string.
+                return format;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/logger/package-info.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+/**
+ * <b>[JDK INTERNAL]</b>
+ * The {@code jdk.internal.logger} package defines an internal provider
+ * whose default naive implementation is replaced by the {@code java.logging}
+ * module when the {@code java.logging} module is present.
+ * <p>
+ * <b>Default Implementation</b>
+ * <p>
+ * The JDK default implementation of the System.LoggerFinder will attempt to
+ * load an installed instance of the {@link jdk.internal.logger.DefaultLoggerFinder}
+ * defined in this package.
+ * When the {@code java.util.logging} package is present, this will usually
+ * resolve to an instance of {@link sun.util.logging.internal.LoggingProviderImpl} -
+ * which provides an implementation of the Logger whose backend is a
+ * {@link java.util.logging.Logger java.util.logging.Logger}.
+ * Configuration can thus be performed by direct access to the regular
+ * {@code java.util.logging} APIs,
+ * using {@link java.util.logging.Logger java.util.logging.Logger} and
+ * {@link java.util.logging.LogManager} to access and configure the backend
+ * Loggers.
+ * <br>
+ * If however {@code java.util.logging} is not linked with the application, then
+ * the default implementation will return a simple logger that will print out
+ * all log messages of INFO level and above to the console ({@code System.err}),
+ * as implemented by the base {@link jdk.internal.logger.DefaultLoggerFinder} class.
+ * <p>
+ * <b>Message Levels and Mapping to java.util.logging</b>
+ * <p>
+ * The {@link java.lang.System.LoggerFinder} class documentation describe how
+ * {@linkplain java.lang.System.Logger.Level System.Logger levels} are mapped
+ * to {@linkplain java.util.logging.Level JUL levels} when {@code
+ * java.util.logging} is the backend.
+ *
+ * @see jdk.internal.logger.DefaultLoggerFinder
+ * @see sun.util.logging.internal.LoggingProviderImpl
+ * @see java.lang.System.LoggerFinder
+ * @see java.lang.System.Logger
+ * @see sun.util.logging.PlatformLogger.Bridge
+ * @see sun.util.logging.internal
+ *
+ * @since 1.9
+ */
+package jdk.internal.logger;
--- a/src/java.base/share/classes/sun/util/logging/LoggingProxy.java	Fri Nov 20 15:34:12 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-/*
- * Copyright (c) 2009, 2013, 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.
- */
-
-
-package sun.util.logging;
-
-/**
- * A proxy interface for the java.util.logging support.
- *
- * @see sun.util.logging.LoggingSupport
- */
-public interface LoggingProxy {
-    // Methods to bridge java.util.logging.Logger methods
-    public Object getLogger(String name);
-
-    public Object getLevel(Object logger);
-
-    public void setLevel(Object logger, Object newLevel);
-
-    public boolean isLoggable(Object logger, Object level);
-
-    public void log(Object logger, Object level, String msg);
-
-    public void log(Object logger, Object level, String msg, Throwable t);
-
-    public void log(Object logger, Object level, String msg, Object... params);
-
-    // Methods to bridge java.util.logging.LoggingMXBean methods
-    public java.util.List<String> getLoggerNames();
-
-    public String getLoggerLevel(String loggerName);
-
-    public void setLoggerLevel(String loggerName, String levelName);
-
-    public String getParentLoggerName(String loggerName);
-
-    // Methods to bridge Level.parse() and Level.getName() method
-    public Object parseLevel(String levelName);
-
-    public String getLevelName(Object level);
-
-    public int getLevelValue(Object level);
-
-    // return the logging property
-    public String getProperty(String key);
-}
--- a/src/java.base/share/classes/sun/util/logging/LoggingSupport.java	Fri Nov 20 15:34:12 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,190 +0,0 @@
-/*
- * Copyright (c) 2009, 2015, 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.
- */
-
-
-package sun.util.logging;
-
-import java.lang.reflect.Field;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.time.ZonedDateTime;
-
-/**
- * Internal API to support JRE implementation to detect if the java.util.logging
- * support is available but with no dependency on the java.util.logging
- * classes.  This LoggingSupport class provides several static methods to
- * access the java.util.logging functionality that requires the caller
- * to ensure that the logging support is {@linkplain #isAvailable available}
- * before invoking it.
- *
- * @see sun.util.logging.PlatformLogger if you want to log messages even
- * if the logging support is not available
- */
-public class LoggingSupport {
-    private LoggingSupport() { }
-
-    private static final LoggingProxy proxy =
-        AccessController.doPrivileged(new PrivilegedAction<LoggingProxy>() {
-            public LoggingProxy run() {
-                try {
-                    // create a LoggingProxyImpl instance when
-                    // java.util.logging classes exist
-                    Class<?> c = Class.forName("java.util.logging.LoggingProxyImpl", true, null);
-                    Field f = c.getDeclaredField("INSTANCE");
-                    f.setAccessible(true);
-                    return (LoggingProxy) f.get(null);
-                } catch (ClassNotFoundException cnf) {
-                    return null;
-                } catch (NoSuchFieldException e) {
-                    throw new AssertionError(e);
-                } catch (IllegalAccessException e) {
-                    throw new AssertionError(e);
-                }
-            }});
-
-    /**
-     * Returns true if java.util.logging support is available.
-     */
-    public static boolean isAvailable() {
-        return proxy != null;
-    }
-
-    private static void ensureAvailable() {
-        if (proxy == null)
-            throw new AssertionError("Should not here");
-    }
-
-    public static java.util.List<String> getLoggerNames() {
-        ensureAvailable();
-        return proxy.getLoggerNames();
-    }
-    public static String getLoggerLevel(String loggerName) {
-        ensureAvailable();
-        return proxy.getLoggerLevel(loggerName);
-    }
-
-    public static void setLoggerLevel(String loggerName, String levelName) {
-        ensureAvailable();
-        proxy.setLoggerLevel(loggerName, levelName);
-    }
-
-    public static String getParentLoggerName(String loggerName) {
-        ensureAvailable();
-        return proxy.getParentLoggerName(loggerName);
-    }
-
-    public static Object getLogger(String name) {
-        ensureAvailable();
-        return proxy.getLogger(name);
-    }
-
-    public static Object getLevel(Object logger) {
-        ensureAvailable();
-        return proxy.getLevel(logger);
-    }
-
-    public static void setLevel(Object logger, Object newLevel) {
-        ensureAvailable();
-        proxy.setLevel(logger, newLevel);
-    }
-
-    public static boolean isLoggable(Object logger, Object level) {
-        ensureAvailable();
-        return proxy.isLoggable(logger,level);
-    }
-
-    public static void log(Object logger, Object level, String msg) {
-        ensureAvailable();
-        proxy.log(logger, level, msg);
-    }
-
-    public static void log(Object logger, Object level, String msg, Throwable t) {
-        ensureAvailable();
-        proxy.log(logger, level, msg, t);
-    }
-
-    public static void log(Object logger, Object level, String msg, Object... params) {
-        ensureAvailable();
-        proxy.log(logger, level, msg, params);
-    }
-
-    public static Object parseLevel(String levelName) {
-        ensureAvailable();
-        return proxy.parseLevel(levelName);
-    }
-
-    public static String getLevelName(Object level) {
-        ensureAvailable();
-        return proxy.getLevelName(level);
-    }
-
-    public static int getLevelValue(Object level) {
-        ensureAvailable();
-        return proxy.getLevelValue(level);
-    }
-
-    // Since JDK 9, logging uses java.time to get more precise time stamps.
-    // It is possible to configure the simple format to print nano seconds (.%1$tN)
-    // by specifying:
-    // java.util.logging.SimpleFormatter.format=%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS.%1$tN %1$Tp %2$s%n%4$s: %5$s%6$s%n
-    // in the logging configuration
-    private static final String DEFAULT_FORMAT =
-        "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n";
-
-    private static final String FORMAT_PROP_KEY = "java.util.logging.SimpleFormatter.format";
-    public static String getSimpleFormat() {
-        return getSimpleFormat(true);
-    }
-
-    // useProxy if true will cause initialization of
-    // java.util.logging and read its configuration
-    static String getSimpleFormat(boolean useProxy) {
-        String format =
-            AccessController.doPrivileged(
-                new PrivilegedAction<String>() {
-                    public String run() {
-                        return System.getProperty(FORMAT_PROP_KEY);
-                    }
-                });
-
-        if (useProxy && proxy != null && format == null) {
-            format = proxy.getProperty(FORMAT_PROP_KEY);
-        }
-
-        if (format != null) {
-            try {
-                // validate the user-defined format string
-                String.format(format, ZonedDateTime.now(), "", "", "", "", "");
-            } catch (IllegalArgumentException e) {
-                // illegal syntax; fall back to the default format
-                format = DEFAULT_FORMAT;
-            }
-        } else {
-            format = DEFAULT_FORMAT;
-        }
-        return format;
-    }
-
-}
--- a/src/java.base/share/classes/sun/util/logging/PlatformLogger.java	Fri Nov 20 15:34:12 2015 +0100
+++ b/src/java.base/share/classes/sun/util/logging/PlatformLogger.java	Fri Nov 20 19:26:16 2015 +0100
@@ -27,20 +27,13 @@
 package sun.util.logging;
 
 import java.lang.ref.WeakReference;
-import java.io.PrintStream;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.time.Clock;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
-import jdk.internal.misc.JavaLangAccess;
-import jdk.internal.misc.SharedSecrets;
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+import jdk.internal.logger.LazyLoggers;
+import jdk.internal.logger.LoggerWrapper;
 
 /**
  * Platform logger provides an API for the JRE components to log
@@ -56,18 +49,28 @@
  * the stack frame information issuing the log message.
  *
  * When the logging facility is enabled (at startup or runtime),
- * the java.util.logging.Logger will be created for each platform
+ * the backend logger will be created for each platform
  * logger and all log messages will be forwarded to the Logger
  * to handle.
  *
+ * The PlatformLogger uses an underlying PlatformLogger.Bridge instance
+ * obtained by calling {@link PlatformLogger.Bridge#convert PlatformLogger.Bridge.convert(}
+ * {@link jdk.internal.logger.LazyLoggers#getLazyLogger(java.lang.String, java.lang.Class)
+ * jdk.internal.logger.LazyLoggers#getLazyLogger(name, PlatformLogger.class))}.
+ *
  * Logging facility is "enabled" when one of the following
  * conditions is met:
- * 1) a system property "java.util.logging.config.class" or
- *    "java.util.logging.config.file" is set
- * 2) java.util.logging.LogManager or java.util.logging.Logger
- *    is referenced that will trigger the logging initialization.
+ * 1) ServiceLoader.load({@link java.lang.System.LoggerFinder LoggerFinder.class},
+ *    ClassLoader.getSystemClassLoader()).iterator().hasNext().
+ * 2) ServiceLoader.loadInstalled({@link jdk.internal.logger.DefaultLoggerFinder}).iterator().hasNext(),
+ *    and 2.1) a system property "java.util.logging.config.class" or
+ *             "java.util.logging.config.file" is set
+ *     or  2.2) java.util.logging.LogManager or java.util.logging.Logger
+ *              is referenced that will trigger the logging initialization.
  *
  * Default logging configuration:
+ *
+ *   No LoggerFinder service implementation declared
  *   global logging level = INFO
  *   handlers = java.util.logging.ConsoleHandler
  *   java.util.logging.ConsoleHandler.level = INFO
@@ -84,71 +87,84 @@
  * The platform loggers are designed for JDK developers use and
  * this limitation can be workaround with setting
  * -Djava.util.logging.config.file system property.
+ * <br>
+ * Calling PlatformLogger.setLevel will not work when there is a custom
+ * LoggerFinder installed - and as a consequence {@link #setLevel setLevel}
+ * is now deprecated.
  *
  * @since 1.7
  */
 public class PlatformLogger {
 
-    // The integer values must match that of {@code java.util.logging.Level}
-    // objects.
-    private static final int OFF     = Integer.MAX_VALUE;
-    private static final int SEVERE  = 1000;
-    private static final int WARNING = 900;
-    private static final int INFO    = 800;
-    private static final int CONFIG  = 700;
-    private static final int FINE    = 500;
-    private static final int FINER   = 400;
-    private static final int FINEST  = 300;
-    private static final int ALL     = Integer.MIN_VALUE;
-
     /**
      * PlatformLogger logging levels.
      */
     public static enum Level {
         // The name and value must match that of {@code java.util.logging.Level}s.
         // Declare in ascending order of the given value for binary search.
-        ALL,
-        FINEST,
-        FINER,
-        FINE,
-        CONFIG,
-        INFO,
-        WARNING,
-        SEVERE,
-        OFF;
+        ALL(System.Logger.Level.ALL),
+        FINEST(System.Logger.Level.TRACE),
+        FINER(System.Logger.Level.TRACE),
+        FINE(System.Logger.Level.DEBUG),
+        CONFIG(System.Logger.Level.DEBUG),
+        INFO(System.Logger.Level.INFO),
+        WARNING(System.Logger.Level.WARNING),
+        SEVERE(System.Logger.Level.ERROR),
+        OFF(System.Logger.Level.OFF);
 
-        /**
-         * Associated java.util.logging.Level lazily initialized in
-         * JavaLoggerProxy's static initializer only once
-         * when java.util.logging is available and enabled.
-         * Only accessed by JavaLoggerProxy.
-         */
-        /* java.util.logging.Level */ Object javaLevel;
+        final System.Logger.Level systemLevel;
+        Level(System.Logger.Level systemLevel) {
+            this.systemLevel = systemLevel;
+        }
+
+        // The integer values must match that of {@code java.util.logging.Level}
+        // objects.
+        private static final int SEVERITY_OFF     = Integer.MAX_VALUE;
+        private static final int SEVERITY_SEVERE  = 1000;
+        private static final int SEVERITY_WARNING = 900;
+        private static final int SEVERITY_INFO    = 800;
+        private static final int SEVERITY_CONFIG  = 700;
+        private static final int SEVERITY_FINE    = 500;
+        private static final int SEVERITY_FINER   = 400;
+        private static final int SEVERITY_FINEST  = 300;
+        private static final int SEVERITY_ALL     = Integer.MIN_VALUE;
 
         // ascending order for binary search matching the list of enum constants
         private static final int[] LEVEL_VALUES = new int[] {
-            PlatformLogger.ALL, PlatformLogger.FINEST, PlatformLogger.FINER,
-            PlatformLogger.FINE, PlatformLogger.CONFIG, PlatformLogger.INFO,
-            PlatformLogger.WARNING, PlatformLogger.SEVERE, PlatformLogger.OFF
+            SEVERITY_ALL, SEVERITY_FINEST, SEVERITY_FINER,
+            SEVERITY_FINE, SEVERITY_CONFIG, SEVERITY_INFO,
+            SEVERITY_WARNING, SEVERITY_SEVERE, SEVERITY_OFF
         };
 
+        public System.Logger.Level systemLevel() {
+            return systemLevel;
+        }
+
         public int intValue() {
             return LEVEL_VALUES[this.ordinal()];
         }
 
-        static Level valueOf(int level) {
+        /**
+         * Maps a severity value to an effective logger level.
+         * @param level The severity of the messages that should be
+         *        logged with a logger set to the returned level.
+         * @return The effective logger level, which is the nearest Level value
+         *         whose severity is greater or equal to the given level.
+         *         For level > SEVERE (OFF excluded), return SEVERE.
+         */
+        public static Level valueOf(int level) {
             switch (level) {
                 // ordering per the highest occurrences in the jdk source
                 // finest, fine, finer, info first
-                case PlatformLogger.FINEST  : return Level.FINEST;
-                case PlatformLogger.FINE    : return Level.FINE;
-                case PlatformLogger.FINER   : return Level.FINER;
-                case PlatformLogger.INFO    : return Level.INFO;
-                case PlatformLogger.WARNING : return Level.WARNING;
-                case PlatformLogger.CONFIG  : return Level.CONFIG;
-                case PlatformLogger.SEVERE  : return Level.SEVERE;
-                case PlatformLogger.OFF     : return Level.OFF;
-                case PlatformLogger.ALL     : return Level.ALL;
+                case SEVERITY_FINEST  : return Level.FINEST;
+                case SEVERITY_FINE    : return Level.FINE;
+                case SEVERITY_FINER   : return Level.FINER;
+                case SEVERITY_INFO    : return Level.INFO;
+                case SEVERITY_WARNING : return Level.WARNING;
+                case SEVERITY_CONFIG  : return Level.CONFIG;
+                case SEVERITY_SEVERE  : return Level.SEVERE;
+                case SEVERITY_OFF     : return Level.OFF;
+                case SEVERITY_ALL     : return Level.ALL;
             }
             // return the nearest Level value >= the given level,
             // for level > SEVERE, return SEVERE and exclude OFF
@@ -157,39 +173,110 @@
         }
     }
 
-    private static final Level DEFAULT_LEVEL = Level.INFO;
-    private static boolean loggingEnabled;
-    static {
-        loggingEnabled = AccessController.doPrivileged(
-            new PrivilegedAction<>() {
-                public Boolean run() {
-                    String cname = System.getProperty("java.util.logging.config.class");
-                    String fname = System.getProperty("java.util.logging.config.file");
-                    return (cname != null || fname != null);
-                }
-            });
+    /**
+     *
+     * The PlatformLogger.Bridge interface is implemented by the System.Logger
+     * objects returned by our default JUL provider - so that JRE classes using
+     * PlatformLogger see no difference when JUL is the actual backend.
+     *
+     * PlatformLogger is now only a thin adaptation layer over the same
+     * loggers than returned by java.lang.System.getLogger(String name).
+     *
+     * The recommendation for JRE classes going forward is to use
+     * java.lang.System.getLogger(String name), which will
+     * use Lazy Loggers when possible and necessary.
+     *
+     */
+    public static interface Bridge {
 
-        // force loading of all JavaLoggerProxy (sub)classes to make JIT de-optimizations
-        // less probable.  Don't initialize JavaLoggerProxy class since
-        // java.util.logging may not be enabled.
-        try {
-            Class.forName("sun.util.logging.PlatformLogger$DefaultLoggerProxy",
-                          false,
-                          PlatformLogger.class.getClassLoader());
-            Class.forName("sun.util.logging.PlatformLogger$JavaLoggerProxy",
-                          false,   // do not invoke class initializer
-                          PlatformLogger.class.getClassLoader());
-        } catch (ClassNotFoundException ex) {
-            throw new InternalError(ex);
+        /**
+         * Gets the name for this platform logger.
+         * @return the name of the platform logger.
+         */
+        public String getName();
+
+        /**
+         * Returns true if a message of the given level would actually
+         * be logged by this logger.
+         * @param level the level
+         * @return whether a message of that level would be logged
+         */
+        public boolean isLoggable(Level level);
+        public boolean isEnabled();
+
+        public void log(Level level, String msg);
+        public void log(Level level, String msg, Throwable thrown);
+        public void log(Level level, String msg, Object... params);
+        public void log(Level level, Supplier<String> msgSupplier);
+        public void log(Level level, Throwable thrown, Supplier<String> msgSupplier);
+        public void logp(Level level, String sourceClass, String sourceMethod, String msg);
+        public void logp(Level level, String sourceClass, String sourceMethod,
+                         Supplier<String> msgSupplier);
+        public void logp(Level level, String sourceClass, String sourceMethod,
+                                                    String msg, Object... params);
+        public void logp(Level level, String sourceClass, String sourceMethod,
+                         String msg, Throwable thrown);
+        public void logp(Level level, String sourceClass, String sourceMethod,
+                         Throwable thrown, Supplier<String> msgSupplier);
+        public void logrb(Level level, String sourceClass, String sourceMethod,
+                          ResourceBundle bundle, String msg, Object... params);
+        public void logrb(Level level, String sourceClass, String sourceMethod,
+                          ResourceBundle bundle, String msg, Throwable thrown);
+        public void logrb(Level level, ResourceBundle bundle, String msg,
+                Object... params);
+        public void logrb(Level level, ResourceBundle bundle, String msg,
+                Throwable thrown);
+
+
+        public static Bridge convert(System.Logger logger) {
+            if (logger instanceof PlatformLogger.Bridge) {
+                return (Bridge) logger;
+            } else {
+                return new LoggerWrapper<>(logger);
+            }
+        }
+    }
+
+    /**
+     * The {@code PlatformLogger.ConfigurableBridge} interface is used to
+     * implement the deprecated {@link PlatformLogger#setLevel} method.
+     *
+     * PlatformLogger is now only a thin adaptation layer over the same
+     * loggers than returned by java.lang.System.getLogger(String name).
+     *
+     * The recommendation for JRE classes going forward is to use
+     * java.lang.System.getLogger(String name), which will
+     * use Lazy Loggers when possible and necessary.
+     *
+     */
+    public static interface ConfigurableBridge {
+
+        public abstract class LoggerConfiguration {
+            public abstract Level getPlatformLevel();
+            public abstract void setPlatformLevel(Level level);
+        }
+
+        public default LoggerConfiguration getLoggerConfiguration() {
+            return null;
+        }
+
+        public static LoggerConfiguration getLoggerConfiguration(PlatformLogger.Bridge logger) {
+            if (logger instanceof PlatformLogger.ConfigurableBridge) {
+                return ((ConfigurableBridge) logger).getLoggerConfiguration();
+            } else {
+                return null;
+            }
         }
     }
 
     // Table of known loggers.  Maps names to PlatformLoggers.
-    private static Map<String,WeakReference<PlatformLogger>> loggers =
+    private static final Map<String,WeakReference<PlatformLogger>> loggers =
         new HashMap<>();
 
     /**
      * Returns a PlatformLogger of a given name.
+     * @param name the name of the logger
+     * @return a PlatformLogger
      */
     public static synchronized PlatformLogger getLogger(String name) {
         PlatformLogger log = null;
@@ -198,56 +285,31 @@
             log = ref.get();
         }
         if (log == null) {
-            log = new PlatformLogger(name);
+            log = new PlatformLogger(PlatformLogger.Bridge.convert(
+                    // We pass PlatformLogger.class rather than the actual caller
+                    // because we want PlatformLoggers to be system loggers: we
+                    // won't need to resolve any resource bundles anyway.
+                    // Note: Many unit tests depend on the fact that
+                    //       PlatformLogger.getLoggerFromFinder is not caller sensitive.
+                    LazyLoggers.getLazyLogger(name, PlatformLogger.class)));
             loggers.put(name, new WeakReference<>(log));
         }
         return log;
     }
 
-    /**
-     * Initialize java.util.logging.Logger objects for all platform loggers.
-     * This method is called from LogManager.readPrimordialConfiguration().
-     */
-    public static synchronized void redirectPlatformLoggers() {
-        if (loggingEnabled || !LoggingSupport.isAvailable()) return;
-
-        loggingEnabled = true;
-        for (Map.Entry<String, WeakReference<PlatformLogger>> entry : loggers.entrySet()) {
-            WeakReference<PlatformLogger> ref = entry.getValue();
-            PlatformLogger plog = ref.get();
-            if (plog != null) {
-                plog.redirectToJavaLoggerProxy();
-            }
-        }
-    }
-
-    /**
-     * Creates a new JavaLoggerProxy and redirects the platform logger to it
-     */
-    private void redirectToJavaLoggerProxy() {
-        DefaultLoggerProxy lp = DefaultLoggerProxy.class.cast(this.loggerProxy);
-        JavaLoggerProxy jlp = new JavaLoggerProxy(lp.name, lp.level);
-        // the order of assignments is important
-        this.javaLoggerProxy = jlp;   // isLoggable checks javaLoggerProxy if set
-        this.loggerProxy = jlp;
-    }
-
-    // DefaultLoggerProxy may be replaced with a JavaLoggerProxy object
-    // when the java.util.logging facility is enabled
-    private volatile LoggerProxy loggerProxy;
-    // javaLoggerProxy is only set when the java.util.logging facility is enabled
-    private volatile JavaLoggerProxy javaLoggerProxy;
-    private PlatformLogger(String name) {
-        if (loggingEnabled) {
-            this.loggerProxy = this.javaLoggerProxy = new JavaLoggerProxy(name);
-        } else {
-            this.loggerProxy = new DefaultLoggerProxy(name);
-        }
+    // The system loggerProxy returned by LazyLoggers
+    // This may be a lazy logger - see jdk.internal.logger.LazyLoggers,
+    // or may be a Logger instance (or a wrapper thereof).
+    //
+    private final PlatformLogger.Bridge loggerProxy;
+    private PlatformLogger(PlatformLogger.Bridge loggerProxy) {
+        this.loggerProxy = loggerProxy;
     }
 
     /**
      * A convenience method to test if the logger is turned off.
      * (i.e. its level is OFF).
+     * @return whether the logger is turned off.
      */
     public boolean isEnabled() {
         return loggerProxy.isEnabled();
@@ -255,22 +317,24 @@
 
     /**
      * Gets the name for this platform logger.
+     * @return the name of the platform logger.
      */
     public String getName() {
-        return loggerProxy.name;
+        return loggerProxy.getName();
     }
 
     /**
      * Returns true if a message of the given level would actually
      * be logged by this logger.
+     * @param level the level
+     * @return whether a message of that level would be logged
      */
     public boolean isLoggable(Level level) {
         if (level == null) {
             throw new NullPointerException();
         }
-        // performance-sensitive method: use two monomorphic call-sites
-        JavaLoggerProxy jlp = javaLoggerProxy;
-        return jlp != null ? jlp.isLoggable(level) : loggerProxy.isLoggable(level);
+
+        return loggerProxy.isLoggable(level);
     }
 
     /**
@@ -281,13 +345,15 @@
      * @return  this PlatformLogger's level
      */
     public Level level() {
-        return loggerProxy.getLevel();
+        final ConfigurableBridge.LoggerConfiguration spi =
+                PlatformLogger.ConfigurableBridge.getLoggerConfiguration(loggerProxy);
+        return spi == null ? null : spi.getPlatformLevel();
     }
 
     /**
      * Set the log level specifying which message levels will be
      * logged by this logger.  Message levels lower than this
-     * value will be discarded.  The level value {@link #OFF}
+     * value will be discarded.  The level value {@link Level#OFF}
      * can be used to turn off logging.
      * <p>
      * If the new level is null, it means that this node should
@@ -295,366 +361,153 @@
      * (non-null) level value.
      *
      * @param newLevel the new value for the log level (may be null)
+     * @deprecated Platform Loggers should not be configured programmatically.
+     *             This method will not work if a custom {@link
+     *             java.lang.System.LoggerFinder} is installed.
      */
+    @Deprecated
     public void setLevel(Level newLevel) {
-        loggerProxy.setLevel(newLevel);
+        final ConfigurableBridge.LoggerConfiguration spi =
+                PlatformLogger.ConfigurableBridge.getLoggerConfiguration(loggerProxy);;
+        if (spi != null) {
+            spi.setPlatformLevel(newLevel);
+        }
     }
 
     /**
      * Logs a SEVERE message.
+     * @param msg the message
      */
     public void severe(String msg) {
-        loggerProxy.doLog(Level.SEVERE, msg);
+        loggerProxy.log(Level.SEVERE, msg, (Object[])null);
     }
 
     public void severe(String msg, Throwable t) {
-        loggerProxy.doLog(Level.SEVERE, msg, t);
+        loggerProxy.log(Level.SEVERE, msg, t);
     }
 
     public void severe(String msg, Object... params) {
-        loggerProxy.doLog(Level.SEVERE, msg, params);
+        loggerProxy.log(Level.SEVERE, msg, params);
     }
 
     /**
      * Logs a WARNING message.
+     * @param msg the message
      */
     public void warning(String msg) {
-        loggerProxy.doLog(Level.WARNING, msg);
+        loggerProxy.log(Level.WARNING, msg, (Object[])null);
     }
 
     public void warning(String msg, Throwable t) {
-        loggerProxy.doLog(Level.WARNING, msg, t);
+        loggerProxy.log(Level.WARNING, msg, t);
     }
 
     public void warning(String msg, Object... params) {
-        loggerProxy.doLog(Level.WARNING, msg, params);
+        loggerProxy.log(Level.WARNING, msg, params);
     }
 
     /**
      * Logs an INFO message.
+     * @param msg the message
      */
     public void info(String msg) {
-        loggerProxy.doLog(Level.INFO, msg);
+        loggerProxy.log(Level.INFO, msg, (Object[])null);
     }
 
     public void info(String msg, Throwable t) {
-        loggerProxy.doLog(Level.INFO, msg, t);
+        loggerProxy.log(Level.INFO, msg, t);
     }
 
     public void info(String msg, Object... params) {
-        loggerProxy.doLog(Level.INFO, msg, params);
+        loggerProxy.log(Level.INFO, msg, params);
     }
 
     /**
      * Logs a CONFIG message.
+     * @param msg the message
      */
     public void config(String msg) {
-        loggerProxy.doLog(Level.CONFIG, msg);
+        loggerProxy.log(Level.CONFIG, msg, (Object[])null);
     }
 
     public void config(String msg, Throwable t) {
-        loggerProxy.doLog(Level.CONFIG, msg, t);
+        loggerProxy.log(Level.CONFIG, msg, t);
     }
 
     public void config(String msg, Object... params) {
-        loggerProxy.doLog(Level.CONFIG, msg, params);
+        loggerProxy.log(Level.CONFIG, msg, params);
     }
 
     /**
      * Logs a FINE message.
+     * @param msg the message
      */
     public void fine(String msg) {
-        loggerProxy.doLog(Level.FINE, msg);
+        loggerProxy.log(Level.FINE, msg, (Object[])null);
     }
 
     public void fine(String msg, Throwable t) {
-        loggerProxy.doLog(Level.FINE, msg, t);
+        loggerProxy.log(Level.FINE, msg, t);
     }
 
     public void fine(String msg, Object... params) {
-        loggerProxy.doLog(Level.FINE, msg, params);
+        loggerProxy.log(Level.FINE, msg, params);
     }
 
     /**
      * Logs a FINER message.
+     * @param msg the message
      */
     public void finer(String msg) {
-        loggerProxy.doLog(Level.FINER, msg);
+        loggerProxy.log(Level.FINER, msg, (Object[])null);
     }
 
     public void finer(String msg, Throwable t) {
-        loggerProxy.doLog(Level.FINER, msg, t);
+        loggerProxy.log(Level.FINER, msg, t);
     }
 
     public void finer(String msg, Object... params) {
-        loggerProxy.doLog(Level.FINER, msg, params);
+        loggerProxy.log(Level.FINER, msg, params);
     }
 
     /**
      * Logs a FINEST message.
+     * @param msg the message
      */
     public void finest(String msg) {
-        loggerProxy.doLog(Level.FINEST, msg);
+        loggerProxy.log(Level.FINEST, msg, (Object[])null);
     }
 
     public void finest(String msg, Throwable t) {
-        loggerProxy.doLog(Level.FINEST, msg, t);
+        loggerProxy.log(Level.FINEST, msg, t);
     }
 
     public void finest(String msg, Object... params) {
-        loggerProxy.doLog(Level.FINEST, msg, params);
+        loggerProxy.log(Level.FINEST, msg, params);
     }
 
-    /**
-     * Abstract base class for logging support, defining the API and common field.
-     */
-    private abstract static class LoggerProxy {
-        final String name;
+    // ------------------------------------
+    // Maps used for Level conversion
+    // ------------------------------------
 
-        protected LoggerProxy(String name) {
-            this.name = name;
-        }
+    // This map is indexed by java.util.spi.Logger.Level.ordinal() and returns
+    // a PlatformLogger.Level
+    //
+    // ALL, TRACE, DEBUG, INFO, WARNING, ERROR, OFF
+    private static final Level[] spi2platformLevelMapping = {
+            Level.ALL,     // mapped from ALL
+            Level.FINER,   // mapped from TRACE
+            Level.FINE,    // mapped from DEBUG
+            Level.INFO,    // mapped from INFO
+            Level.WARNING, // mapped from WARNING
+            Level.SEVERE,  // mapped from ERROR
+            Level.OFF      // mapped from OFF
+    };
 
-        abstract boolean isEnabled();
-
-        abstract Level getLevel();
-        abstract void setLevel(Level newLevel);
-
-        abstract void doLog(Level level, String msg);
-        abstract void doLog(Level level, String msg, Throwable thrown);
-        abstract void doLog(Level level, String msg, Object... params);
-
-        abstract boolean isLoggable(Level level);
+    public static Level toPlatformLevel(java.lang.System.Logger.Level level) {
+        if (level == null) return null;
+        assert level.ordinal() < spi2platformLevelMapping.length;
+        return spi2platformLevelMapping[level.ordinal()];
     }
 
-
-    private static final class DefaultLoggerProxy extends LoggerProxy {
-        /**
-         * Default platform logging support - output messages to System.err -
-         * equivalent to ConsoleHandler with SimpleFormatter.
-         */
-        private static PrintStream outputStream() {
-            return System.err;
-        }
-
-        volatile Level effectiveLevel; // effective level (never null)
-        volatile Level level;          // current level set for this node (may be null)
-
-        DefaultLoggerProxy(String name) {
-            super(name);
-            this.effectiveLevel = deriveEffectiveLevel(null);
-            this.level = null;
-        }
-
-        boolean isEnabled() {
-            return effectiveLevel != Level.OFF;
-        }
-
-        Level getLevel() {
-            return level;
-        }
-
-        void setLevel(Level newLevel) {
-            Level oldLevel = level;
-            if (oldLevel != newLevel) {
-                level = newLevel;
-                effectiveLevel = deriveEffectiveLevel(newLevel);
-            }
-        }
-
-        void doLog(Level level, String msg) {
-            if (isLoggable(level)) {
-                outputStream().print(format(level, msg, null));
-            }
-        }
-
-        void doLog(Level level, String msg, Throwable thrown) {
-            if (isLoggable(level)) {
-                outputStream().print(format(level, msg, thrown));
-            }
-        }
-
-        void doLog(Level level, String msg, Object... params) {
-            if (isLoggable(level)) {
-                String newMsg = formatMessage(msg, params);
-                outputStream().print(format(level, newMsg, null));
-            }
-        }
-
-        boolean isLoggable(Level level) {
-            Level effectiveLevel = this.effectiveLevel;
-            return level.intValue() >= effectiveLevel.intValue() && effectiveLevel != Level.OFF;
-        }
-
-        // derive effective level (could do inheritance search like j.u.l.Logger)
-        private Level deriveEffectiveLevel(Level level) {
-            return level == null ? DEFAULT_LEVEL : level;
-        }
-
-        // Copied from java.util.logging.Formatter.formatMessage
-        private String formatMessage(String format, Object... parameters) {
-            // Do the formatting.
-            try {
-                if (parameters == null || parameters.length == 0) {
-                    // No parameters.  Just return format string.
-                    return format;
-                }
-                // Is it a java.text style format?
-                // Ideally we could match with
-                // Pattern.compile("\\{\\d").matcher(format).find())
-                // However the cost is 14% higher, so we cheaply check for
-                // 1 of the first 4 parameters
-                if (format.indexOf("{0") >= 0 || format.indexOf("{1") >=0 ||
-                            format.indexOf("{2") >=0|| format.indexOf("{3") >=0) {
-                    return java.text.MessageFormat.format(format, parameters);
-                }
-                return format;
-            } catch (Exception ex) {
-                // Formatting failed: use format string.
-                return format;
-            }
-        }
-
-        private static final String formatString =
-            LoggingSupport.getSimpleFormat(false); // don't check logging.properties
-        private final ZoneId zoneId = ZoneId.systemDefault();
-        private synchronized String format(Level level, String msg, Throwable thrown) {
-            ZonedDateTime zdt = ZonedDateTime.now(zoneId);
-            String throwable = "";
-            if (thrown != null) {
-                StringWriter sw = new StringWriter();
-                PrintWriter pw = new PrintWriter(sw);
-                pw.println();
-                thrown.printStackTrace(pw);
-                pw.close();
-                throwable = sw.toString();
-            }
-
-            return String.format(formatString,
-                                 zdt,
-                                 getCallerInfo(),
-                                 name,
-                                 level.name(),
-                                 msg,
-                                 throwable);
-        }
-
-        // Returns the caller's class and method's name; best effort
-        // if cannot infer, return the logger's name.
-        private String getCallerInfo() {
-            String sourceClassName = null;
-            String sourceMethodName = null;
-
-            JavaLangAccess access = SharedSecrets.getJavaLangAccess();
-            Throwable throwable = new Throwable();
-            int depth = access.getStackTraceDepth(throwable);
-
-            String logClassName = "sun.util.logging.PlatformLogger";
-            boolean lookingForLogger = true;
-            for (int ix = 0; ix < depth; ix++) {
-                // Calling getStackTraceElement directly prevents the VM
-                // from paying the cost of building the entire stack frame.
-                StackTraceElement frame =
-                    access.getStackTraceElement(throwable, ix);
-                String cname = frame.getClassName();
-                if (lookingForLogger) {
-                    // Skip all frames until we have found the first logger frame.
-                    if (cname.equals(logClassName)) {
-                        lookingForLogger = false;
-                    }
-                } else {
-                    if (!cname.equals(logClassName)) {
-                        // We've found the relevant frame.
-                        sourceClassName = cname;
-                        sourceMethodName = frame.getMethodName();
-                        break;
-                    }
-                }
-            }
-
-            if (sourceClassName != null) {
-                return sourceClassName + " " + sourceMethodName;
-            } else {
-                return name;
-            }
-        }
-    }
-
-    /**
-     * JavaLoggerProxy forwards all the calls to its corresponding
-     * java.util.logging.Logger object.
-     */
-    private static final class JavaLoggerProxy extends LoggerProxy {
-        // initialize javaLevel fields for mapping from Level enum -> j.u.l.Level object
-        static {
-            for (Level level : Level.values()) {
-                level.javaLevel = LoggingSupport.parseLevel(level.name());
-            }
-        }
-
-        private final /* java.util.logging.Logger */ Object javaLogger;
-
-        JavaLoggerProxy(String name) {
-            this(name, null);
-        }
-
-        JavaLoggerProxy(String name, Level level) {
-            super(name);
-            this.javaLogger = LoggingSupport.getLogger(name);
-            if (level != null) {
-                // level has been updated and so set the Logger's level
-                LoggingSupport.setLevel(javaLogger, level.javaLevel);
-            }
-        }
-
-        void doLog(Level level, String msg) {
-            LoggingSupport.log(javaLogger, level.javaLevel, msg);
-        }
-
-        void doLog(Level level, String msg, Throwable t) {
-            LoggingSupport.log(javaLogger, level.javaLevel, msg, t);
-        }
-
-        void doLog(Level level, String msg, Object... params) {
-            if (!isLoggable(level)) {
-                return;
-            }
-            // only pass String objects to the j.u.l.Logger which may
-            // be created by untrusted code
-            int len = (params != null) ? params.length : 0;
-            Object[] sparams = new String[len];
-            for (int i = 0; i < len; i++) {
-                sparams [i] = String.valueOf(params[i]);
-            }
-            LoggingSupport.log(javaLogger, level.javaLevel, msg, sparams);
-        }
-
-        boolean isEnabled() {
-            return LoggingSupport.isLoggable(javaLogger, Level.OFF.javaLevel);
-        }
-
-        /**
-         * Returns the PlatformLogger.Level mapped from j.u.l.Level
-         * set in the logger.  If the j.u.l.Logger is set to a custom Level,
-         * this method will return the nearest Level.
-         */
-        Level getLevel() {
-            Object javaLevel = LoggingSupport.getLevel(javaLogger);
-            if (javaLevel == null) return null;
-
-            try {
-                return Level.valueOf(LoggingSupport.getLevelName(javaLevel));
-            } catch (IllegalArgumentException e) {
-                return Level.valueOf(LoggingSupport.getLevelValue(javaLevel));
-            }
-        }
-
-        void setLevel(Level level) {
-            LoggingSupport.setLevel(javaLogger, level == null ? null : level.javaLevel);
-        }
-
-        boolean isLoggable(Level level) {
-            return LoggingSupport.isLoggable(javaLogger, level.javaLevel);
-        }
-    }
 }
--- a/src/java.desktop/share/classes/sun/font/FontUtilities.java	Fri Nov 20 15:34:12 2015 +0100
+++ b/src/java.desktop/share/classes/sun/font/FontUtilities.java	Fri Nov 20 19:26:16 2015 +0100
@@ -72,6 +72,8 @@
     static {
 
         AccessController.doPrivileged(new PrivilegedAction<Object>() {
+            @SuppressWarnings("deprecation") // PlatformLogger.setLevel is deprecated.
+            @Override
             public Object run() {
                 String osName = System.getProperty("os.name", "unknownOS");
                 isSolaris = osName.startsWith("SunOS");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.logging/share/classes/META-INF/services/jdk.internal.logger.DefaultLoggerFinder	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,1 @@
+sun.util.logging.internal.LoggingProviderImpl
--- a/src/java.logging/share/classes/java/util/logging/LogManager.java	Fri Nov 20 15:34:12 2015 +0100
+++ b/src/java.logging/share/classes/java/util/logging/LogManager.java	Fri Nov 20 19:26:16 2015 +0100
@@ -43,6 +43,7 @@
 import jdk.internal.misc.JavaAWTAccess;
 import jdk.internal.misc.SharedSecrets;
 import sun.misc.ManagedLocalsThread;
+import sun.util.logging.internal.LoggingProviderImpl;
 
 /**
  * There is a single global LogManager object that is used to
@@ -436,7 +437,8 @@
                 readConfiguration();
 
                 // Platform loggers begin to delegate to java.util.logging.Logger
-                sun.util.logging.PlatformLogger.redirectPlatformLoggers();
+                jdk.internal.logger.BootstrapLogger.redirectTemporaryLoggers();
+
             } catch (Exception ex) {
                 assert false : "Exception raised while reading logging configuration: " + ex;
             }
@@ -1481,7 +1483,7 @@
      * <p>
      * Any {@linkplain #addConfigurationListener registered configuration
      * listener} will be invoked after the properties are read.
-     * <p>
+     *
      * @apiNote This {@code readConfiguration} method should only be used for
      * initializing the configuration during LogManager initialization or
      * used with the "java.util.logging.config.class" property.
@@ -2363,7 +2365,8 @@
         }
     }
 
-    static final Permission controlPermission = new LoggingPermission("control", null);
+    static final Permission controlPermission =
+            new LoggingPermission("control", null);
 
     void checkPermission() {
         SecurityManager sm = System.getSecurityManager();
@@ -2607,4 +2610,69 @@
         if (t instanceof RuntimeException) throw (RuntimeException)t;
     }
 
+    /**
+     * This class allows the {@link LoggingProviderImpl} to demand loggers on
+     * behalf of system and application classes.
+     */
+    private static final class LoggingProviderAccess
+        implements LoggingProviderImpl.LogManagerAccess,
+                   PrivilegedAction<Void> {
+
+        private LoggingProviderAccess() {
+        }
+
+        /**
+         * Demands a logger on behalf of the given {@code caller}.
+         * <p>
+         * If a named logger suitable for the given caller is found
+         * returns it.
+         * Otherwise, creates a new logger suitable for the given caller.
+         *
+         * @param name   The logger name.
+         * @param caller The caller on which behalf the logger is created/retrieved.
+         * @return A logger for the given {@code caller}.
+         *
+         * @throws NullPointerException if {@code name} is {@code null}
+         *         or {@code caller} is {@code null}.
+         * @throws IllegalArgumentException if {@code manager} is not the default
+         *         LogManager.
+         * @throws SecurityException if a security manager is present and the
+         *         calling code doesn't have the
+         *        {@link LoggingPermission LoggingPermission("demandLogger", null)}.
+         */
+        @Override
+        public Logger demandLoggerFor(LogManager manager, String name, /* Module */ Class<?> caller) {
+            if (manager != getLogManager()) {
+                // having LogManager as parameter just ensures that the
+                // caller will have initialized the LogManager before reaching
+                // here.
+                throw new IllegalArgumentException("manager");
+            }
+            Objects.requireNonNull(name);
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(controlPermission);
+            }
+            if (caller.getClassLoader() == null) {
+                return manager.demandSystemLogger(name,
+                    Logger.SYSTEM_LOGGER_RB_NAME, caller);
+            } else {
+                return manager.demandLogger(name, null, caller);
+            }
+        }
+
+        @Override
+        public Void run() {
+            LoggingProviderImpl.setLogManagerAccess(INSTANCE);
+            return null;
+        }
+
+        static final LoggingProviderAccess INSTANCE = new LoggingProviderAccess();
+    }
+
+    static {
+        AccessController.doPrivileged(LoggingProviderAccess.INSTANCE, null,
+                                      controlPermission);
+    }
+
 }
--- a/src/java.logging/share/classes/java/util/logging/LogRecord.java	Fri Nov 20 15:34:12 2015 +0100
+++ b/src/java.logging/share/classes/java/util/logging/LogRecord.java	Fri Nov 20 19:26:16 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2015, 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
@@ -33,6 +33,7 @@
 
 import jdk.internal.misc.JavaLangAccess;
 import jdk.internal.misc.SharedSecrets;
+import static jdk.internal.logger.SimpleConsoleLogger.skipLoggingFrame;
 
 /**
  * LogRecord objects are used to pass logging requests between
@@ -637,6 +638,27 @@
     }
 
     // Private method to infer the caller's class and method names
+    //
+    // Note:
+    // For testing purposes - it is possible to customize the process
+    // by which LogRecord will infer the source class name and source method name
+    // when analyzing the call stack.
+    // <p>
+    // The system property {@code jdk.logger.packages} can define a comma separated
+    // list of strings corresponding to additional package name prefixes that
+    // should be ignored when trying to infer the source caller class name.
+    // Those stack frames whose {@linkplain StackTraceElement#getClassName()
+    // declaring class name} start with one such prefix will be ignored.
+    // <p>
+    // This is primarily useful when providing utility logging classes wrapping
+    // a logger instance, as it makes it possible to instruct LogRecord to skip
+    // those utility frames when inferring the caller source class name.
+    // <p>
+    // The {@code jdk.logger.packages} system property is consulted only once.
+    // <p>
+    // This property is not standard, implementation specific, and yet
+    // undocumented (and thus subject to changes without notice).
+    //
     private void inferCaller() {
         needToInferCaller = false;
         JavaLangAccess access = SharedSecrets.getJavaLangAccess();
@@ -658,8 +680,8 @@
                 }
             } else {
                 if (!isLoggerImpl) {
-                    // skip reflection call
-                    if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) {
+                    // skip logging/logger infrastructure and reflection calls
+                    if (!skipLoggingFrame(cname)) {
                        // We've found the relevant frame.
                        setSourceClassName(cname);
                        setSourceMethodName(frame.getMethodName());
@@ -675,7 +697,6 @@
     private boolean isLoggerImplFrame(String cname) {
         // the log record could be created for a platform logger
         return (cname.equals("java.util.logging.Logger") ||
-                cname.startsWith("java.util.logging.LoggingProxyImpl") ||
-                cname.startsWith("sun.util.logging."));
+                cname.startsWith("sun.util.logging.PlatformLogger"));
     }
 }
--- a/src/java.logging/share/classes/java/util/logging/Logger.java	Fri Nov 20 15:34:12 2015 +0100
+++ b/src/java.logging/share/classes/java/util/logging/Logger.java	Fri Nov 20 19:26:16 2015 +0100
@@ -447,8 +447,7 @@
 
     private static Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {
         LogManager manager = LogManager.getLogManager();
-        SecurityManager sm = System.getSecurityManager();
-        if (sm != null && !SystemLoggerHelper.disableCallerCheck) {
+        if (!SystemLoggerHelper.disableCallerCheck) {
             if (caller.getClassLoader() == null) {
                 return manager.demandSystemLogger(name, resourceBundleName, caller);
             }
@@ -1254,14 +1253,14 @@
      * with an optional list of message parameters.
      * <p>
      * If the logger is currently enabled for the given message
-     * level then a corresponding LogRecord is created and forwarded
-     * to all the registered output Handler objects.
+     * {@code level} then a corresponding {@code LogRecord} is created and
+     * forwarded to all the registered output {@code Handler} objects.
      * <p>
      * The {@code msg} string is localized using the given resource bundle.
      * If the resource bundle is {@code null}, then the {@code msg} string is not
      * localized.
      *
-     * @param   level   One of the message level identifiers, e.g., SEVERE
+     * @param   level   One of the message level identifiers, e.g., {@code SEVERE}
      * @param   sourceClass    Name of the class that issued the logging request
      * @param   sourceMethod   Name of the method that issued the logging request
      * @param   bundle         Resource bundle to localize {@code msg},
@@ -1285,6 +1284,36 @@
     }
 
     /**
+     * Log a message, specifying source class, method, and resource bundle,
+     * with an optional list of message parameters.
+     * <p>
+     * If the logger is currently enabled for the given message
+     * {@code level} then a corresponding {@code LogRecord} is created
+     * and forwarded to all the registered output {@code Handler} objects.
+     * <p>
+     * The {@code msg} string is localized using the given resource bundle.
+     * If the resource bundle is {@code null}, then the {@code msg} string is not
+     * localized.
+     * <p>
+     * @param   level   One of the message level identifiers, e.g., {@code SEVERE}
+     * @param   bundle  Resource bundle to localize {@code msg};
+     *                  can be {@code null}.
+     * @param   msg     The string message (or a key in the message catalog)
+     * @param   params  Parameters to the message (optional, may be none).
+     * @since 1.9
+     */
+    public void logrb(Level level, ResourceBundle bundle, String msg, Object... params) {
+        if (!isLoggable(level)) {
+            return;
+        }
+        LogRecord lr = new LogRecord(level, msg);
+        if (params != null && params.length != 0) {
+            lr.setParameters(params);
+        }
+        doLog(lr, bundle);
+    }
+
+    /**
      * Log a message, specifying source class, method, and resource bundle name,
      * with associated Throwable information.
      * <p>
@@ -1330,19 +1359,20 @@
      * with associated Throwable information.
      * <p>
      * If the logger is currently enabled for the given message
-     * level then the given arguments are stored in a LogRecord
+     * {@code level} then the given arguments are stored in a {@code LogRecord}
      * which is forwarded to all registered output handlers.
      * <p>
      * The {@code msg} string is localized using the given resource bundle.
      * If the resource bundle is {@code null}, then the {@code msg} string is not
      * localized.
      * <p>
-     * Note that the thrown argument is stored in the LogRecord thrown
-     * property, rather than the LogRecord parameters property.  Thus it is
-     * processed specially by output Formatters and is not treated
-     * as a formatting parameter to the LogRecord message property.
+     * Note that the {@code thrown} argument is stored in the {@code LogRecord}
+     * {@code thrown} property, rather than the {@code LogRecord}
+     * {@code parameters} property.  Thus it is
+     * processed specially by output {@code Formatter} objects and is not treated
+     * as a formatting parameter to the {@code LogRecord} {@code message} property.
      *
-     * @param   level   One of the message level identifiers, e.g., SEVERE
+     * @param   level   One of the message level identifiers, e.g., {@code SEVERE}
      * @param   sourceClass    Name of the class that issued the logging request
      * @param   sourceMethod   Name of the method that issued the logging request
      * @param   bundle         Resource bundle to localize {@code msg},
@@ -1363,6 +1393,42 @@
         doLog(lr, bundle);
     }
 
+    /**
+     * Log a message, specifying source class, method, and resource bundle,
+     * with associated Throwable information.
+     * <p>
+     * If the logger is currently enabled for the given message
+     * {@code level} then the given arguments are stored in a {@code LogRecord}
+     * which is forwarded to all registered output handlers.
+     * <p>
+     * The {@code msg} string is localized using the given resource bundle.
+     * If the resource bundle is {@code null}, then the {@code msg} string is not
+     * localized.
+     * <p>
+     * Note that the {@code thrown} argument is stored in the {@code LogRecord}
+     * {@code thrown} property, rather than the {@code LogRecord}
+     * {@code parameters} property.  Thus it is
+     * processed specially by output {@code Formatter} objects and is not treated
+     * as a formatting parameter to the {@code LogRecord} {@code message}
+     * property.
+     * <p>
+     * @param   level   One of the message level identifiers, e.g., {@code SEVERE}
+     * @param   bundle  Resource bundle to localize {@code msg};
+     *                  can be {@code null}.
+     * @param   msg     The string message (or a key in the message catalog)
+     * @param   thrown  Throwable associated with the log message.
+     * @since 1.9
+     */
+    public void logrb(Level level, ResourceBundle bundle, String msg,
+            Throwable thrown) {
+        if (!isLoggable(level)) {
+            return;
+        }
+        LogRecord lr = new LogRecord(level, msg);
+        lr.setThrown(thrown);
+        doLog(lr, bundle);
+    }
+
     //======================================================================
     // Start of convenience methods for logging method entries and returns.
     //======================================================================
--- a/src/java.logging/share/classes/java/util/logging/LoggingProxyImpl.java	Fri Nov 20 15:34:12 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +0,0 @@
-/*
- * Copyright (c) 2009, 2013, 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.
- */
-
-package java.util.logging;
-
-import sun.util.logging.LoggingProxy;
-
-/**
- * Implementation of LoggingProxy when java.util.logging classes exist.
- */
-class LoggingProxyImpl implements LoggingProxy {
-    static final LoggingProxy INSTANCE = new LoggingProxyImpl();
-
-    private LoggingProxyImpl() { }
-
-    @Override
-    public Object getLogger(String name) {
-        // always create a platform logger with the resource bundle name
-        return Logger.getPlatformLogger(name);
-    }
-
-    @Override
-    public Object getLevel(Object logger) {
-        return ((Logger) logger).getLevel();
-    }
-
-    @Override
-    public void setLevel(Object logger, Object newLevel) {
-        ((Logger) logger).setLevel((Level) newLevel);
-    }
-
-    @Override
-    public boolean isLoggable(Object logger, Object level) {
-        return ((Logger) logger).isLoggable((Level) level);
-    }
-
-    @Override
-    public void log(Object logger, Object level, String msg) {
-        ((Logger) logger).log((Level) level, msg);
-    }
-
-    @Override
-    public void log(Object logger, Object level, String msg, Throwable t) {
-        ((Logger) logger).log((Level) level, msg, t);
-    }
-
-    @Override
-    public void log(Object logger, Object level, String msg, Object... params) {
-        ((Logger) logger).log((Level) level, msg, params);
-    }
-
-    @Override
-    public java.util.List<String> getLoggerNames() {
-        return LogManager.getLoggingMXBean().getLoggerNames();
-    }
-
-    @Override
-    public String getLoggerLevel(String loggerName) {
-        return LogManager.getLoggingMXBean().getLoggerLevel(loggerName);
-    }
-
-    @Override
-    public void setLoggerLevel(String loggerName, String levelName) {
-        LogManager.getLoggingMXBean().setLoggerLevel(loggerName, levelName);
-    }
-
-    @Override
-    public String getParentLoggerName(String loggerName) {
-        return LogManager.getLoggingMXBean().getParentLoggerName(loggerName);
-    }
-
-    @Override
-    public Object parseLevel(String levelName) {
-        Level level = Level.findLevel(levelName);
-        if (level == null) {
-            throw new IllegalArgumentException("Unknown level \"" + levelName + "\"");
-        }
-        return level;
-    }
-
-    @Override
-    public String getLevelName(Object level) {
-        return ((Level) level).getLevelName();
-    }
-
-    @Override
-    public int getLevelValue(Object level) {
-        return ((Level) level).intValue();
-    }
-
-    @Override
-    public String getProperty(String key) {
-        return LogManager.getLogManager().getProperty(key);
-    }
-}
--- a/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java	Fri Nov 20 15:34:12 2015 +0100
+++ b/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java	Fri Nov 20 19:26:16 2015 +0100
@@ -27,10 +27,9 @@
 package java.util.logging;
 
 import java.io.*;
-import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
-import sun.util.logging.LoggingSupport;
+import jdk.internal.logger.SimpleConsoleLogger;
 
 /**
  * Print a brief summary of the {@code LogRecord} in a human readable
@@ -60,8 +59,12 @@
 public class SimpleFormatter extends Formatter {
 
     // format string for printing the log record
-    private final String format = LoggingSupport.getSimpleFormat();
-    private final ZoneId zoneId = ZoneId.systemDefault();
+    static String getLoggingProperty(String name) {
+        return LogManager.getLogManager().getProperty(name);
+    }
+
+    private final String format =
+        SimpleConsoleLogger.getSimpleFormat(SimpleFormatter::getLoggingProperty);
 
     /**
      * Format the given LogRecord.
@@ -152,7 +155,7 @@
     @Override
     public synchronized String format(LogRecord record) {
         ZonedDateTime zdt = ZonedDateTime.ofInstant(
-                record.getInstant(), zoneId);
+                record.getInstant(), ZoneId.systemDefault());
         String source;
         if (record.getSourceClassName() != null) {
             source = record.getSourceClassName();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.logging/share/classes/sun/util/logging/internal/LoggingProviderImpl.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,466 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+
+package sun.util.logging.internal;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.util.Objects;
+import java.util.logging.LogManager;
+import jdk.internal.logger.DefaultLoggerFinder;
+import java.util.logging.LoggingPermission;
+import sun.util.logging.PlatformLogger;
+import sun.util.logging.PlatformLogger.ConfigurableBridge.LoggerConfiguration;
+
+/**
+ * This {@code LoggingProviderImpl} is the JDK internal implementation of the
+ * {@link jdk.internal.logger.DefaultLoggerFinder} which is used by
+ * the default implementation of the {@link Logger}
+ * when no {@link LoggerFinder} is found
+ * and {@code java.util.logging} is present.
+ * When {@code java.util.logging} is present, the {@code LoggingProviderImpl}
+ * is {@linkplain java.util.ServiceLoader#loadInstalled(Class) installed} as
+ * an internal service provider, making it possible to use {@code java.util.logging}
+ * as the backend for loggers returned by the default LoggerFinder implementation.
+ * <p>
+ * This implementation of {@link DefaultLoggerFinder} returns instances of
+ * {@link java.lang.System.Logger} which
+ * delegate to a wrapped instance of {@link java.util.logging.Logger
+ * java.util.logging.Logger}.
+ * <br>
+ * Loggers returned by this class can therefore be configured by accessing
+ * their wrapped implementation through the regular {@code java.util.logging}
+ * APIs - such as {@link java.util.logging.LogManager java.util.logging.LogManager}
+ * and {@link java.util.logging.Logger java.util.logging.Logger}.
+ *
+ * @apiNote Programmers are not expected to call this class directly.
+ * Instead they should rely on the static methods defined by
+ * {@link java.lang.System java.lang.System}.
+ * <p>
+ * To replace this default
+ * {@code java.util.logging} backend, an application is expected to install
+ * its own {@link java.lang.System.LoggerFinder}.
+ *
+ * @see java.lang.System.Logger
+ * @see java.lang.System.LoggerFinder
+ * @see sun.util.logging.PlatformLogger.Bridge
+ * @see java.lang.System
+ * @see jdk.internal.logger
+ * @see jdk.internal.logger
+ *
+ */
+public final class LoggingProviderImpl extends DefaultLoggerFinder {
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+    private static final LoggingPermission LOGGING_CONTROL_PERMISSION =
+            new LoggingPermission("control", null);
+
+    /**
+     * Creates a new instance of LoggingProviderImpl.
+     * @throws SecurityException if the calling code does not have the
+     * {@code RuntimePermission("loggerFinder")}.
+     */
+    public LoggingProviderImpl() {
+    }
+
+    /**
+     * A logger that delegates to a java.util.logging.Logger delegate.
+     */
+    static final class JULWrapper extends LoggerConfiguration
+            implements System.Logger, PlatformLogger.Bridge,
+                       PlatformLogger.ConfigurableBridge {
+
+
+        private static final java.util.logging.Level[] spi2JulLevelMapping = {
+                java.util.logging.Level.ALL,     // mapped from ALL
+                java.util.logging.Level.FINER,   // mapped from TRACE
+                java.util.logging.Level.FINE,    // mapped from DEBUG
+                java.util.logging.Level.INFO,    // mapped from INFO
+                java.util.logging.Level.WARNING, // mapped from WARNING
+                java.util.logging.Level.SEVERE,  // mapped from ERROR
+                java.util.logging.Level.OFF      // mapped from OFF
+        };
+
+        private static final java.util.logging.Level[] platform2JulLevelMapping = {
+                java.util.logging.Level.ALL,     // mapped from ALL
+                java.util.logging.Level.FINEST,  // mapped from FINEST
+                java.util.logging.Level.FINER,   // mapped from FINER
+                java.util.logging.Level.FINE,    // mapped from FINE
+                java.util.logging.Level.CONFIG,  // mapped from CONFIG
+                java.util.logging.Level.INFO,    // mapped from INFO
+                java.util.logging.Level.WARNING, // mapped from WARNING
+                java.util.logging.Level.SEVERE,  // mapped from SEVERE
+                java.util.logging.Level.OFF      // mapped from OFF
+        };
+
+        private final java.util.logging.Logger julLogger;
+
+
+        private JULWrapper(java.util.logging.Logger logger) {
+            this.julLogger = logger;
+        }
+
+        @Override
+        public String getName() {
+            return julLogger.getName();
+        }
+        @Override
+        public void log(sun.util.logging.PlatformLogger.Level level, String msg, Throwable throwable) {
+            julLogger.log(toJUL(level), msg, throwable);
+        }
+
+        @Override
+        public void log(sun.util.logging.PlatformLogger.Level level, String format, Object... params) {
+            julLogger.log(toJUL(level), format, params);
+        }
+
+        @Override
+        public void log(sun.util.logging.PlatformLogger.Level level, String msg) {
+            julLogger.log(toJUL(level), msg);
+        }
+
+        @Override
+        public void log(sun.util.logging.PlatformLogger.Level level, Supplier<String> msgSuppier) {
+            julLogger.log(toJUL(level), msgSuppier);
+        }
+
+        @Override
+        public void log(sun.util.logging.PlatformLogger.Level level, Throwable thrown, Supplier<String> msgSuppier) {
+            julLogger.log(toJUL(level), thrown, msgSuppier);
+        }
+
+        @Override
+        public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, String key, Throwable throwable) {
+            julLogger.logrb(toJUL(level), bundle, key, throwable);
+        }
+
+        @Override
+        public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, String key, Object... params) {
+            julLogger.logrb(toJUL(level), bundle, key, params);
+        }
+
+        @Override
+        public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, String msg) {
+            julLogger.logp(toJUL(level), sourceClass, sourceMethod, msg);
+        }
+
+        @Override
+        public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+                Supplier<String> msgSupplier) {
+            julLogger.logp(toJUL(level), sourceClass, sourceMethod, msgSupplier);
+        }
+
+        @Override
+        public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+                String msg, Object... params) {
+            julLogger.logp(toJUL(level), sourceClass, sourceMethod, msg, params);
+        }
+
+        @Override
+        public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+                String msg, Throwable thrown) {
+            julLogger.logp(toJUL(level), sourceClass, sourceMethod, msg, thrown);
+        }
+
+        @Override
+        public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+                Throwable thrown, Supplier<String> msgSupplier) {
+            julLogger.logp(toJUL(level), sourceClass, sourceMethod,
+                    thrown, msgSupplier);
+        }
+
+        @Override
+        public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+                ResourceBundle bundle, String key, Object... params) {
+            julLogger.logrb(toJUL(level), sourceClass, sourceMethod,
+                    bundle, key, params);
+        }
+
+        @Override
+        public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+                ResourceBundle bundle, String key, Throwable thrown) {
+            julLogger.logrb(toJUL(level), sourceClass, sourceMethod,
+                    bundle, key, thrown);
+        }
+
+        @Override
+        public  boolean isLoggable(sun.util.logging.PlatformLogger.Level level) {
+            return julLogger.isLoggable(toJUL(level));
+        }
+
+        // -----------------------------------------------------------------
+        // Generic methods taking a Level as parameter
+        // -----------------------------------------------------------------
+
+
+        @Override
+        public boolean isLoggable(Level level) {
+            return julLogger.isLoggable(toJUL(level));
+        }
+
+        @Override
+        public void log(Level level, String msg) {
+            julLogger.log(toJUL(level), msg);
+        }
+
+        @Override
+        public void log(Level level,
+                        Supplier<String> msgSupplier) {
+            // We need to check for null here to satisfy the contract
+            // of System.Logger - because the underlying implementation
+            // of julLogger will check for it only if the level is
+            // loggable
+            Objects.requireNonNull(msgSupplier);
+            julLogger.log(toJUL(level), msgSupplier);
+        }
+
+        @Override
+        public void log(Level level, Object obj) {
+            // We need to check for null here to satisfy the contract
+            // of System.Logger - because the underlying implementation
+            // of julLogger will check for it only if the level is
+            // loggable
+            Objects.requireNonNull(obj);
+            julLogger.log(toJUL(level), () -> obj.toString());
+        }
+
+        @Override
+        public void log(Level level,
+                        String msg, Throwable thrown) {
+            julLogger.log(toJUL(level), msg, thrown);
+        }
+
+        @Override
+        public void log(Level level, Supplier<String> msgSupplier,
+                        Throwable thrown) {
+            // We need to check for null here to satisfy the contract
+            // of System.Logger - because the underlying implementation
+            // of julLogger will check for it only if the level is
+            // loggable
+            Objects.requireNonNull(msgSupplier);
+            julLogger.log(toJUL(level), thrown, msgSupplier);
+        }
+
+        @Override
+        public void log(Level level,
+                        String format, Object... params) {
+            julLogger.log(toJUL(level), format, params);
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle bundle,
+                        String key, Throwable thrown) {
+            julLogger.logrb(toJUL(level), bundle, key, thrown);
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle bundle,
+                        String format, Object... params) {
+            julLogger.logrb(toJUL(level), bundle, format, params);
+        }
+
+        static java.util.logging.Level toJUL(Level level) {
+            if (level == null) return null;
+            assert level.ordinal() < spi2JulLevelMapping.length;
+            return spi2JulLevelMapping[level.ordinal()];
+        }
+
+        // ---------------------------------------------------------
+        // Methods from PlatformLogger.Bridge
+        // ---------------------------------------------------------
+
+        @Override
+        public boolean isEnabled() {
+            return julLogger.getLevel() != java.util.logging.Level.OFF;
+        }
+
+        @Override
+        public PlatformLogger.Level getPlatformLevel() {
+            final java.util.logging.Level javaLevel = julLogger.getLevel();
+            if (javaLevel == null) return null;
+            try {
+                return PlatformLogger.Level.valueOf(javaLevel.getName());
+            } catch (IllegalArgumentException e) {
+                return PlatformLogger.Level.valueOf(javaLevel.intValue());
+            }
+        }
+
+        @Override
+        public void setPlatformLevel(PlatformLogger.Level level) {
+            // null is allowed here
+            julLogger.setLevel(toJUL(level));
+        }
+
+        @Override
+        public LoggerConfiguration getLoggerConfiguration() {
+            return this;
+        }
+
+        static java.util.logging.Level toJUL(PlatformLogger.Level level) {
+            // The caller will throw if null is invalid in its context.
+            // There's at least one case where a null level is valid.
+            if (level == null) return null;
+            assert level.ordinal() < platform2JulLevelMapping.length;
+            return platform2JulLevelMapping[level.ordinal()];
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return (obj instanceof JULWrapper)
+                    && obj.getClass() == this.getClass()
+                    && ((JULWrapper)obj).julLogger == this.julLogger;
+        }
+
+        @Override
+        public int hashCode() {
+            return julLogger.hashCode();
+        }
+
+        // A JULWrapper is just a stateless thin shell over a JUL logger - so
+        // for a given JUL logger, we could always return the same wrapper.
+        //
+        // This is an optimization which may - or may not - be worth the
+        // trouble: if many classes use the same logger, and if each class
+        // keeps a reference to that logger, then caching the wrapper will
+        // be worthwhile. Otherwise, if each logger is only referred once,
+        // then the cache will eat up more memory than would be necessary...
+        //
+        // Here is an example of how we could implement JULWrapper.of(...)
+        // if we wanted to create at most one wrapper instance for each logger
+        // instance:
+        //
+        //        static final WeakHashMap<JULWrapper, WeakReference<JULWrapper>>
+        //                wrappers = new WeakHashMap<>();
+        //
+        //        static JULWrapper of(java.util.logging.Logger logger) {
+        //
+        //            // First access without synchronizing
+        //            final JULWrapper candidate = new JULWrapper(logger);
+        //            WeakReference<JULWrapper> ref = wrappers.get(candidate);
+        //            JULWrapper found = ref.get();
+        //
+        //            // OK - we found it - lets return it.
+        //            if (found != null) return found;
+        //
+        //            // Not found. Need to synchronize.
+        //            synchronized (wrappers) {
+        //                ref = wrappers.get(candidate);
+        //                found = ref.get();
+        //                if (found == null) {
+        //                    wrappers.put(candidate, new WeakReference<>(candidate));
+        //                    found = candidate;
+        //                }
+        //            }
+        //            assert found != null;
+        //            return found;
+        //        }
+        //
+        // But given that it may end up eating more memory in the nominal case
+        // (where each class that does logging has its own logger with the
+        //  class name as logger name and stashes that logger away in a static
+        //  field, thus making the cache redundant - as only one wrapper will
+        //  ever be created anyway) - then we will simply return a new wrapper
+        // for each invocation of JULWrapper.of(...) - which may
+        // still prove more efficient in terms of memory consumption...
+        //
+        static JULWrapper of(java.util.logging.Logger logger) {
+            return new JULWrapper(logger);
+        }
+
+
+    }
+
+    /**
+     * Creates a java.util.logging.Logger for the given caller.
+     * @param name the logger name.
+     * @param caller the caller for which the logger should be created.
+     * @return a Logger suitable for use in the given caller.
+     */
+    private static java.util.logging.Logger demandJULLoggerFor(final String name,
+                                                            /* Module */
+                                                            final Class<?> caller) {
+        final LogManager manager = LogManager.getLogManager();
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm == null) {
+            return logManagerAccess.demandLoggerFor(manager, name, caller);
+        } else {
+            final PrivilegedAction<java.util.logging.Logger> pa =
+                    () -> logManagerAccess.demandLoggerFor(manager, name, caller);
+            return AccessController.doPrivileged(pa, null, LOGGING_CONTROL_PERMISSION);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @apiNote The logger returned by this method can be configured through
+     * its {@linkplain java.util.logging.LogManager#getLogger(String)
+     * corresponding java.util.logging.Logger backend}.
+     *
+     * @return {@inheritDoc}
+     * @throws SecurityException if the calling code doesn't have the
+     * {@code RuntimePermission("loggerFinder")}.
+     */
+    @Override
+    protected Logger demandLoggerFor(String name, /* Module */ Class<?> caller) {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(LOGGERFINDER_PERMISSION);
+        }
+        return JULWrapper.of(demandJULLoggerFor(name,caller));
+    }
+
+    public static interface LogManagerAccess {
+        java.util.logging.Logger demandLoggerFor(LogManager manager,
+                String name, /* Module */ Class<?> caller);
+    }
+
+    // Hook for tests
+    public static LogManagerAccess getLogManagerAccess() {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(LOGGING_CONTROL_PERMISSION);
+        }
+        // Triggers initialization of accessJulLogger if not set.
+        if (logManagerAccess == null) LogManager.getLogManager();
+        return logManagerAccess;
+    }
+
+
+    private static volatile LogManagerAccess logManagerAccess;
+    public static void setLogManagerAccess(LogManagerAccess accesLoggers) {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(LOGGING_CONTROL_PERMISSION);
+        }
+        logManagerAccess = accesLoggers;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.logging/share/classes/sun/util/logging/internal/package-info.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+/**
+ * <p>
+ * <b>[JDK INTERNAL]</b>
+ * The {@code sun.util.logging.internal} package defines an internal
+ * implementation of the {@link jdk.internal.logger.DefaultLoggerFinder} which
+ * provides an extension of the {@link java.lang.System.Logger System.Logger}
+ * interface making it easy to bridge from {@link java.util.logging};
+ * the JDK default implementation of the LoggerFinder will return loggers
+ * implementing this extension when {@code java.util.logging} is present.
+ * </p>
+ * <p>
+ * When {@code java.util.logging} is present, Logger instances returned by
+ * the JDK default implementation of the LoggerFinder
+ * wrap an instance of {@link java.util.logging.Logger java.util.logging.Logger}
+ * and implement the {@link
+ * sun.util.logging.PlatformLogger.Bridge PlatformLogger.Bridge}
+ * extension, overriding all the methods defined in
+ * that extension in order to call the corresponding methods on their wrapped
+ * {@linkplain java.util.logging.Logger backend Logger} instance.
+ * <p>
+ * <br>
+ * @see java.lang.System.LoggerFinder
+ * @see java.lang.System.Logger
+ * @see sun.util.logging.PlatformLogger
+ * @see sun.util.logging.PlatformLogger.Bridge
+ * @see jdk.internal.logger
+ *
+ * @since 1.9
+ */
+package sun.util.logging.internal;
--- a/src/java.management/share/classes/java/lang/management/DefaultPlatformMBeanProvider.java	Fri Nov 20 15:34:12 2015 +0100
+++ b/src/java.management/share/classes/java/lang/management/DefaultPlatformMBeanProvider.java	Fri Nov 20 19:26:16 2015 +0100
@@ -355,36 +355,38 @@
             }
         });
 
-        /**
-         * Logging facility.
-         */
-        initMBeanList.add(new PlatformComponent<PlatformLoggingMXBean>() {
-            private final Set<String> platformLoggingMXBeanInterfaceNames
+        if (ManagementFactoryHelper.isPlatformLoggingMXBeanAvailable()) {
+            /**
+             * Logging facility.
+             */
+            initMBeanList.add(new PlatformComponent<PlatformLoggingMXBean>() {
+                private final Set<String> platformLoggingMXBeanInterfaceNames
                     = Collections.unmodifiableSet(Collections.singleton(
                             "java.lang.management.PlatformLoggingMXBean"));
 
-            @Override
-            public Set<Class<? extends PlatformLoggingMXBean>> mbeanInterfaces() {
-                return Collections.singleton(PlatformLoggingMXBean.class);
-            }
+                @Override
+                public Set<Class<? extends PlatformLoggingMXBean>> mbeanInterfaces() {
+                    return Collections.singleton(PlatformLoggingMXBean.class);
+                }
 
-            @Override
-            public Set<String> mbeanInterfaceNames() {
-                return platformLoggingMXBeanInterfaceNames;
-            }
+                @Override
+                public Set<String> mbeanInterfaceNames() {
+                    return platformLoggingMXBeanInterfaceNames;
+                }
 
-            @Override
-            public String getObjectNamePattern() {
-                return "java.util.logging:type=Logging";
-            }
+                @Override
+                public String getObjectNamePattern() {
+                    return "java.util.logging:type=Logging";
+                }
 
-            @Override
-            public Map<String, PlatformLoggingMXBean> nameToMBeanMap() {
-                return Collections.singletonMap(
+                @Override
+                public Map<String, PlatformLoggingMXBean> nameToMBeanMap() {
+                    return Collections.singletonMap(
                         "java.util.logging:type=Logging",
                         ManagementFactoryHelper.getPlatformLoggingMXBean());
-            }
-        });
+                }
+            });
+        }
 
         /**
          * Buffer pools.
--- a/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java	Fri Nov 20 15:34:12 2015 +0100
+++ b/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java	Fri Nov 20 19:26:16 2015 +0100
@@ -39,10 +39,14 @@
 
 import jdk.internal.misc.JavaNioAccess;
 import jdk.internal.misc.SharedSecrets;
-import sun.util.logging.LoggingSupport;
+
 import java.util.ArrayList;
 import java.util.List;
 
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.security.PrivilegedAction;
+
 /**
  * ManagementFactoryHelper provides static factory methods to create
  * instances of the management interface.
@@ -141,13 +145,17 @@
     }
 
     public static PlatformLoggingMXBean getPlatformLoggingMXBean() {
-        if (LoggingSupport.isAvailable()) {
+        if (LoggingMXBeanSupport.isAvailable()) {
             return PlatformLoggingImpl.instance;
         } else {
             return null;
         }
     }
 
+    public static boolean isPlatformLoggingMXBeanAvailable() {
+        return LoggingMXBeanSupport.isAvailable();
+    }
+
     /**
      * The logging MXBean object is an instance of
      * PlatformLoggingMXBean and java.util.logging.LoggingMXBean
@@ -165,8 +173,44 @@
         extends PlatformLoggingMXBean, java.util.logging.LoggingMXBean {
     }
 
+    // This is a trick: if java.util.logging is not present then
+    // attempting to access something that implements
+    // java.util.logging.LoggingMXBean will trigger a CNFE.
+    // So we cannot directly call any static method or access any static field
+    // on PlatformLoggingImpl, as we would risk raising a CNFE.
+    // Instead we use this intermediate LoggingMXBeanSupport class to determine
+    // whether java.util.logging is present, and load the actual LoggingMXBean
+    // implementation.
+    //
+    static final class LoggingMXBeanSupport {
+        final static Object loggingImpl =
+                AccessController.doPrivileged(new PrivilegedAction<Object>() {
+            @Override
+            public Object run() {
+                try {
+                    // create a LoggingProxyImpl instance when
+                    // java.util.logging classes exist
+                    Class<?> c = Class.forName("java.util.logging.Logging", true, null);
+                    Constructor<?> cons = c.getDeclaredConstructor();
+                    cons.setAccessible(true);
+                    return cons.newInstance();
+                } catch (ClassNotFoundException cnf) {
+                    return null;
+                } catch (NoSuchMethodException | InstantiationException
+                        | IllegalAccessException | InvocationTargetException e) {
+                    throw new AssertionError(e);
+                }
+            }});
+
+        static boolean isAvailable() {
+            return loggingImpl != null;
+        }
+    }
+
     static class PlatformLoggingImpl implements LoggingMXBean
     {
+        final static java.util.logging.LoggingMXBean impl =
+                (java.util.logging.LoggingMXBean) LoggingMXBeanSupport.loggingImpl;
         final static PlatformLoggingMXBean instance = new PlatformLoggingImpl();
         final static String LOGGING_MXBEAN_NAME = "java.util.logging:type=Logging";
 
@@ -188,22 +232,22 @@
 
         @Override
         public java.util.List<String> getLoggerNames() {
-            return LoggingSupport.getLoggerNames();
+            return impl.getLoggerNames();
         }
 
         @Override
         public String getLoggerLevel(String loggerName) {
-            return LoggingSupport.getLoggerLevel(loggerName);
+            return impl.getLoggerLevel(loggerName);
         }
 
         @Override
         public void setLoggerLevel(String loggerName, String levelName) {
-            LoggingSupport.setLoggerLevel(loggerName, levelName);
+            impl.setLoggerLevel(loggerName, levelName);
         }
 
         @Override
         public String getParentLoggerName(String loggerName) {
-            return LoggingSupport.getParentLoggerName(loggerName);
+            return impl.getParentLoggerName(loggerName);
         }
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/System/Logger/Level/LoggerLevelTest.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+
+import java.lang.System.Logger.Level;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Set;
+/**
+ * @test
+ * @bug 8140364
+ * @summary Tests System.Logger.Level names and severity.
+ * @author danielfuchs
+ */
+public class LoggerLevelTest {
+    public static void main(String[] args) {
+        Set<Level> untested = EnumSet.allOf(Level.class);
+        testLevel(untested, Level.ALL, java.util.logging.Level.ALL);
+        testLevel(untested, Level.TRACE, java.util.logging.Level.FINER);
+        testLevel(untested, Level.DEBUG, java.util.logging.Level.FINE);
+        testLevel(untested, Level.INFO, java.util.logging.Level.INFO);
+        testLevel(untested, Level.WARNING, java.util.logging.Level.WARNING);
+        testLevel(untested, Level.ERROR, java.util.logging.Level.SEVERE);
+        testLevel(untested, Level.OFF, java.util.logging.Level.OFF);
+        if (!untested.isEmpty()) {
+            throw new RuntimeException("Some level values were not tested: " + untested);
+        }
+    }
+
+    private static void testLevel(Set<Level> untested, Level systemLevel, java.util.logging.Level julLevel) {
+        untested.remove(systemLevel);
+        assertEquals(systemLevel.getName(), systemLevel.name(),
+                "System.Logger.Level." + systemLevel.name() + ".getName()");
+        assertEquals(systemLevel.getSeverity(), julLevel.intValue(),
+                "System.Logger.Level." + systemLevel.name() + ".getSeverity");
+    }
+
+    private static void assertEquals(Object actual, Object expected, String what) {
+        if (!Objects.equals(actual, expected)) {
+            throw new RuntimeException("Bad value for " + what
+                    + "\n\t expected: " + expected
+                    + "\n\t   actual: " + actual);
+        } else {
+            System.out.println("Got expected value for " + what + ": " + actual);
+        }
+    }
+
+    private static void assertEquals(int actual, int expected, String what) {
+        if (!Objects.equals(actual, expected)) {
+            throw new RuntimeException("Bad value for " + what
+                    + "\n\t expected: " + toString(expected)
+                    + "\n\t   actual: " + toString(actual));
+        } else {
+            System.out.println("Got expected value for " + what + ": " + toString(actual));
+        }
+    }
+
+    private static String toString(int value) {
+        switch (value) {
+            case Integer.MAX_VALUE: return "Integer.MAX_VALUE";
+            case Integer.MIN_VALUE: return "Integer.MIN_VALUE";
+            default:
+                return Integer.toString(value);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/System/Logger/custom/AccessSystemLogger.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ResourceBundle;
+
+/**
+ *
+ * @author danielfuchs
+ */
+public final class AccessSystemLogger {
+
+    public AccessSystemLogger() {
+        this(check());
+    }
+
+    private AccessSystemLogger(Void unused) {
+    }
+
+    private static Void check() {
+        if (AccessSystemLogger.class.getClassLoader() != null) {
+            throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader");
+        }
+        return null;
+    }
+
+    public Logger getLogger(String name) {
+        Logger logger = System.getLogger(name);
+        System.out.println("System.getLogger(\"" + name + "\"): " + logger);
+        return logger;
+    }
+
+    public Logger getLogger(String name, ResourceBundle bundle) {
+        Logger logger = System.getLogger(name, bundle);
+        System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger);
+        return logger;
+    }
+
+    // copy AccessSystemLogger.class to ./boot
+    public static void main(String[] args) throws IOException {
+        Path testDir = Paths.get(System.getProperty("user.dir", "."));
+        Path bootDir = Paths.get(testDir.toString(), "boot");
+        Path classes = Paths.get(System.getProperty("test.classes", "build/classes"));
+        Path thisClass = Paths.get(classes.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        if (Files.notExists(bootDir)) {
+            Files.createDirectory(bootDir);
+        }
+        Path dest = Paths.get(bootDir.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/System/Logger/custom/CustomLoggerTest.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,728 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.ResourceBundle;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.stream.Stream;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary Tests loggers returned by System.getLogger with a naive implementation
+ *          of LoggerFinder, and in particular the default body of
+ *          System.Logger methods.
+ * @build CustomLoggerTest AccessSystemLogger
+ * @run driver AccessSystemLogger
+ * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest NOSECURITY
+ * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest NOPERMISSIONS
+ * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest WITHPERMISSIONS
+ * @author danielfuchs
+ */
+public class CustomLoggerTest {
+
+    final static AtomicLong sequencer = new AtomicLong();
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+
+    public static class BaseLoggerFinder extends LoggerFinder {
+        final ConcurrentHashMap<String, LoggerImpl> system = new ConcurrentHashMap<>();
+        final ConcurrentHashMap<String, LoggerImpl> user = new ConcurrentHashMap<>();
+        public Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
+
+        // changing this to true requires changing the logic in the
+        // test in order to load this class with a protection domain
+        // that has the CONTROL_PERMISSION (e.g. by using a custom
+        // system class loader.
+        final boolean doChecks = false;
+
+        public static final class LogEvent {
+
+            public LogEvent() {
+                this(sequencer.getAndIncrement());
+            }
+
+            LogEvent(long sequenceNumber) {
+                this.sequenceNumber = sequenceNumber;
+            }
+
+            long sequenceNumber;
+            boolean isLoggable;
+            String loggerName;
+            Level level;
+            ResourceBundle bundle;
+            Throwable thrown;
+            Object[] args;
+            Supplier<String> supplier;
+            String msg;
+
+            Object[] toArray() {
+                return new Object[] {
+                    sequenceNumber,
+                    isLoggable,
+                    loggerName,
+                    level,
+                    bundle,
+                    thrown,
+                    args,
+                    supplier,
+                    msg,
+                };
+            }
+
+            @Override
+            public String toString() {
+                return Arrays.deepToString(toArray());
+            }
+
+
+
+            @Override
+            public boolean equals(Object obj) {
+                return obj instanceof LogEvent
+                        && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray());
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(toArray());
+            }
+
+
+            public static LogEvent of(boolean isLoggable, String name,
+                    Level level, ResourceBundle bundle,
+                    String key, Throwable thrown) {
+                LogEvent evt = new LogEvent();
+                evt.isLoggable = isLoggable;
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = null;
+                evt.bundle = bundle;
+                evt.thrown = thrown;
+                evt.supplier = null;
+                evt.msg = key;
+                return evt;
+            }
+
+            public static LogEvent of(boolean isLoggable, String name,
+                    Level level, ResourceBundle bundle,
+                    String key, Object... params) {
+                LogEvent evt = new LogEvent();
+                evt.isLoggable = isLoggable;
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = params;
+                evt.bundle = bundle;
+                evt.thrown = null;
+                evt.supplier = null;
+                evt.msg = key;
+                return evt;
+            }
+
+            public static LogEvent of(long sequenceNumber,
+                    boolean isLoggable, String name,
+                    Level level, ResourceBundle bundle,
+                    String key, Supplier<String> supplier,
+                    Throwable thrown, Object... params) {
+                LogEvent evt = new LogEvent(sequenceNumber);
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = params;
+                evt.bundle = bundle;
+                evt.thrown = thrown;
+                evt.supplier = supplier;
+                evt.msg = key;
+                evt.isLoggable = isLoggable;
+                return evt;
+            }
+
+        }
+
+        public class LoggerImpl implements Logger {
+            private final String name;
+            private Level level = Level.INFO;
+
+            public LoggerImpl(String name) {
+                this.name = name;
+            }
+
+            @Override
+            public String getName() {
+                return name;
+            }
+
+            @Override
+            public boolean isLoggable(Level level) {
+                return this.level != Level.OFF && this.level.getSeverity() <= level.getSeverity();
+            }
+
+            @Override
+            public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown));
+            }
+
+            @Override
+            public void log(Level level, ResourceBundle bundle, String format, Object... params) {
+                log(LogEvent.of(isLoggable(level), name, level, bundle, format, params));
+            }
+
+            void log(LogEvent event) {
+                eventQueue.add(event);
+            }
+        }
+
+        @Override
+        public Logger getLogger(String name, Class<?> caller) {
+            // We should check the permission to obey the API contract, but
+            // what happens if we don't?
+            // This is the main difference compared with what we test in
+            // java/lang/System/LoggerFinder/BaseLoggerFinderTest
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null && doChecks) {
+                sm.checkPermission(SimplePolicy.LOGGERFINDER_PERMISSION);
+            }
+
+            PrivilegedAction<ClassLoader> pa = () -> caller.getClassLoader();
+            ClassLoader callerLoader = AccessController.doPrivileged(pa);
+            if (callerLoader == null) {
+                return system.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+            } else {
+                return user.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+            }
+        }
+    }
+
+    static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger();
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            Policy.setPolicy(new SimplePolicy(allowControl));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+    public static void main(String[] args) {
+        if (args.length == 0)
+            args = new String[] {
+                "NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+
+        // 1. Obtain destination loggers directly from the LoggerFinder
+        //   - LoggerFinder.getLogger("foo", type)
+        BaseLoggerFinder provider =
+                BaseLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+        BaseLoggerFinder.LoggerImpl appSink =
+                BaseLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", CustomLoggerTest.class));
+        BaseLoggerFinder.LoggerImpl sysSink =
+                BaseLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class));
+
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    test(provider, true, appSink, sysSink);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    setSecurityManager();
+                    test(provider, false, appSink, sysSink);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with control permission\n");
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        test(provider, true, appSink, sysSink);
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + sequencer.get() + " cases.");
+    }
+
+    public static void test(BaseLoggerFinder provider, boolean hasRequiredPermissions,
+            BaseLoggerFinder.LoggerImpl appSink, BaseLoggerFinder.LoggerImpl sysSink) {
+
+        ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
+        final Map<Logger, String> loggerDescMap = new HashMap<>();
+
+
+        // 1. Test loggers returned by:
+        //   - System.getLogger("foo")
+        //   - and AccessSystemLogger.getLogger("foo")
+        Logger appLogger1 = System.getLogger("foo");
+        loggerDescMap.put(appLogger1, "System.getLogger(\"foo\");");
+
+        Logger sysLogger1 = null;
+        try {
+            sysLogger1 = accessSystemLogger.getLogger("foo");
+            loggerDescMap.put(sysLogger1, "AccessSystemLogger.getLogger(\"foo\")");
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            throw new RuntimeException("unexpected exception: " + acx, acx);
+        }
+
+        if (appLogger1 == sysLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        if (provider.system.contains(appLogger1)) {
+            throw new RuntimeException("app logger in system map");
+        }
+        if (provider.user.contains(sysLogger1)) {
+            throw new RuntimeException("sys logger in appplication map");
+        }
+        if (provider.system.contains(sysLogger1)) {
+            // sysLogger should be a a LazyLoggerWrapper
+            throw new RuntimeException("sys logger is in system map (should be wrapped)");
+        }
+
+
+        // 2. Test loggers returned by:
+        //   - System.getLogger(\"foo\", loggerBundle)
+        //   - and AccessSystemLogger.getLogger(\"foo\", loggerBundle)
+        Logger appLogger2 =
+                System.getLogger("foo", loggerBundle);
+        loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)");
+
+        Logger sysLogger2 = null;
+        try {
+            sysLogger2 = accessSystemLogger.getLogger("foo", loggerBundle);
+            loggerDescMap.put(sysLogger2, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)");
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            throw new RuntimeException("unexpected exception: " + acx, acx);
+        }
+        if (appLogger2 == sysLogger2) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (appLogger2 == appSink) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (sysLogger2 == sysSink) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        if (provider.system.contains(appLogger2)) {
+            throw new RuntimeException("localized app logger in system map");
+        }
+        if (provider.user.contains(appLogger2)) {
+            throw new RuntimeException("localized app logger  in appplication map");
+        }
+        if (provider.user.contains(sysLogger2)) {
+            throw new RuntimeException("localized sys logger in appplication map");
+        }
+        if (provider.system.contains(sysLogger2)) {
+            throw new RuntimeException("localized sys logger not in system map");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", null, appLogger1, appSink);
+        testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink);
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appSink);
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysSink);
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    // Calls the 8 methods defined on Logger and verify the
+    // parameters received by the underlying BaseLoggerFinder.LoggerImpl
+    // logger.
+    private static void testLogger(BaseLoggerFinder provider,
+            Map<Logger, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            Logger logger,
+            BaseLoggerFinder.LoggerImpl sink) {
+
+        System.out.println("Testing " + loggerDescMap.get(logger));
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, foo): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, messageLevel, (ResourceBundle)null,
+                            fooMsg, null, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, foo);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (provider.eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String msg = "blah";
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, loggerBundle,
+                            msg, null, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, msg);
+                BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+        Supplier<String> fooSupplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return this.toString();
+            }
+        };
+
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, messageLevel, (ResourceBundle)null,
+                            fooSupplier.get(), null,
+                            (Throwable)null, (Object[])null);
+                logger.log(messageLevel, fooSupplier);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (provider.eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = msg;
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, loggerBundle,
+                            format, null, (Throwable)null, new Object[] {arg1, arg2});
+                logger.log(messageLevel, format, arg1, arg2);
+                BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, loggerBundle,
+                            msg, null, thrown, (Object[]) null);
+                logger.log(messageLevel, msg, thrown);
+                BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, messageLevel, (ResourceBundle)null,
+                            fooSupplier.get(), null,
+                            (Throwable)thrown, (Object[])null);
+                logger.log(messageLevel, fooSupplier, thrown);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (provider.eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, bundle,
+                            format, null, (Throwable)null, new Object[] {foo, msg});
+                logger.log(messageLevel, bundle, format, foo, msg);
+                BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, bundle,
+                            msg, null, thrown, (Object[]) null);
+                logger.log(messageLevel, bundle, msg, thrown);
+                BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+
+        static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+        final Permissions permissions;
+        final Permissions allPermissions;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl) {
+            this.allowControl = allowControl;
+            permissions = new Permissions();
+
+            // these are used for configuring the test itself...
+            allPermissions = new Permissions();
+            allPermissions.add(LOGGERFINDER_PERMISSION);
+
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            if (allowControl.get().get()) return allPermissions.implies(permission);
+            return permissions.implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(allowControl.get().get()
+                    ? allPermissions : permissions).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(allowControl.get().get()
+                    ? allPermissions : permissions).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/System/Logger/custom/META-INF/services/java.lang.System$LoggerFinder	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,1 @@
+CustomLoggerTest$BaseLoggerFinder
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/System/Logger/default/AccessSystemLogger.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ResourceBundle;
+import java.util.logging.LogManager;
+
+/**
+ *
+ * @author danielfuchs
+ */
+public final class AccessSystemLogger {
+
+    public AccessSystemLogger() {
+        this(check());
+    }
+
+    private AccessSystemLogger(Void unused) {
+    }
+
+    private static Void check() {
+        if (AccessSystemLogger.class.getClassLoader() != null) {
+            throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader");
+        }
+        return null;
+    }
+
+    public Logger getLogger(String name) {
+        Logger logger = System.getLogger(name);
+        System.out.println("System.getLogger(\"" + name + "\"): " + logger);
+        return logger;
+    }
+
+    public Logger getLogger(String name, ResourceBundle bundle) {
+        Logger logger = System.getLogger(name, bundle);
+        System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger);
+        return logger;
+    }
+
+    public java.util.logging.Logger demandSystemLogger(String name) {
+        return java.util.logging.Logger.getLogger(name);
+    }
+
+    // copy AccessSystemLogger.class to ./boot
+    public static void main(String[] args) throws IOException {
+        Path testDir = Paths.get(System.getProperty("user.dir", "."));
+        Path bootDir = Paths.get(testDir.toString(), "boot");
+        Path classes = Paths.get(System.getProperty("test.classes", "build/classes"));
+        Path thisClass = Paths.get(classes.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        if (Files.notExists(bootDir)) {
+            Files.createDirectory(bootDir);
+        }
+        Path dest = Paths.get(bootDir.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/System/Logger/default/DefaultLoggerTest.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,726 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+import java.security.AccessControlException;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.ResourceBundle;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.stream.Stream;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary Tests default loggers returned by System.getLogger, and in
+ *          particular the implementation of the the System.Logger method
+ *          performed by the default binding.
+ *
+ * @build DefaultLoggerTest AccessSystemLogger
+ * @run driver AccessSystemLogger
+ * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest NOSECURITY
+ * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest NOPERMISSIONS
+ * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest WITHPERMISSIONS
+ * @author danielfuchs
+ */
+public class DefaultLoggerTest {
+
+    final static AtomicLong sequencer = new AtomicLong();
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    public static final Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
+
+    public static final class LogEvent {
+
+        public LogEvent() {
+            this(sequencer.getAndIncrement());
+        }
+
+        LogEvent(long sequenceNumber) {
+            this.sequenceNumber = sequenceNumber;
+        }
+
+        long sequenceNumber;
+        boolean isLoggable;
+        String loggerName;
+        java.util.logging.Level level;
+        ResourceBundle bundle;
+        Throwable thrown;
+        Object[] args;
+        String msg;
+        String className;
+        String methodName;
+
+        Object[] toArray() {
+            return new Object[] {
+                sequenceNumber,
+                isLoggable,
+                loggerName,
+                level,
+                bundle,
+                thrown,
+                args,
+                msg,
+                className,
+                methodName,
+            };
+        }
+
+        @Override
+        public String toString() {
+            return Arrays.deepToString(toArray());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return obj instanceof LogEvent
+                    && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(toArray());
+        }
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                java.util.logging.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            return LogEvent.of(sequenceNumber, isLoggable, name,
+                    DefaultLoggerTest.class.getName(),
+                    "testLogger", level, bundle, key,
+                    thrown, params);
+        }
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                String className, String methodName,
+                java.util.logging.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            LogEvent evt = new LogEvent(sequenceNumber);
+            evt.loggerName = name;
+            evt.level = level;
+            evt.args = params;
+            evt.bundle = bundle;
+            evt.thrown = thrown;
+            evt.msg = key;
+            evt.isLoggable = isLoggable;
+            evt.className = className;
+            evt.methodName = methodName;
+            return evt;
+        }
+
+    }
+
+    static java.util.logging.Level mapToJul(Level level) {
+        switch (level) {
+            case ALL: return java.util.logging.Level.ALL;
+            case TRACE: return java.util.logging.Level.FINER;
+            case DEBUG: return java.util.logging.Level.FINE;
+            case INFO: return java.util.logging.Level.INFO;
+            case WARNING: return java.util.logging.Level.WARNING;
+            case ERROR: return java.util.logging.Level.SEVERE;
+            case OFF: return java.util.logging.Level.OFF;
+        }
+        throw new InternalError("No such level: " + level);
+    }
+
+    static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) {
+        boolean before = allowAll.get().get();
+        try {
+            allowAll.get().set(true);
+            sink.setLevel(loggerLevel);
+        } finally {
+            allowAll.get().set(before);
+        }
+    }
+
+    public static class MyHandler extends Handler {
+
+        @Override
+        public java.util.logging.Level getLevel() {
+            return java.util.logging.Level.ALL;
+        }
+
+        @Override
+        public void publish(LogRecord record) {
+            eventQueue.add(LogEvent.of(sequencer.getAndIncrement(),
+                    true, record.getLoggerName(),
+                    record.getSourceClassName(),
+                    record.getSourceMethodName(),
+                    record.getLevel(),
+                    record.getResourceBundle(), record.getMessage(),
+                    record.getThrown(), record.getParameters()));
+        }
+        @Override
+        public void flush() {
+        }
+        @Override
+        public void close() throws SecurityException {
+        }
+
+    }
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+    static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger();
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            Policy.setPolicy(new SimplePolicy(allowControl, allowAll));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+    public static void main(String[] args) {
+        if (args.length == 0)
+            args = new String[] {
+                "NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+
+        // 1. Obtain destination loggers directly from the LoggerFinder
+        //   - LoggerFinder.getLogger("foo", type)
+
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    test(true);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    setSecurityManager();
+                    test(false);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with control permission\n");
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        test(true);
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + sequencer.get() + " cases.");
+    }
+
+    public static void test(boolean hasRequiredPermissions) {
+
+        ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
+        final Map<Logger, String> loggerDescMap = new HashMap<>();
+
+
+        // 1. Test loggers returned by:
+        //   - System.getLogger("foo")
+        //   - and AccessSystemLogger.getLogger("foo")
+        Logger sysLogger1 = null;
+        try {
+            sysLogger1 = accessSystemLogger.getLogger("foo");
+            loggerDescMap.put(sysLogger1, "AccessSystemLogger.getLogger(\"foo\")");
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            throw new RuntimeException("unexpected exception: " + acx, acx);
+        }
+
+        Logger appLogger1 = System.getLogger("foo");
+        loggerDescMap.put(appLogger1, "System.getLogger(\"foo\");");
+
+        if (appLogger1 == sysLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        // 2. Test loggers returned by:
+        //   - System.getLogger(\"foo\", loggerBundle)
+        //   - and AccessSystemLogger.getLogger(\"foo\", loggerBundle)
+        Logger appLogger2 =
+                System.getLogger("foo", loggerBundle);
+        loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)");
+
+        Logger sysLogger2 = null;
+        try {
+            sysLogger2 = accessSystemLogger.getLogger("foo", loggerBundle);
+            loggerDescMap.put(sysLogger2, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)");
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            throw new RuntimeException("unexpected exception: " + acx, acx);
+        }
+        if (appLogger2 == sysLogger2) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        final java.util.logging.Logger appSink;
+        final java.util.logging.Logger sysSink;
+        final java.util.logging.Handler appHandler;
+        final java.util.logging.Handler sysHandler;
+        final  LoggerFinder provider;
+        allowAll.get().set(true);
+        try {
+            appSink = java.util.logging.Logger.getLogger("foo");
+            sysSink = accessSystemLogger.demandSystemLogger("foo");
+            appSink.addHandler(appHandler = new MyHandler());
+            sysSink.addHandler(sysHandler = new MyHandler());
+            appSink.setUseParentHandlers(false);
+            sysSink.setUseParentHandlers(false);
+            provider = LoggerFinder.getLoggerFinder();
+        } finally {
+            allowAll.get().set(false);
+        }
+        try {
+            testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink);
+            testLogger(provider, loggerDescMap, "foo", null, appLogger1, appSink);
+            testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysSink);
+            testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appSink);
+        } finally {
+            allowAll.get().set(true);
+            try {
+                appSink.removeHandler(appHandler);
+                sysSink.removeHandler(sysHandler);
+                sysSink.setLevel(null);
+                appSink.setLevel(null);
+            } finally {
+                allowAll.get().set(false);
+            }
+        }
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    // Calls the 8 methods defined on Logger and verify the
+    // parameters received by the underlying BaseLoggerFinder.LoggerImpl
+    // logger.
+    private static void testLogger(LoggerFinder provider,
+            Map<Logger, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            Logger logger,
+            java.util.logging.Logger sink) {
+
+        System.out.println("Testing " + loggerDescMap.get(logger));
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, foo): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, mapToJul(messageLevel), (ResourceBundle)null,
+                            fooMsg, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, foo);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual = eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String msg = "blah";
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, mapToJul(messageLevel), loggerBundle,
+                            msg, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, msg);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        Supplier<String> fooSupplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return this.toString();
+            }
+        };
+
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, mapToJul(messageLevel), (ResourceBundle)null,
+                            fooSupplier.get(),
+                            (Throwable)null, (Object[])null);
+                logger.log(messageLevel, fooSupplier);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = msg;
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, mapToJul(messageLevel), loggerBundle,
+                            format, (Throwable)null, new Object[] {arg1, arg2});
+                logger.log(messageLevel, format, arg1, arg2);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, mapToJul(messageLevel), loggerBundle,
+                            msg, thrown, (Object[]) null);
+                logger.log(messageLevel, msg, thrown);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, mapToJul(messageLevel), (ResourceBundle)null,
+                            fooSupplier.get(),
+                            (Throwable)thrown, (Object[])null);
+                logger.log(messageLevel, fooSupplier, thrown);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, mapToJul(messageLevel), bundle,
+                            format, (Throwable)null, new Object[] {foo, msg});
+                logger.log(messageLevel, bundle, format, foo, msg);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, mapToJul(messageLevel), bundle,
+                            msg, thrown, (Object[]) null);
+                logger.log(messageLevel, bundle, msg, thrown);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+        static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+        final Permissions permissions;
+        final Permissions allPermissions;
+        final Permissions controlPermissions;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        final ThreadLocal<AtomicBoolean> allowAll;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl, ThreadLocal<AtomicBoolean> allowAll) {
+            this.allowControl = allowControl;
+            this.allowAll = allowAll;
+            permissions = new Permissions();
+
+            // these are used for configuring the test itself...
+            controlPermissions = new Permissions();
+            controlPermissions.add(LOGGERFINDER_PERMISSION);
+            allPermissions = new Permissions();
+            allPermissions.add(new java.security.AllPermission());
+
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            if (allowAll.get().get()) return allPermissions.implies(permission);
+            if (allowControl.get().get()) return controlPermissions.implies(permission);
+            return permissions.implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(allowAll.get().get()
+                    ? allPermissions : allowControl.get().get()
+                    ? controlPermissions : permissions).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(allowAll.get().get()
+                    ? allPermissions : allowControl.get().get()
+                    ? controlPermissions : permissions).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/System/Logger/interface/LoggerInterfaceTest.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,592 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+
+import java.util.ResourceBundle;
+import java.util.function.Consumer;
+import java.lang.System.Logger.Level;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.function.Supplier;
+
+/**
+ * @test
+ * @bug 8140364
+ * @summary Tests the default body of the System.Logger interface.
+ * @author danielfuchs
+ */
+public class LoggerInterfaceTest {
+
+    public static class LoggerImpl implements System.Logger {
+
+        public static class LogEvent implements Cloneable {
+            Level level;
+            ResourceBundle bundle;
+            String msg;
+            Throwable thrown;
+            Object[] params;
+            StackTraceElement[] callStack;
+
+            @Override
+            protected LogEvent clone() {
+                try {
+                    return (LogEvent)super.clone();
+                } catch (CloneNotSupportedException x) {
+                    throw new RuntimeException(x);
+                }
+            }
+
+
+        }
+
+        public static class LogEventBuilder {
+            private LogEvent event = new LogEvent();
+            public LogEventBuilder level(Level level) {
+                event.level = level;
+                return this;
+            }
+            public LogEventBuilder stack(StackTraceElement... stack) {
+                event.callStack = stack;
+                return this;
+            }
+            public LogEventBuilder bundle(ResourceBundle bundle) {
+                event.bundle = bundle;
+                return this;
+            }
+            public LogEventBuilder msg(String msg) {
+                event.msg = msg;
+                return this;
+            }
+            public LogEventBuilder thrown(Throwable thrown) {
+                event.thrown = thrown;
+                return this;
+            }
+            public LogEventBuilder params(Object... params) {
+                event.params = params;
+                return this;
+            }
+            public LogEvent build() {
+                return event.clone();
+            }
+
+            public LogEventBuilder clear() {
+                event = new LogEvent();
+                return this;
+            }
+
+        }
+
+        Level level = Level.WARNING;
+        Consumer<LogEvent> consumer;
+        final LogEventBuilder builder = new LogEventBuilder();
+
+        @Override
+        public String getName() {
+            return "noname";
+        }
+
+        @Override
+        public boolean isLoggable(Level level) {
+            return level.getSeverity() >= this.level.getSeverity();
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
+            builder.clear().level(level).bundle(bundle).msg(msg).thrown(thrown)
+                    .stack(new Exception().getStackTrace());
+            consumer.accept(builder.build());
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle bundle, String format, Object... params) {
+            builder.clear().level(level).bundle(bundle).msg(format).params(params)
+                    .stack(new Exception().getStackTrace());
+            consumer.accept(builder.build());
+        }
+
+    }
+
+    static class Throwing {
+        @Override
+        public String toString() {
+            throw new RuntimeException("should not have been called");
+        }
+    }
+    static class NotTrowing {
+        private final String toString;
+        private int count = 0;
+        public NotTrowing(String toString) {
+            this.toString = toString;
+        }
+
+        @Override
+        public String toString() {
+            return toString + "[" + (++count) + "]";
+        }
+    }
+
+    public static void main(String[] args) {
+        final LoggerImpl loggerImpl = new LoggerImpl();
+        final System.Logger logger = loggerImpl;
+        final Queue<LoggerImpl.LogEvent> events = new LinkedList<>();
+        loggerImpl.consumer = (x) -> events.add(x);
+
+        System.out.println("\nlogger.isLoggable(Level)");
+        assertTrue(logger.isLoggable(Level.WARNING), "logger.isLoggable(Level.WARNING)","  ");
+        assertFalse(logger.isLoggable(Level.INFO), "logger.isLoggable(Level.INFO)", "  ");
+
+
+        System.out.println("\nlogger.log(Level, Object)");
+        for (Level l : Level.values()) {
+            boolean logged = l.compareTo(Level.WARNING) >= 0;
+            Object[][] cases = new Object[][] {
+                {null}, {"baz"}
+            };
+            for (Object[] p : cases) {
+                String msg = (String)p[0];
+                final Object obj = msg == null ? null : logged ? new NotTrowing(msg) : new Throwing();
+                String par1 = msg == null ? "(Object)null"
+                        : logged ? "new NotTrowing(\""+ msg+"\")" : "new Throwing()";
+                System.out.println("  logger.log(" + l + ", " +  par1 + ")");
+                try {
+                    logger.log(l, obj);
+                    if (obj == null) {
+                        throw new RuntimeException("Expected NullPointerException not thrown for"
+                                  + " logger.log(" + l + ", " +  par1 + ")");
+                    }
+                } catch (NullPointerException x) {
+                    if (obj == null) {
+                        System.out.println("    Got expected exception: " + x);
+                        continue;
+                    } else {
+                        throw x;
+                    }
+                }
+                LoggerImpl.LogEvent e = events.poll();
+                if (logged) {
+                    assertNonNull(e, "e", "    ");
+                    assertEquals(l, e.level, "e.level", "    ");
+                    assertToString(e.msg, msg, 1, "e.msg", "    ");
+                    assertEquals(e.bundle, null, "e.bundle", "    ");
+                    assertEquals(e.params, null, "e.params", "    ");
+                    assertEquals(e.thrown, null, "e.thrown", "    ");
+                    assertEquals(e.bundle, null, "e.bundle", "    ");
+                    assertEquals(e.callStack[0].getMethodName(), "log",
+                                 "e.callStack[0].getMethodName()", "    ");
+                    assertEquals(e.callStack[0].getClassName(),
+                                logger.getClass().getName(),
+                                "e.callStack[0].getClassName() ", "    ");
+                    assertEquals(e.callStack[1].getMethodName(), "log",
+                                 "e.callStack[1].getMethodName()", "    ");
+                    assertEquals(e.callStack[1].getClassName(),
+                                 System.Logger.class.getName(),
+                                 "e.callStack[1].getClassName() ", "    ");
+                    assertEquals(e.callStack[2].getMethodName(), "main",
+                                 "e.callStack[2].getMethodName()", "    ");
+                } else {
+                    assertEquals(e, null, "e", "    ");
+                }
+            }
+        }
+        System.out.println("  logger.log(" + null + ", " +
+                "new NotThrowing(\"foobar\")" + ")");
+        try {
+            logger.log(null, new NotTrowing("foobar"));
+            throw new RuntimeException("Expected NullPointerException not thrown for"
+                                      + " logger.log(" + null + ", "
+                                      + "new NotThrowing(\"foobar\")" + ")");
+        } catch (NullPointerException x) {
+            System.out.println("    Got expected exception: " + x);
+        }
+
+
+        System.out.println("\nlogger.log(Level, String)");
+        for (Level l : Level.values()) {
+            boolean logged = l.compareTo(Level.WARNING) >= 0;
+            String par = "bar";
+            System.out.println("  logger.log(" + l + ", \"" +  par +"\");");
+            logger.log(l, par);
+            LoggerImpl.LogEvent e = events.poll();
+            assertNonNull(e, "e", "    ");
+            assertEquals(e.level, l, "e.level", "    ");
+            assertEquals(e.msg, "bar", "e.msg", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.params, null, "e.params", "    ");
+            assertEquals(e.thrown, null, "e.thrown", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.callStack[0].getMethodName(), "log",
+                         "e.callStack[0].getMethodName()", "    ");
+            assertEquals(e.callStack[0].getClassName(),
+                         logger.getClass().getName(),
+                         "e.callStack[0].getClassName() ", "    ");
+            assertEquals(e.callStack[1].getMethodName(), "log",
+                         "e.callStack[1].getMethodName()", "    ");
+            assertEquals(e.callStack[1].getClassName(),
+                         System.Logger.class.getName(),
+                         "e.callStack[1].getClassName() ", "    ");
+            assertEquals(e.callStack[2].getMethodName(), "main",
+                         "e.callStack[2].getMethodName()", "    ");
+
+            System.out.println("  logger.log(" + l + ", (String)null);");
+            logger.log(l, (String)null);
+            e = events.poll();
+            assertNonNull(e, "e", "    ");
+            assertEquals(e.level, l, "e.level", "    ");
+            assertEquals(e.msg, null, "e.msg", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.params, null, "e.params", "    ");
+            assertEquals(e.thrown, null, "e.thrown", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.callStack[0].getMethodName(), "log",
+                         "e.callStack[0].getMethodName()", "    ");
+            assertEquals(e.callStack[0].getClassName(),
+                         logger.getClass().getName(),
+                         "e.callStack[0].getClassName() ", "    ");
+            assertEquals(e.callStack[1].getMethodName(), "log",
+                         "e.callStack[1].getMethodName()", "    ");
+            assertEquals(e.callStack[1].getClassName(),
+                         System.Logger.class.getName(),
+                         "e.callStack[1].getClassName() ", "    ");
+            assertEquals(e.callStack[2].getMethodName(), "main",
+                         "e.callStack[2].getMethodName()", "    ");
+        }
+
+        System.out.println("\nlogger.log(Level, Supplier<String>)");
+        for (Level l : Level.values()) {
+            boolean logged = l.compareTo(Level.WARNING) >= 0;
+            Object[][] cases = new Object[][] {
+                {null}, {"baz"}
+            };
+            for (Object[] p : cases) {
+                String msg = (String)p[0];
+                final Object obj = msg == null ? null : logged ? new NotTrowing(msg) : new Throwing();
+                final Supplier<String> s = msg == null ? null : () -> obj.toString();
+                String par1 = msg == null ? "(Supplier<String>)null"
+                        : logged ? "() -> new NotTrowing(\""+ msg+"\").toString()" : "new Throwing()";
+                System.out.println("  logger.log(" + l + ", " +  par1 + ")");
+                try {
+                    logger.log(l, s);
+                    if (s == null) {
+                        throw new RuntimeException("Expected NullPointerException not thrown for"
+                                  + " logger.log(" + l + ", " +  par1 + ")");
+                    }
+                } catch (NullPointerException x) {
+                    if (s == null) {
+                        System.out.println("    Got expected exception: " + x);
+                        continue;
+                    } else {
+                        throw x;
+                    }
+                }
+                LoggerImpl.LogEvent e = events.poll();
+                if (logged) {
+                    assertNonNull(e, "e", "    ");
+                    assertEquals(l, e.level, "e.level", "    ");
+                    assertToString(e.msg, msg, 1, "e.msg", "    ");
+                    assertEquals(e.bundle, null, "e.bundle", "    ");
+                    assertEquals(e.params, null, "e.params", "    ");
+                    assertEquals(e.thrown, null, "e.thrown", "    ");
+                    assertEquals(e.bundle, null, "e.bundle", "    ");
+                    assertEquals(e.callStack[0].getMethodName(), "log",
+                                 "e.callStack[0].getMethodName()", "    ");
+                    assertEquals(e.callStack[0].getClassName(),
+                                 logger.getClass().getName(),
+                                 "e.callStack[0].getClassName() ", "    ");
+                    assertEquals(e.callStack[1].getMethodName(), "log",
+                                 "e.callStack[1].getMethodName()", "    ");
+                    assertEquals(e.callStack[1].getClassName(),
+                                 System.Logger.class.getName(),
+                                 "e.callStack[1].getClassName() ", "    ");
+                    assertEquals(e.callStack[2].getMethodName(), "main",
+                                 "e.callStack[2].getMethodName()", "    ");
+                } else {
+                    assertEquals(e, null, "e", "    ");
+                }
+            }
+        }
+        System.out.println("  logger.log(" + null + ", " + "() -> \"biz\"" + ")");
+        try {
+            logger.log(null, () -> "biz");
+            throw new RuntimeException("Expected NullPointerException not thrown for"
+                                      + " logger.log(" + null + ", "
+                                      + "() -> \"biz\"" + ")");
+        } catch (NullPointerException x) {
+            System.out.println("    Got expected exception: " + x);
+        }
+
+        System.out.println("\nlogger.log(Level, String, Object...)");
+        for (Level l : Level.values()) {
+            boolean logged = l.compareTo(Level.WARNING) >= 0;
+            String par = "bam";
+            Object[] params = null;
+            System.out.println("  logger.log(" + l + ", \"" +  par +"\", null);");
+            logger.log(l, par, params);
+            LoggerImpl.LogEvent e = events.poll();
+            assertNonNull(e, "e", "    ");
+            assertEquals(l, e.level, "e.level", "    ");
+            assertEquals(e.msg, "bam", "e.msg", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.params, null, "e.params", "    ");
+            assertEquals(e.thrown, null, "e.thrown", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.callStack[0].getMethodName(), "log",
+                        "e.callStack[0].getMethodName()", "    ");
+            assertEquals(e.callStack[0].getClassName(),
+                         logger.getClass().getName(),
+                         "e.callStack[0].getClassName() ", "    ");
+            assertEquals(e.callStack[1].getMethodName(), "log",
+                         "e.callStack[1].getMethodName()", "    ");
+            assertEquals(e.callStack[1].getClassName(),
+                         System.Logger.class.getName(),
+                         "e.callStack[1].getClassName() ", "    ");
+            assertEquals(e.callStack[2].getMethodName(), "main",
+                         "e.callStack[2].getMethodName()", "    ");
+
+            params = new Object[] {new NotTrowing("one")};
+            par = "bam {0}";
+            System.out.println("  logger.log(" + l + ", \"" +  par
+                               + "\", new NotTrowing(\"one\"));");
+            logger.log(l, par, params[0]);
+            e = events.poll();
+            assertNonNull(e, "e", "    ");
+            assertEquals(l, e.level, "e.level", "    ");
+            assertEquals(e.msg, par, "e.msg", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertArrayEquals(e.params, params, "e.params", "    ");
+            assertEquals(e.thrown, null, "e.thrown", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.callStack[0].getMethodName(), "log",
+                         "e.callStack[0].getMethodName()", "    ");
+            assertEquals(e.callStack[0].getClassName(),
+                         logger.getClass().getName(),
+                         "e.callStack[0].getClassName() ", "    ");
+            assertEquals(e.callStack[1].getMethodName(), "log",
+                         "e.callStack[1].getMethodName()", "    ");
+            assertEquals(e.callStack[1].getClassName(),
+                         System.Logger.class.getName(),
+                         "e.callStack[1].getClassName() ", "    ");
+            assertEquals(e.callStack[2].getMethodName(), "main",
+                         "e.callStack[2].getMethodName()", "    ");
+
+            params = new Object[] {new NotTrowing("fisrt"), new NotTrowing("second")};
+            par = "bam {0} {1}";
+            System.out.println("  logger.log(" + l + ", \"" +  par
+                              + "\", new NotTrowing(\"fisrt\"),"
+                              + " new NotTrowing(\"second\"));");
+            logger.log(l, par, params[0], params[1]);
+            e = events.poll();
+            assertNonNull(e, "e", "    ");
+            assertEquals(l, e.level, "e.level", "    ");
+            assertEquals(e.msg, par, "e.msg", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertArrayEquals(e.params, params, "e.params", "    ");
+            assertEquals(e.thrown, null, "e.thrown", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.callStack[0].getMethodName(), "log",
+                         "e.callStack[0].getMethodName()", "    ");
+            assertEquals(e.callStack[0].getClassName(),
+                         logger.getClass().getName(),
+                         "e.callStack[0].getClassName() ", "    ");
+            assertEquals(e.callStack[1].getMethodName(), "log",
+                         "e.callStack[1].getMethodName()", "    ");
+            assertEquals(e.callStack[1].getClassName(),
+                         System.Logger.class.getName(),
+                         "e.callStack[1].getClassName() ", "    ");
+            assertEquals(e.callStack[2].getMethodName(), "main",
+                         "e.callStack[2].getMethodName()", "    ");
+
+            params = new Object[] {new NotTrowing("third"), new NotTrowing("fourth")};
+            par = "bam {2}";
+            System.out.println("  logger.log(" + l + ", \"" +  par
+                              + "\", new Object[] {new NotTrowing(\"third\"),"
+                              + " new NotTrowing(\"fourth\")});");
+            logger.log(l, par, params);
+            e = events.poll();
+            assertNonNull(e, "e", "    ");
+            assertEquals(l, e.level, "e.level", "    ");
+            assertEquals(e.msg, par, "e.msg", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertArrayEquals(e.params, params, "e.params", "    ");
+            assertEquals(e.thrown, null, "e.thrown", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.callStack[0].getMethodName(), "log",
+                         "e.callStack[0].getMethodName()", "    ");
+            assertEquals(e.callStack[0].getClassName(), logger.getClass().getName(),
+                         "e.callStack[0].getClassName() ", "    ");
+            assertEquals(e.callStack[1].getMethodName(), "log",
+                         "e.callStack[1].getMethodName()", "    ");
+            assertEquals(e.callStack[1].getClassName(),
+                         System.Logger.class.getName(),
+                         "e.callStack[1].getClassName() ", "    ");
+            assertEquals(e.callStack[2].getMethodName(), "main",
+                        "e.callStack[2].getMethodName()", "    ");
+        }
+
+        System.out.println("\nlogger.log(Level, String, Throwable)");
+        for (Level l : Level.values()) {
+            boolean logged = l.compareTo(Level.WARNING) >= 0;
+            Object[][] cases = new Object[][] {
+                {null, null}, {null, new Throwable()}, {"biz", null}, {"boz", new Throwable()}
+            };
+            for (Object[] p : cases) {
+                String msg = (String)p[0];
+                Throwable thrown = (Throwable)p[1];
+                String par1 = msg == null ? "(String)null" : "\"" + msg + "\"";
+                String par2 = thrown == null ? "(Throwable)null" : "new Throwable()";
+                System.out.println("  logger.log(" + l + ", " +  par1 +", " + par2 + ")");
+                logger.log(l, msg, thrown);
+                LoggerImpl.LogEvent e = events.poll();
+                assertNonNull(e, "e", "    ");
+                assertEquals(e.level, l, "e.level", "    ");
+                assertEquals(e.msg, msg, "e.msg", "    ");
+                assertEquals(e.bundle, null, "e.bundle", "    ");
+                assertEquals(e.params, null, "e.params", "    ");
+                assertEquals(e.thrown, thrown, "e.thrown", "    ");
+                assertEquals(e.bundle, null, "e.bundle", "    ");
+                assertEquals(e.callStack[0].getMethodName(),
+                             "log", "e.callStack[0].getMethodName()", "    ");
+                assertEquals(e.callStack[0].getClassName(),
+                            logger.getClass().getName(),
+                            "e.callStack[0].getClassName() ", "    ");
+                assertEquals(e.callStack[1].getMethodName(), "log",
+                             "e.callStack[1].getMethodName()", "    ");
+                assertEquals(e.callStack[1].getClassName(),
+                            System.Logger.class.getName(),
+                            "e.callStack[1].getClassName() ", "    ");
+                assertEquals(e.callStack[2].getMethodName(), "main",
+                             "e.callStack[2].getMethodName()", "    ");
+            }
+        }
+
+        System.out.println("\nlogger.log(Level, Supplier<String>, Throwable)");
+        for (Level l : Level.values()) {
+            boolean logged = l.compareTo(Level.WARNING) >= 0;
+            Object[][] cases = new Object[][] {
+                {null, null}, {null, new Throwable()}, {"biz", null}, {"boz", new Throwable()}
+            };
+            for (Object[] p : cases) {
+                String msg = (String)p[0];
+                Throwable thrown = (Throwable)p[1];
+                final Object obj = msg == null ? null : logged ? new NotTrowing(msg) : new Throwing();
+                final Supplier<String> s = msg == null ? null : () -> obj.toString();
+                String par1 = msg == null ? "(Supplier<String>)null"
+                        : logged ? "() -> new NotTrowing(\""+ msg+"\").toString()" : "new Throwing()";
+                String par2 = thrown == null ? "(Throwable)null" : "new Throwable()";
+                System.out.println("  logger.log(" + l + ", " +  par1 +", " + par2 + ")");
+                try {
+                    logger.log(l, s, thrown);
+                    if (s== null) {
+                        throw new RuntimeException("Expected NullPointerException not thrown for"
+                                  + " logger.log(" + l + ", " +  par1 +", " + par2 + ")");
+                    }
+                } catch (NullPointerException x) {
+                    if (s == null) {
+                        System.out.println("    Got expected exception: " + x);
+                        continue;
+                    } else {
+                        throw x;
+                    }
+                }
+                LoggerImpl.LogEvent e = events.poll();
+                if (logged) {
+                    assertNonNull(e, "e", "    ");
+                    assertEquals(l, e.level, "e.level", "    ");
+                    assertToString(e.msg, msg, 1, "e.msg", "    ");
+                    assertEquals(e.bundle, null, "e.bundle", "    ");
+                    assertEquals(e.params, null, "e.params", "    ");
+                    assertEquals(e.thrown, thrown, "e.thrown", "    ");
+                    assertEquals(e.bundle, null, "e.bundle", "    ");
+                    assertEquals(e.callStack[0].getMethodName(), "log",
+                                 "e.callStack[0].getMethodName()", "    ");
+                    assertEquals(e.callStack[0].getClassName(),
+                                 logger.getClass().getName(),
+                                 "e.callStack[0].getClassName() ", "    ");
+                    assertEquals(e.callStack[1].getMethodName(), "log",
+                                 "e.callStack[1].getMethodName()", "    ");
+                    assertEquals(e.callStack[1].getClassName(),
+                                 System.Logger.class.getName(),
+                                 "e.callStack[1].getClassName() ", "    ");
+                    assertEquals(e.callStack[2].getMethodName(), "main",
+                                 "e.callStack[2].getMethodName()", "    ");
+                } else {
+                    assertEquals(e, null, "e", "    ");
+                }
+            }
+        }
+        System.out.println("  logger.log(" + null + ", " + "() -> \"biz\""
+                           + ", " + "new Throwable()" + ")");
+        try {
+            logger.log(null, () -> "biz", new Throwable());
+            throw new RuntimeException("Expected NullPointerException not thrown for"
+                                      + " logger.log(" + null + ", "
+                                      + "() -> \"biz\"" + ", "
+                                      + "new Throwable()" + ")");
+        } catch (NullPointerException x) {
+            System.out.println("    Got expected exception: " + x);
+        }
+
+        System.out.println("Checking that we have no spurious events in the queue");
+        assertEquals(events.poll(), null, "events.poll()", "  ");
+    }
+
+    static void assertTrue(boolean test, String what, String prefix) {
+        if (!test) {
+            throw new RuntimeException("Expected true for " + what);
+        }
+        System.out.println(prefix + "Got expected " + what + ": " + test);
+    }
+    static void assertFalse(boolean test, String what, String prefix) {
+        if (test) {
+            throw new RuntimeException("Expected false for " + what);
+        }
+        System.out.println(prefix + "Got expected " + what + ": " + test);
+    }
+    static void assertToString(String actual, String expected, int count, String what, String prefix) {
+        assertEquals(actual, expected + "["+count+"]", what, prefix);
+    }
+    static void assertEquals(Object actual, Object expected, String what, String prefix) {
+        if (!Objects.equals(actual, expected)) {
+            throw new RuntimeException("Bad " + what + ":"
+                    + "\n\t expected: " + expected
+                    + "\n\t   actual: " + actual);
+        }
+        System.out.println(prefix + "Got expected " + what + ": " + actual);
+    }
+    static void assertArrayEquals(Object[] actual, Object[] expected, String what, String prefix) {
+        if (!Objects.deepEquals(actual, expected)) {
+            throw new RuntimeException("Bad " + what + ":"
+                    + "\n\t expected: " + expected == null ? "null" : Arrays.deepToString(expected)
+                    + "\n\t   actual: " + actual == null ? "null" : Arrays.deepToString(actual));
+        }
+        System.out.println(prefix + "Got expected " + what + ": " + Arrays.deepToString(actual));
+    }
+    static void assertNonNull(Object actual, String what, String prefix) {
+        if (Objects.equals(actual, null)) {
+            throw new RuntimeException("Bad " + what + ":"
+                    + "\n\t expected: non null"
+                    + "\n\t   actual: " + actual);
+        }
+        System.out.println(prefix + "Got expected " + what + ": " + "non null");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/AccessSystemLogger.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ResourceBundle;
+
+/**
+ *
+ * @author danielfuchs
+ */
+public final class AccessSystemLogger {
+
+    public AccessSystemLogger() {
+        this(check());
+    }
+
+    private AccessSystemLogger(Void unused) {
+    }
+
+    private static Void check() {
+        if (AccessSystemLogger.class.getClassLoader() != null) {
+            throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader");
+        }
+        return null;
+    }
+
+    public Logger getLogger(String name) {
+        Logger logger = System.getLogger(name);
+        System.out.println("System.getLogger(\"" + name + "\"): " + logger);
+        return logger;
+    }
+
+    public Logger getLogger(String name, ResourceBundle bundle) {
+        Logger logger = System.getLogger(name, bundle);
+        System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger);
+        return logger;
+    }
+
+    // copy AccessSystemLogger.class to ./boot
+    public static void main(String[] args) throws IOException {
+        Path testDir = Paths.get(System.getProperty("user.dir", "."));
+        Path bootDir = Paths.get(testDir.toString(), "boot");
+        Path classes = Paths.get(System.getProperty("test.classes", "build/classes"));
+        Path thisClass = Paths.get(classes.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        if (Files.notExists(bootDir)) {
+            Files.createDirectory(bootDir);
+        }
+        Path dest = Paths.get(bootDir.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinder.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+
+public  class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder {
+
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+    @Override
+    public Logger getLogger(String name, Class<?> caller) {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(LOGGERFINDER_PERMISSION);
+        }
+        PrivilegedAction<ClassLoader> pa = () -> caller.getClassLoader();
+        ClassLoader callerLoader = AccessController.doPrivileged(pa);
+        if (callerLoader == null) {
+            return system.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+        } else {
+            return user.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinderTest.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,694 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+import java.security.AccessControlException;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.stream.Stream;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary Tests a naive implementation of LoggerFinder, and in particular
+ *   the default body of System.Logger methods.
+ * @build AccessSystemLogger BaseLoggerFinderTest CustomSystemClassLoader BaseLoggerFinder TestLoggerFinder
+ * @run  driver AccessSystemLogger
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerFinderTest NOSECURITY
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerFinderTest NOPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerFinderTest WITHPERMISSIONS
+ * @author danielfuchs
+ */
+public class BaseLoggerFinderTest {
+
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAccess = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    final static AccessSystemLogger accessSystemLogger = new AccessSystemLogger();
+    static final Class<?> providerClass;
+    static {
+        try {
+            providerClass = ClassLoader.getSystemClassLoader().loadClass("BaseLoggerFinder");
+        } catch (ClassNotFoundException ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+    }
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            Policy.setPolicy(new SimplePolicy(allowControl, allowAccess));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+
+    public static void main(String[] args) {
+        if (args.length == 0)
+            args = new String[] {
+                //"NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+
+        System.out.println("Using provider class: " + providerClass + "[" + providerClass.getClassLoader() + "]");
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            TestLoggerFinder provider;
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                    test(provider, true);
+                    System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    setSecurityManager();
+                    try {
+                        provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                        throw new RuntimeException("Expected exception not raised");
+                    } catch (AccessControlException x) {
+                        if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) {
+                            throw new RuntimeException("Unexpected permission check", x);
+                        }
+                        final boolean control = allowControl.get().get();
+                        try {
+                            allowControl.get().set(true);
+                            provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                        } finally {
+                            allowControl.get().set(control);
+                        }
+                    }
+                    test(provider, false);
+                    System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with control permission\n");
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                        test(provider, true);
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + TestLoggerFinder.sequencer.get() + " cases.");
+    }
+
+    public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) {
+
+        ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
+        final Map<Logger, String> loggerDescMap = new HashMap<>();
+
+
+        // 1. Test loggers returned by LoggerFinder, both for system callers
+        //    and not system callers.
+        TestLoggerFinder.LoggerImpl appLogger1 = null;
+        try {
+            appLogger1 =
+                TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerFinderTest.class));
+            loggerDescMap.put(appLogger1, "provider.getLogger(\"foo\", BaseLoggerFinderTest.class)");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for logger: " + acx);
+            final boolean old = allowControl.get().get();
+            allowControl.get().set(true);
+            try {
+                appLogger1 =
+                    TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerFinderTest.class));
+                    loggerDescMap.put(appLogger1, "provider.getLogger(\"foo\", BaseLoggerFinderTest.class)");
+            } finally {
+                allowControl.get().set(old);
+            }
+        }
+
+        TestLoggerFinder.LoggerImpl sysLogger1 = null;
+        try {
+            sysLogger1 = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class));
+            loggerDescMap.put(sysLogger1, "provider.getLogger(\"foo\", Thread.class)");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for system logger: " + acx);
+            final boolean old = allowControl.get().get();
+            allowControl.get().set(true);
+            try {
+                sysLogger1 = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class));
+                loggerDescMap.put(sysLogger1, "provider.getLogger(\"foo\", Thread.class)");
+            } finally {
+                allowControl.get().set(old);
+            }
+        }
+        if (appLogger1 == sysLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        if (provider.system.contains(appLogger1)) {
+            throw new RuntimeException("app logger in system map");
+        }
+        if (!provider.user.contains(appLogger1)) {
+            throw new RuntimeException("app logger not in appplication map");
+        }
+        if (provider.user.contains(sysLogger1)) {
+            throw new RuntimeException("sys logger in appplication map");
+        }
+        if (!provider.system.contains(sysLogger1)) {
+            throw new RuntimeException("sys logger not in system map");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", null, appLogger1, appLogger1);
+        testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysLogger1);
+
+        // 2. Test localized loggers returned LoggerFinder, both for system
+        //   callers and non system callers
+        Logger appLogger2 = null;
+        try {
+            appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, BaseLoggerFinderTest.class);
+            loggerDescMap.put(appLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, BaseLoggerFinderTest.class)");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for logger: " + acx);
+            final boolean old = allowControl.get().get();
+            allowControl.get().set(true);
+            try {
+                appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, BaseLoggerFinderTest.class);
+                loggerDescMap.put(appLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, BaseLoggerFinderTest.class)");
+            } finally {
+                allowControl.get().set(old);
+            }
+        }
+
+        Logger sysLogger2 = null;
+        try {
+            sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class);
+            loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class)");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for localized system logger: " + acx);
+            final boolean old = allowControl.get().get();
+            allowControl.get().set(true);
+            try {
+                sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class);
+                loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class))");
+            } finally {
+                allowControl.get().set(old);
+            }
+        }
+        if (appLogger2 == sysLogger2) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (appLogger2 == appLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (sysLogger2 == sysLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        if (provider.system.contains(appLogger2)) {
+            throw new RuntimeException("localized app logger in system map");
+        }
+        if (provider.user.contains(appLogger2)) {
+            throw new RuntimeException("localized app logger  in appplication map");
+        }
+        if (provider.user.contains(sysLogger2)) {
+            throw new RuntimeException("localized sys logger in appplication map");
+        }
+        if (provider.system.contains(sysLogger2)) {
+            throw new RuntimeException("localized sys logger not in system map");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appLogger1);
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysLogger1);
+
+        // 3 Test loggers returned by:
+        //   3.1: System.getLogger("foo")
+        Logger appLogger3 = System.getLogger("foo");
+        loggerDescMap.put(appLogger3, "System.getLogger(\"foo\")");
+        testLogger(provider, loggerDescMap, "foo", null, appLogger3, appLogger1);
+
+        //   3.2: System.getLogger("foo")
+        //        Emulate what System.getLogger() does when the caller is a
+        //        platform classes
+        Logger sysLogger3 = accessSystemLogger.getLogger("foo");
+        loggerDescMap.put(sysLogger3, "AccessSystemLogger.getLogger(\"foo\")");
+
+        if (appLogger3 == sysLogger3) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", null, sysLogger3, sysLogger1);
+
+        // 4. Test loggers returned by:
+        //    4.1 System.getLogger("foo", loggerBundle)
+        Logger appLogger4 =
+                System.getLogger("foo", loggerBundle);
+        loggerDescMap.put(appLogger4, "System.getLogger(\"foo\", loggerBundle)");
+        if (appLogger4 == appLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger4, appLogger1);
+
+        //   4.2: System.getLogger("foo", loggerBundle)
+        //        Emulate what System.getLogger() does when the caller is a
+        //        platform classes
+        Logger sysLogger4 = accessSystemLogger.getLogger("foo", loggerBundle);
+        loggerDescMap.put(sysLogger4, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)");
+        if (appLogger4 == sysLogger4) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger4, sysLogger1);
+
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    // Calls the 8 methods defined on Logger and verify the
+    // parameters received by the underlying TestProvider.LoggerImpl
+    // logger.
+    private static void testLogger(TestLoggerFinder provider,
+            Map<Logger, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            Logger logger,
+            TestLoggerFinder.LoggerImpl sink) {
+
+        System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]");
+        AtomicLong sequencer = TestLoggerFinder.sequencer;
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, foo): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, messageLevel, (ResourceBundle)null,
+                            fooMsg, null, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, foo);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (provider.eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String msg = "blah";
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, loggerBundle,
+                            msg, null, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, msg);
+                TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+        Supplier<String> fooSupplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return this.toString();
+            }
+        };
+
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, messageLevel, (ResourceBundle)null,
+                            fooSupplier.get(), null,
+                            (Throwable)null, (Object[])null);
+                logger.log(messageLevel, fooSupplier);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (provider.eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = msg;
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, loggerBundle,
+                            format, null, (Throwable)null, new Object[] {foo, msg});
+                logger.log(messageLevel, format, foo, msg);
+                TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, loggerBundle,
+                            msg, null, thrown, (Object[]) null);
+                logger.log(messageLevel, msg, thrown);
+                TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, messageLevel, (ResourceBundle)null,
+                            fooSupplier.get(), null,
+                            (Throwable)thrown, (Object[])null);
+                logger.log(messageLevel, fooSupplier, thrown);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (provider.eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, bundle,
+                            format, null, (Throwable)null, new Object[] {foo, msg});
+                logger.log(messageLevel, bundle, format, foo, msg);
+                TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, bundle,
+                            msg, null, thrown, (Object[]) null);
+                logger.log(messageLevel, bundle, msg, thrown);
+                TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+        final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION;
+        final static RuntimePermission ACCESS = new RuntimePermission("accessClassInPackage.jdk.internal.logger");
+
+        final Permissions permissions;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        final ThreadLocal<AtomicBoolean> allowAccess;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl, ThreadLocal<AtomicBoolean> allowAccess) {
+            this.allowControl = allowControl;
+            this.allowAccess = allowAccess;
+            permissions = new Permissions();
+        }
+
+        Permissions getPermissions() {
+            if (allowControl.get().get() || allowAccess.get().get()) {
+                PermissionsBuilder builder =  new PermissionsBuilder()
+                        .addAll(permissions);
+                if (allowControl.get().get()) {
+                    builder.add(CONTROL);
+                }
+                if (allowAccess.get().get()) {
+                    builder.add(ACCESS);
+                }
+                return builder.toPermissions();
+            }
+            return permissions;
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            return getPermissions().implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/CustomSystemClassLoader.java	Fri Nov 20 19:26:16 2015 +0100
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * This code is distrib