插入USB热点后简单Java程序慢100倍
Simple Java program 100 times slower after plugging in USB hotspot
我有以下 Java 程序:
class Main {
public static void main(String[] args) throws java.io.IOException {
long start = System.nanoTime();
java.io.File.createTempFile("java_test", ".txt").delete();
System.out.println((System.nanoTime() - start ) / 1e9);
}
}
正常情况下,执行需要大约 63 毫秒:
$ java Main
0.06308555
但是,一旦我将 Android phone 连接为 USB 热点,它就会花费更长的时间。根据机器的不同,从 3 到 40 秒不等:
$ java Main
4.263285528
奇怪的是这里实际上没有通过网络传输任何东西 - 插入的网络适配器无关紧要。
我做了一个回溯,看起来大部分时间都花在了 NetworkInterface.getAll
方法上:
"main" #1 prio=5 os_prio=0 tid=0x00000000023ae000 nid=0x142c runnable [0x000000000268d000]
java.lang.Thread.State: RUNNABLE
at java.net.NetworkInterface.getAll(Native Method)
at java.net.NetworkInterface.getNetworkInterfaces(Unknown Source)
at sun.security.provider.SeedGenerator.addNetworkAdapterInfo(Unknown Source)
at sun.security.provider.SeedGenerator.access[=14=]0(Unknown Source)
at sun.security.provider.SeedGenerator.run(Unknown Source)
at sun.security.provider.SeedGenerator.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at sun.security.provider.SeedGenerator.getSystemEntropy(Unknown Source)
at sun.security.provider.SecureRandom$SeederHolder.<clinit>(Unknown Source)
at sun.security.provider.SecureRandom.engineNextBytes(Unknown Source)
- locked <0x000000076afa2820> (a sun.security.provider.SecureRandom)
at java.security.SecureRandom.nextBytes(Unknown Source)
- locked <0x000000076af6bdc8> (a java.security.SecureRandom)
at java.security.SecureRandom.next(Unknown Source)
at java.util.Random.nextLong(Unknown Source)
at java.io.File$TempDirectory.generateFile(Unknown Source)
at java.io.File.createTempFile(Unknown Source)
at java.io.File.createTempFile(Unknown Source)
at Main.main(Main.java:4)
反过来,似乎大部分时间都花在 GetIfTable
Windows API 方法中:
Child-SP RetAddr Call Site
00000000`0257ed78 000007fe`fd7210ba ntdll!NtDeviceIoControlFile+0xa
00000000`0257ed80 000007fe`fd721252 nsi+0x10ba
00000000`0257ee20 000007fe`fd7211f9 nsi!NsiEnumerateObjectsAllParametersEx+0x2e
00000000`0257ee60 000007fe`fd7217b0 nsi!NsiEnumerateObjectsAllParameters+0xc9
00000000`0257ef00 000007fe`f9c7928d nsi!NsiAllocateAndGetTable+0x184
00000000`0257efd0 00000000`6f8c5a01 IPHLPAPI!GetIfTable+0xa9
00000000`0257f090 00000000`6f8c6980 net!Java_java_net_NetworkInterface_getMTU0+0x1a1
00000000`0257f150 00000000`6f8c6e57 net!Java_java_net_NetworkInterface_isP2P0_XP+0x88
00000000`0257f270 00000000`6f8c6058 net!Java_java_net_NetworkInterface_getAll_XP+0x23
00000000`0257f2a0 00000000`02867f54 net!Java_java_net_NetworkInterface_getAll+0x2c
GetIfTable
似乎是有问题的功能。我在示例程序中观察到相同的减速:https://msdn.microsoft.com/en-us/library/windows/desktop/aa365943(v=vs.85).aspx 和以下代码段:
#include <iphlpapi.h>
#include <stdlib.h>
int main() {
DWORD dwSize = sizeof(MIB_IFTABLE);
MIB_IFTABLE *pIfTable = malloc(dwSize);
GetIfTable(pIfTable, &dwSize, FALSE);
pIfTable = malloc(dwSize);
GetIfTable(pIfTable, &dwSize, FALSE);
return 0;
}
如何修复或解决此问题?
我可以自己创建临时文件并避免调用 NetworkInterface.getNetworkInterfaces 但 SecureRandom 在整个 Java 标准库中使用。
有没有办法强制 SecureRandom 不使用 GetIfTable?
Java版本:
> java -version
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)
Windows版本:
OS Name: Microsoft Windows 7 Professional
OS Version: 6.1.7601 Service Pack 1 Build 7601
有问题的网络适配器:
Name [00000020] Remote NDIS based Internet Sharing Device
Adapter Type Ethernet 802.3
Product Type Remote NDIS based Internet Sharing Device
Installed Yes
PNP Device ID USB\VID_0FCE&PID_71C4&MI_00&6BE3F3B&0&0000
Last Reset 8/14/2016 12:26 PM
Index 20
Service Name usb_rndisx
IP Address 192.168.42.183, fe80::90ab:3786:4396:2870
IP Subnet 255.255.255.0, 64
Default IP Gateway 192.168.42.129
DHCP Enabled Yes
DHCP Server 192.168.42.129
DHCP Lease Expires 8/14/2016 3:27 PM
DHCP Lease Obtained 8/14/2016 2:27 PM
MAC Address 02:18:61:77:7D:72
Driver c:\windows\system32\drivers\usb8023x.sys (6.1.7600.16385, 19.50 KB (19,968 bytes), 7/14/2009 2:09 AM)
SecureRandom
scans network interfaces as an additional source of system entropy. In order to avoid this, you need to register a custom java.security.Provider
that contains a different implementation of SecureRandomSpi
的默认实现。
幸运的是,Windows 的 JDK 已经有一个合适的 SecureRandomSpi
实现,它依赖于 Microsoft Crypto API:sun.security.mscapi.PRNG
。虽然这不是public API,但class存在于OpenJDK和Oracle JDK的所有版本中,从1.6到9,并且回退可用无论如何。
有两种方法可以将 MS Crypto PRNG 注册为默认的 SecureRandom 算法。
1. 从应用程序内部通过在最开始调用 WindowsSecureRandom.register()
。
import java.security.Provider;
import java.security.Security;
public class WindowsSecureRandom extends Provider {
private static final String MSCAPI = "sun.security.mscapi.PRNG";
private WindowsSecureRandom() {
super("WindowsSecureRandom Provider", 1.0, null);
putService(new Service(this, "SecureRandom", "Windows-PRNG", MSCAPI, null, null));
}
public static void register() {
if (System.getProperty("os.name").contains("Windows")) {
try {
Class.forName(MSCAPI);
Security.insertProviderAt(new WindowsSecureRandom(), 1);
} catch (ClassNotFoundException e) {
// Fallback to default implementation
}
}
}
}
2. 通过重新排序 %JAVA_HOME%\jre\lib\security\java.security
文件中的提供商列表。
security.provider.1=sun.security.mscapi.SunMSCAPI <<<--- make it the first provider
security.provider.2=sun.security.provider.Sun
security.provider.3=sun.security.rsa.SunRsaSign
security.provider.4=sun.security.ec.SunEC
security.provider.5=com.sun.net.ssl.internal.ssl.Provider
...
我已经验证了 SeedGenerator
和 NetworkInterface
class 中的任一解决方案都不再加载。
我有以下 Java 程序:
class Main {
public static void main(String[] args) throws java.io.IOException {
long start = System.nanoTime();
java.io.File.createTempFile("java_test", ".txt").delete();
System.out.println((System.nanoTime() - start ) / 1e9);
}
}
正常情况下,执行需要大约 63 毫秒:
$ java Main
0.06308555
但是,一旦我将 Android phone 连接为 USB 热点,它就会花费更长的时间。根据机器的不同,从 3 到 40 秒不等:
$ java Main
4.263285528
奇怪的是这里实际上没有通过网络传输任何东西 - 插入的网络适配器无关紧要。
我做了一个回溯,看起来大部分时间都花在了 NetworkInterface.getAll
方法上:
"main" #1 prio=5 os_prio=0 tid=0x00000000023ae000 nid=0x142c runnable [0x000000000268d000]
java.lang.Thread.State: RUNNABLE
at java.net.NetworkInterface.getAll(Native Method)
at java.net.NetworkInterface.getNetworkInterfaces(Unknown Source)
at sun.security.provider.SeedGenerator.addNetworkAdapterInfo(Unknown Source)
at sun.security.provider.SeedGenerator.access[=14=]0(Unknown Source)
at sun.security.provider.SeedGenerator.run(Unknown Source)
at sun.security.provider.SeedGenerator.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at sun.security.provider.SeedGenerator.getSystemEntropy(Unknown Source)
at sun.security.provider.SecureRandom$SeederHolder.<clinit>(Unknown Source)
at sun.security.provider.SecureRandom.engineNextBytes(Unknown Source)
- locked <0x000000076afa2820> (a sun.security.provider.SecureRandom)
at java.security.SecureRandom.nextBytes(Unknown Source)
- locked <0x000000076af6bdc8> (a java.security.SecureRandom)
at java.security.SecureRandom.next(Unknown Source)
at java.util.Random.nextLong(Unknown Source)
at java.io.File$TempDirectory.generateFile(Unknown Source)
at java.io.File.createTempFile(Unknown Source)
at java.io.File.createTempFile(Unknown Source)
at Main.main(Main.java:4)
反过来,似乎大部分时间都花在 GetIfTable
Windows API 方法中:
Child-SP RetAddr Call Site
00000000`0257ed78 000007fe`fd7210ba ntdll!NtDeviceIoControlFile+0xa
00000000`0257ed80 000007fe`fd721252 nsi+0x10ba
00000000`0257ee20 000007fe`fd7211f9 nsi!NsiEnumerateObjectsAllParametersEx+0x2e
00000000`0257ee60 000007fe`fd7217b0 nsi!NsiEnumerateObjectsAllParameters+0xc9
00000000`0257ef00 000007fe`f9c7928d nsi!NsiAllocateAndGetTable+0x184
00000000`0257efd0 00000000`6f8c5a01 IPHLPAPI!GetIfTable+0xa9
00000000`0257f090 00000000`6f8c6980 net!Java_java_net_NetworkInterface_getMTU0+0x1a1
00000000`0257f150 00000000`6f8c6e57 net!Java_java_net_NetworkInterface_isP2P0_XP+0x88
00000000`0257f270 00000000`6f8c6058 net!Java_java_net_NetworkInterface_getAll_XP+0x23
00000000`0257f2a0 00000000`02867f54 net!Java_java_net_NetworkInterface_getAll+0x2c
GetIfTable
似乎是有问题的功能。我在示例程序中观察到相同的减速:https://msdn.microsoft.com/en-us/library/windows/desktop/aa365943(v=vs.85).aspx 和以下代码段:
#include <iphlpapi.h>
#include <stdlib.h>
int main() {
DWORD dwSize = sizeof(MIB_IFTABLE);
MIB_IFTABLE *pIfTable = malloc(dwSize);
GetIfTable(pIfTable, &dwSize, FALSE);
pIfTable = malloc(dwSize);
GetIfTable(pIfTable, &dwSize, FALSE);
return 0;
}
如何修复或解决此问题? 我可以自己创建临时文件并避免调用 NetworkInterface.getNetworkInterfaces 但 SecureRandom 在整个 Java 标准库中使用。 有没有办法强制 SecureRandom 不使用 GetIfTable?
Java版本:
> java -version
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)
Windows版本:
OS Name: Microsoft Windows 7 Professional
OS Version: 6.1.7601 Service Pack 1 Build 7601
有问题的网络适配器:
Name [00000020] Remote NDIS based Internet Sharing Device
Adapter Type Ethernet 802.3
Product Type Remote NDIS based Internet Sharing Device
Installed Yes
PNP Device ID USB\VID_0FCE&PID_71C4&MI_00&6BE3F3B&0&0000
Last Reset 8/14/2016 12:26 PM
Index 20
Service Name usb_rndisx
IP Address 192.168.42.183, fe80::90ab:3786:4396:2870
IP Subnet 255.255.255.0, 64
Default IP Gateway 192.168.42.129
DHCP Enabled Yes
DHCP Server 192.168.42.129
DHCP Lease Expires 8/14/2016 3:27 PM
DHCP Lease Obtained 8/14/2016 2:27 PM
MAC Address 02:18:61:77:7D:72
Driver c:\windows\system32\drivers\usb8023x.sys (6.1.7600.16385, 19.50 KB (19,968 bytes), 7/14/2009 2:09 AM)
SecureRandom
scans network interfaces as an additional source of system entropy. In order to avoid this, you need to register a custom java.security.Provider
that contains a different implementation of SecureRandomSpi
的默认实现。
幸运的是,Windows 的 JDK 已经有一个合适的 SecureRandomSpi
实现,它依赖于 Microsoft Crypto API:sun.security.mscapi.PRNG
。虽然这不是public API,但class存在于OpenJDK和Oracle JDK的所有版本中,从1.6到9,并且回退可用无论如何。
有两种方法可以将 MS Crypto PRNG 注册为默认的 SecureRandom 算法。
1. 从应用程序内部通过在最开始调用 WindowsSecureRandom.register()
。
import java.security.Provider;
import java.security.Security;
public class WindowsSecureRandom extends Provider {
private static final String MSCAPI = "sun.security.mscapi.PRNG";
private WindowsSecureRandom() {
super("WindowsSecureRandom Provider", 1.0, null);
putService(new Service(this, "SecureRandom", "Windows-PRNG", MSCAPI, null, null));
}
public static void register() {
if (System.getProperty("os.name").contains("Windows")) {
try {
Class.forName(MSCAPI);
Security.insertProviderAt(new WindowsSecureRandom(), 1);
} catch (ClassNotFoundException e) {
// Fallback to default implementation
}
}
}
}
2. 通过重新排序 %JAVA_HOME%\jre\lib\security\java.security
文件中的提供商列表。
security.provider.1=sun.security.mscapi.SunMSCAPI <<<--- make it the first provider
security.provider.2=sun.security.provider.Sun
security.provider.3=sun.security.rsa.SunRsaSign
security.provider.4=sun.security.ec.SunEC
security.provider.5=com.sun.net.ssl.internal.ssl.Provider
...
我已经验证了 SeedGenerator
和 NetworkInterface
class 中的任一解决方案都不再加载。