pat*_*eno 10 java windows random performance
假设你做的很简单:
public class Main {
public static void main(String[] args) {
long started = System.currentTimeMillis();
try {
new URL(args[0]).openConnection();
} catch (Exception ignore) {
}
System.out.println(System.currentTimeMillis() - started);
}
}
Run Code Online (Sandbox Code Playgroud)
现在使用http:// localhost as 运行它args[0]
它需要~100 msec完成.
需要5000+ msec.
现在在linux或docker中运行相同的东西:
~100 msec~350 msec为什么是这样?为什么平台之间有这么大的差异?你能为这个做什么?
对于长时间运行的应用程序服务器和具有自己长而重的初始化序列的应用程序,这5秒可能无关紧要.
然而,有很多应用程序,这个最初的5秒"挂起"很重要,可能会令人沮丧......
pat*_*eno 16
(注意:请参阅本答复末尾的最新更新)
说明
原因是默认SecureRandom提供商.
在Windows上,有2个SecureRandom提供程序可用:
- provider=SUN, type=SecureRandom, algorithm=SHA1PRNG
- provider=SunMSCAPI, type=SecureRandom, algorithm=Windows-PRNG
Run Code Online (Sandbox Code Playgroud)
在Linux上(使用Oracle JDK 8u162在Alpine docker中测试):
- provider=SUN, type=SecureRandom, algorithm=NativePRNG
- provider=SUN, type=SecureRandom, algorithm=SHA1PRNG
- provider=SUN, type=SecureRandom, algorithm=NativePRNGBlocking
- provider=SUN, type=SecureRandom, algorithm=NativePRNGNonBlocking
Run Code Online (Sandbox Code Playgroud)
这些是在jre/lib/security/java.security文件中指定的.
security.provider.1=sun.security.provider.Sun
...
security.provider.10=sun.security.mscapi.SunMSCAPI
Run Code Online (Sandbox Code Playgroud)
默认情况下,使用第一个SecureRandom提供程序.在Windows上,默认值是sun.security.provider.Sun,并且此实现在JVM运行时报告以下内容-Djava.security.debug="provider,engine=SecureRandom":
Provider: SecureRandom.SHA1PRNG algorithm from: SUN
provider: Failed to use operating system seed generator: java.io.IOException: Required native CryptoAPI features not available on this machine
provider: Using default threaded seed generator
Run Code Online (Sandbox Code Playgroud)
默认的线程种子生成器非常慢.
您需要使用SunMSCAPI提供商.
解决方案1:配置
在配置中重新排序提供商:
编辑jre/lib/security/java.security:
security.provider.1=sun.security.mscapi.SunMSCAPI
...
security.provider.10=sun.security.provider.Sun
Run Code Online (Sandbox Code Playgroud)
我不知道这可以通过系统属性来完成.
或许是的,使用-Djava.security.properties(未经测试,请参阅此内容)
解决方案2:程序化
以编程方式重新排序提供商
Optional.ofNullable(Security.getProvider("SunMSCAPI")).ifPresent(p->{
Security.removeProvider(p.getName());
Security.insertProviderAt(p, 1);
});
Run Code Online (Sandbox Code Playgroud)
JVM现在报告following(-Djava.security.debug="provider,engine=SecureRandom"):
Provider: SecureRandom.Windows-PRNG algorithm from: SunMSCAPI
Run Code Online (Sandbox Code Playgroud)
解决方案3:程序化v2
受此想法的启发,下面的一段代码只插入一个SecureRandom服务,从现有SunMSCAPI提供程序动态配置,而不依赖于sun.*类.这也避免了与提供SunMSCAPI者的所有服务的不加选择的优先次序相关的潜在风险.
public interface WindowsPRNG {
static void init() {
String provider = "SunMSCAPI"; // original provider
String type = "SecureRandom"; // service type
String alg = "Windows-PRNG"; // algorithm
String name = String.format("%s.%s", provider, type); // our provider name
if (Security.getProvider(name) != null) return; // already registered
Optional.ofNullable(Security.getProvider(provider)) // only on Windows
.ifPresent(p-> Optional.ofNullable(p.getService(type, alg)) // should exist but who knows?
.ifPresent(svc-> Security.insertProviderAt( // insert our provider with single SecureRandom service
new Provider(name, p.getVersion(), null) {{
setProperty(String.format("%s.%s", type, alg), svc.getClassName());
}}, 1)));
}
}
Run Code Online (Sandbox Code Playgroud)
性能
<140 msec(而不是5000+ msec)
细节
还有就是通话new SecureRandom()某处当您使用调用堆栈URL.openConnection("https://...")
它调用getPrngAlgorithm()(参见SecureRandom:880)
这将返回SecureRandom它找到的第一个提供者.
出于测试目的,URL.openConnection()可以用以下代码替换:
new SecureRandom().generateSeed(20);
Run Code Online (Sandbox Code Playgroud)
放弃
我不知道提供商重新排序造成的任何负面影响.但是,可能有一些,特别是考虑默认提供商选择算法.
无论如何,至少在理论上,从功能的角度来看,这应该对应用是透明的.
更新2019-01-08
Windows 10(版本1803):无法再在任何最新的JDK上重现此问题(从旧的oracle 1.7.0_72到openjdk"12-ea"2019-03-19都进行了测试).
看起来它是Windows问题,修复了最新的操作系统更新.相关更新可能也可能没有在最近的JRE版本中发生.但是,我无法重现原始问题,即使我最老的JDK 7更新72安装受到了影响,并且definitelly没有以任何方式修补.
使用此解决方案时仍然有轻微的性能提升(平均cca 350毫秒),但默认行为不再受到难以忍受的5秒以上的惩罚.