最近看到一篇文章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