8259065: Optimize MessageDigest.getInstance

Reviewed-by: valeriep
This commit is contained in:
Claes Redestad 2021-01-08 09:20:42 +00:00
parent 712014c595
commit fc1d2a1e8e
4 changed files with 259 additions and 137 deletions

View file

@ -30,6 +30,7 @@ import java.io.ByteArrayOutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import sun.security.jca.GetInstance;
import sun.security.util.Debug; import sun.security.util.Debug;
import sun.security.util.MessageDigestSpi2; import sun.security.util.MessageDigestSpi2;
@ -176,18 +177,19 @@ public abstract class MessageDigest extends MessageDigestSpi {
* @see Provider * @see Provider
*/ */
public static MessageDigest getInstance(String algorithm) public static MessageDigest getInstance(String algorithm)
throws NoSuchAlgorithmException { throws NoSuchAlgorithmException
{
Objects.requireNonNull(algorithm, "null algorithm name"); Objects.requireNonNull(algorithm, "null algorithm name");
try {
MessageDigest md; MessageDigest md;
Object[] objs = Security.getImpl(algorithm, "MessageDigest",
(String)null); GetInstance.Instance instance = GetInstance.getInstance("MessageDigest",
if (objs[0] instanceof MessageDigest) { MessageDigestSpi.class, algorithm);
md = (MessageDigest)objs[0]; if (instance.impl instanceof MessageDigest messageDigest) {
md.provider = (Provider)objs[1]; md = messageDigest;
md.provider = instance.provider;
} else { } else {
md = Delegate.of((MessageDigestSpi)objs[0], algorithm, md = Delegate.of((MessageDigestSpi)instance.impl, algorithm,
(Provider) objs[1]); instance.provider);
} }
if (!skipDebug && pdebug != null) { if (!skipDebug && pdebug != null) {
@ -196,10 +198,6 @@ public abstract class MessageDigest extends MessageDigestSpi {
} }
return md; return md;
} catch(NoSuchProviderException e) {
throw new NoSuchAlgorithmException(algorithm + " not found");
}
} }
/** /**
@ -245,17 +243,18 @@ public abstract class MessageDigest extends MessageDigestSpi {
Objects.requireNonNull(algorithm, "null algorithm name"); Objects.requireNonNull(algorithm, "null algorithm name");
if (provider == null || provider.isEmpty()) if (provider == null || provider.isEmpty())
throw new IllegalArgumentException("missing provider"); throw new IllegalArgumentException("missing provider");
Object[] objs = Security.getImpl(algorithm, "MessageDigest", provider);
if (objs[0] instanceof MessageDigest) { MessageDigest md;
MessageDigest md = (MessageDigest)objs[0]; GetInstance.Instance instance = GetInstance.getInstance("MessageDigest",
md.provider = (Provider)objs[1]; MessageDigestSpi.class, algorithm, provider);
return md; if (instance.impl instanceof MessageDigest messageDigest) {
md = messageDigest;
md.provider = instance.provider;
} else { } else {
MessageDigest delegate = md = Delegate.of((MessageDigestSpi)instance.impl, algorithm,
Delegate.of((MessageDigestSpi)objs[0], algorithm, instance.provider);
(Provider)objs[1]);
return delegate;
} }
return md;
} }
/** /**

View file

@ -147,44 +147,7 @@ public abstract class Provider extends Properties {
private transient boolean initialized; private transient boolean initialized;
private static Object newInstanceUtil(final Class<?> clazz, private static final Object[] EMPTY = new Object[0];
final Class<?> ctrParamClz, final Object ctorParamObj)
throws Exception {
if (ctrParamClz == null) {
Constructor<?> con = clazz.getConstructor();
return con.newInstance();
} else {
// Looking for the constructor with a params first and fallback
// to one without if not found. This is to support the enhanced
// SecureRandom where both styles of constructors are supported.
// Before jdk9, there was no params support (only getInstance(alg))
// and an impl only had the params-less constructor. Since jdk9,
// there is getInstance(alg,params) and an impl can contain
// an Impl(params) constructor.
try {
Constructor<?> con = clazz.getConstructor(ctrParamClz);
return con.newInstance(ctorParamObj);
} catch (NoSuchMethodException nsme) {
// For pre-jdk9 SecureRandom implementations, they only
// have params-less constructors which still works when
// the input ctorParamObj is null.
//
// For other primitives using params, ctorParamObj should not
// be null and nsme is thrown, just like before.
if (ctorParamObj == null) {
try {
Constructor<?> con = clazz.getConstructor();
return con.newInstance();
} catch (NoSuchMethodException nsme2) {
nsme.addSuppressed(nsme2);
throw nsme;
}
} else {
throw nsme;
}
}
}
}
private static double parseVersionStr(String s) { private static double parseVersionStr(String s) {
try { try {
@ -1106,16 +1069,15 @@ public abstract class Provider extends Properties {
this.algorithm = intern ? algorithm.intern() : algorithm; this.algorithm = intern ? algorithm.intern() : algorithm;
} }
public int hashCode() { public int hashCode() {
return Objects.hash(type, algorithm); return type.hashCode() * 31 + algorithm.hashCode();
} }
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) { if (this == obj) {
return true; return true;
} }
if (!(obj instanceof ServiceKey)) { if (!(obj instanceof ServiceKey other)) {
return false; return false;
} }
ServiceKey other = (ServiceKey)obj;
return this.type.equals(other.type) return this.type.equals(other.type)
&& this.algorithm.equals(other.algorithm); && this.algorithm.equals(other.algorithm);
} }
@ -1192,9 +1154,7 @@ public abstract class Provider extends Properties {
ServiceKey key = new ServiceKey(type, stdAlg, true); ServiceKey key = new ServiceKey(type, stdAlg, true);
Service s = legacyMap.get(key); Service s = legacyMap.get(key);
if (s == null) { if (s == null) {
s = new Service(this); s = new Service(this, type, stdAlg);
s.type = type;
s.algorithm = stdAlg;
legacyMap.put(key, s); legacyMap.put(key, s);
} }
legacyMap.put(new ServiceKey(type, aliasAlg, true), s); legacyMap.put(new ServiceKey(type, aliasAlg, true), s);
@ -1213,9 +1173,7 @@ public abstract class Provider extends Properties {
ServiceKey key = new ServiceKey(type, stdAlg, true); ServiceKey key = new ServiceKey(type, stdAlg, true);
Service s = legacyMap.get(key); Service s = legacyMap.get(key);
if (s == null) { if (s == null) {
s = new Service(this); s = new Service(this, type, stdAlg);
s.type = type;
s.algorithm = stdAlg;
legacyMap.put(key, s); legacyMap.put(key, s);
} }
s.className = className; s.className = className;
@ -1238,9 +1196,7 @@ public abstract class Provider extends Properties {
ServiceKey key = new ServiceKey(type, stdAlg, true); ServiceKey key = new ServiceKey(type, stdAlg, true);
Service s = legacyMap.get(key); Service s = legacyMap.get(key);
if (s == null) { if (s == null) {
s = new Service(this); s = new Service(this, type, stdAlg);
s.type = type;
s.algorithm = stdAlg;
legacyMap.put(key, s); legacyMap.put(key, s);
} }
s.addAttribute(attributeName, attributeValue); s.addAttribute(attributeName, attributeValue);
@ -1673,14 +1629,24 @@ public abstract class Provider extends Properties {
* @since 1.5 * @since 1.5
*/ */
public static class Service { public static class Service {
private final String type;
private String type, algorithm, className; private final String algorithm;
private String className;
private final Provider provider; private final Provider provider;
private List<String> aliases; private List<String> aliases;
private Map<UString,String> attributes; private Map<UString,String> attributes;
private final EngineDescription engineDescription;
// Reference to the cached implementation Class object // Reference to the cached implementation Class object.
private volatile Reference<Class<?>> classRef; // Will be a Class if this service is loaded from the built-in
// classloader (unloading not possible), otherwise a WeakReference to a
// Class
private Object classCache;
// Will be a Constructor if this service is loaded from the built-in
// classloader (unloading not possible), otherwise a WeakReference to
// a Constructor
private Object constructorCache;
// flag indicating whether this service has its attributes for // flag indicating whether this service has its attributes for
// supportedKeyFormats or supportedKeyClasses set // supportedKeyFormats or supportedKeyClasses set
@ -1702,8 +1668,11 @@ public abstract class Provider extends Properties {
// this constructor and these methods are used for parsing // this constructor and these methods are used for parsing
// the legacy string properties. // the legacy string properties.
private Service(Provider provider) { private Service(Provider provider, String type, String algorithm) {
this.provider = provider; this.provider = provider;
this.type = type;
this.algorithm = algorithm;
engineDescription = knownEngines.get(type);
aliases = Collections.<String>emptyList(); aliases = Collections.<String>emptyList();
attributes = Collections.<UString,String>emptyMap(); attributes = Collections.<UString,String>emptyMap();
} }
@ -1749,6 +1718,7 @@ public abstract class Provider extends Properties {
} }
this.provider = provider; this.provider = provider;
this.type = getEngineName(type); this.type = getEngineName(type);
engineDescription = knownEngines.get(type);
this.algorithm = algorithm; this.algorithm = algorithm;
this.className = className; this.className = className;
if (aliases == null) { if (aliases == null) {
@ -1863,7 +1833,7 @@ public abstract class Provider extends Properties {
} }
Class<?> ctrParamClz; Class<?> ctrParamClz;
try { try {
EngineDescription cap = knownEngines.get(type); EngineDescription cap = engineDescription;
if (cap == null) { if (cap == null) {
// unknown engine type, use generic code // unknown engine type, use generic code
// this is the code path future for non-core // this is the code path future for non-core
@ -1890,7 +1860,7 @@ public abstract class Provider extends Properties {
} }
} }
// constructorParameter can be null if not provided // constructorParameter can be null if not provided
return newInstanceUtil(getImplClass(), ctrParamClz, constructorParameter); return newInstanceUtil(ctrParamClz, constructorParameter);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
throw e; throw e;
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
@ -1906,11 +1876,59 @@ public abstract class Provider extends Properties {
} }
} }
private Object newInstanceOf() throws Exception {
Constructor<?> con = getDefaultConstructor();
return con.newInstance(EMPTY);
}
private Object newInstanceUtil(Class<?> ctrParamClz, Object ctorParamObj)
throws Exception
{
if (ctrParamClz == null) {
return newInstanceOf();
} else {
// Looking for the constructor with a params first and fallback
// to one without if not found. This is to support the enhanced
// SecureRandom where both styles of constructors are supported.
// Before jdk9, there was no params support (only getInstance(alg))
// and an impl only had the params-less constructor. Since jdk9,
// there is getInstance(alg,params) and an impl can contain
// an Impl(params) constructor.
try {
Constructor<?> con = getImplClass().getConstructor(ctrParamClz);
return con.newInstance(ctorParamObj);
} catch (NoSuchMethodException nsme) {
// For pre-jdk9 SecureRandom implementations, they only
// have params-less constructors which still works when
// the input ctorParamObj is null.
//
// For other primitives using params, ctorParamObj should not
// be null and nsme is thrown, just like before.
if (ctorParamObj == null) {
try {
return newInstanceOf();
} catch (NoSuchMethodException nsme2) {
nsme.addSuppressed(nsme2);
throw nsme;
}
} else {
throw nsme;
}
}
}
}
// return the implementation Class object for this service // return the implementation Class object for this service
private Class<?> getImplClass() throws NoSuchAlgorithmException { private Class<?> getImplClass() throws NoSuchAlgorithmException {
try { try {
Reference<Class<?>> ref = classRef; Object cache = classCache;
Class<?> clazz = (ref == null) ? null : ref.get(); if (cache instanceof Class<?> clazz) {
return clazz;
}
Class<?> clazz = null;
if (cache instanceof WeakReference<?> ref){
clazz = (Class<?>)ref.get();
}
if (clazz == null) { if (clazz == null) {
ClassLoader cl = provider.getClass().getClassLoader(); ClassLoader cl = provider.getClass().getClassLoader();
if (cl == null) { if (cl == null) {
@ -1923,7 +1941,7 @@ public abstract class Provider extends Properties {
("class configured for " + type + " (provider: " + ("class configured for " + type + " (provider: " +
provider.getName() + ") is not public."); provider.getName() + ") is not public.");
} }
classRef = new WeakReference<>(clazz); classCache = (cl == null) ? clazz : new WeakReference<Class<?>>(clazz);
} }
return clazz; return clazz;
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
@ -1933,6 +1951,26 @@ public abstract class Provider extends Properties {
} }
} }
private Constructor<?> getDefaultConstructor()
throws NoSuchAlgorithmException, NoSuchMethodException
{
Object cache = constructorCache;
if (cache instanceof Constructor<?> con) {
return con;
}
Constructor<?> con = null;
if (cache instanceof WeakReference<?> ref){
con = (Constructor<?>)ref.get();
}
if (con == null) {
Class<?> clazz = getImplClass();
con = clazz.getConstructor();
constructorCache = (clazz.getClassLoader() == null)
? con : new WeakReference<Constructor<?>>(con);
}
return con;
}
/** /**
* Test whether this Service can use the specified parameter. * Test whether this Service can use the specified parameter.
* Returns false if this service cannot use the parameter. Returns * Returns false if this service cannot use the parameter. Returns
@ -1960,7 +1998,7 @@ public abstract class Provider extends Properties {
* used with this type of service * used with this type of service
*/ */
public boolean supportsParameter(Object parameter) { public boolean supportsParameter(Object parameter) {
EngineDescription cap = knownEngines.get(type); EngineDescription cap = engineDescription;
if (cap == null) { if (cap == null) {
// unknown engine type, return true by default // unknown engine type, return true by default
return true; return true;

View file

@ -160,12 +160,18 @@ final class ProviderConfig {
* Get the provider object. Loads the provider if it is not already loaded. * Get the provider object. Loads the provider if it is not already loaded.
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
synchronized Provider getProvider() { Provider getProvider() {
// volatile variable load // volatile variable load
Provider p = provider; Provider p = provider;
if (p != null) { if (p != null) {
return p; return p;
} }
// DCL
synchronized (this) {
p = provider;
if (p != null) {
return p;
}
if (shouldLoad() == false) { if (shouldLoad() == false) {
return null; return null;
} }
@ -220,6 +226,7 @@ final class ProviderConfig {
} }
} }
provider = p; provider = p;
}
return p; return p;
} }

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2021, 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.
*/
package org.openjdk.bench.java.security;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
/**
* Micros for speed of looking up and instantiating MessageDigests.
*/
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 10, time = 1)
@Fork(value = 3)
public class GetMessageDigest {
@Param({"md5", "SHA-1", "SHA-256"})
private String digesterName;
private MessageDigest messageDigest;
@Setup
public void setupMessageDigestForCloning() throws NoSuchAlgorithmException {
messageDigest = MessageDigest.getInstance(digesterName);
}
@Benchmark
public MessageDigest getInstance() throws NoSuchAlgorithmException {
return MessageDigest.getInstance(digesterName);
}
@Benchmark
public MessageDigest cloneInstance() throws NoSuchAlgorithmException, CloneNotSupportedException {
return (MessageDigest)messageDigest.clone();
}
@Benchmark
public MessageDigest getInstanceWithProvider() throws NoSuchAlgorithmException, NoSuchProviderException {
return MessageDigest.getInstance(digesterName, "SUN");
}
}