最近看到一篇文章Docker面对Java将再也不尴尬:Java 10为Docker作了特殊优化,里面提到了java10对于docker作了一些特殊的优化。众所周知java的docker容器化支持一直以来都比较的尴尬,因为docker底层使用了cgroups来进行进程级别的隔离,虽然咱们经过docker设置了容器的资源限制,但jvm虚拟机其实感知不到这里些限制。好比咱们的宿主机多是8核16G,限定docker容器为2核4G,在容器中读出来的资源可能仍是8核16G,咱们平时可能会来读取机器资源来作性能优化,好比核心线程数、最大线程数的设定。这对于一些程序来说,在docker上跑可能会会带来性能损耗,所幸的是java10已经增长了这些支持,而且有jdk8兼容的计划。html
想起最近工做中,在优化程序过程当中发现availableProcessors
彷佛有较大性能损耗,所以对它进行了详细的了解并作了一些测试。java
/**
* Returns the number of processors available to the Java virtual machine.
*
* <p> This value may change during a particular invocation of the virtual
* machine. Applications that are sensitive to the number of available
* processors should therefore occasionally poll this property and adjust
* their resource usage appropriately. </p>
*
* @return the maximum number of processors available to the virtual
* machine; never smaller than one
* @since 1.4
*/
public native int availableProcessors();
复制代码
jdk文档中这么写到,返回jvm虚拟机可用核心数。而且后面还有一段注释:这个值有可能在虚拟机的特定调用期间更改。咱们平时对于此函数的直观印象为:返回机器的CPU数,这个应该是一个常量值。由此看来,可能有很大的一些误解。由此我产生了两个疑问:linux
这个比较好理解,顾名思义为JVM能够用来工做利用的CPU核心数。在一个多核CPU服务器上,可能安装了多个应用,JVM只是其中的一个部分,有些cpu被其余应用使用了。docker
返回值可变这个也比较好理解,既然多核CPU服务器上多个应用公用cpu,对于不一样时刻来说能够被JVM利用的数量固然是不一样的,既然如此,那java中是如何作的呢? 经过阅读jdk8的源码,linux系统与windows系统的实现差异还比较大。windows
linux 实现
int os::active_processor_count() {
// Linux doesn't yet have a (official) notion of processor sets, // so just return the number of online processors. int online_cpus = ::sysconf(_SC_NPROCESSORS_ONLN); assert(online_cpus > 0 && online_cpus <= processor_count(), "sanity check"); return online_cpus; } 复制代码
linux 实现比较懒,直接经过sysconf读取系统参数,_SC_NPROCESSORS_ONLN。缓存
windows 实现
int os::active_processor_count() {
DWORD_PTR lpProcessAffinityMask = 0;
DWORD_PTR lpSystemAffinityMask = 0;
int proc_count = processor_count();
if (proc_count <= sizeof(UINT_PTR) * BitsPerByte &&
GetProcessAffinityMask(GetCurrentProcess(), &lpProcessAffinityMask, &lpSystemAffinityMask)) {
// Nof active processors is number of bits in process affinity mask
int bitcount = 0;
while (lpProcessAffinityMask != 0) {
lpProcessAffinityMask = lpProcessAffinityMask & (lpProcessAffinityMask-1);
bitcount++;
}
return bitcount;
} else {
return proc_count;
}
}
复制代码
windows系统实现就比较复杂,能够看到不只须要判断CPU是否可用,还须要依据CPU亲和性去判断是否该线程可用该CPU。里面经过一个while循环去解析CPU亲和性掩码,所以这是一个CPU密集型的操做。性能优化
经过如上分析,咱们基本能够知道这个操做是一个cpu敏感型操做,那么它的性能在各个操做系统下表现如何呢?以下我测试了该函数在正常工做何cpu满负荷工做状况下的一些表现。测试数据为执行100万次调用,统计10次执行状况,取平均值。相关代码以下:bash
public class RuntimeDemo {
private static final int EXEC_TIMES = 100_0000;
private static final int TEST_TIME = 10;
public static void main(String[] args) throws Exception{
int[] arr = new int[TEST_TIME];
for(int i = 0; i < TEST_TIME; i++){
long start = System.currentTimeMillis();
for(int j = 0; j < EXEC_TIMES; j++){
Runtime.getRuntime().availableProcessors();
}
long end = System.currentTimeMillis();
arr[i] = (int)(end-start);
}
double avg = Arrays.stream(arr).average().orElse(0);
System.out.println("avg spend time:" + avg + "ms");
}
}
复制代码
CPU 满负荷代码以下:服务器
public class CpuIntesive {
private static final int THREAD_COUNT = 16;
public static void main(String[] args) {
for(int i = 0; i < THREAD_COUNT; i++){
new Thread(()->{
long count = 1000_0000_0000L;
long index=0;
long sum = 0;
while(index < count){
sum = sum + index;
index++;
}
}).start();
}
}
}
复制代码
系统 | 配置 | 测试方法 | 测试结果 |
---|---|---|---|
Windows | 2核8G | 正常 | 1425.2ms |
Windows | 2核8G | CPU 满负荷 | 6113.1ms |
MacOS | 4核8G | 正常 | 69.4ms |
MacOS | 4核8G | CPU满负荷 | 322.8ms |
虽然两个机器的配置相差较大,测试数据比较意义不大,但从测试状况仍是能够得出以下结论:app