JVM棧
?根據(jù)JVM規(guī)范,JVM包括兩種棧,java虛擬機(jī)棧和本地方法棧。也就是說(shuō),每當(dāng)啟動(dòng)一個(gè)java線程時(shí),JVM就會(huì)為其分配一個(gè)java虛擬機(jī)棧和一個(gè)本地方法棧。棧對(duì)線程而言是獨(dú)立私有內(nèi)存區(qū)域,線程間無(wú)法對(duì)棧進(jìn)行互訪,這由虛擬機(jī)保證。如下圖:
?
?java虛擬機(jī)棧在老的規(guī)范中描述為java棧,顧名思義,是為java方法即java 字節(jié)碼方法調(diào)用服務(wù)的棧。如同C棧,每當(dāng)java線程調(diào)用java方法時(shí),JVM會(huì)向java虛擬機(jī)棧壓入新的棧幀,該棧幀成為當(dāng)前幀,用來(lái)存儲(chǔ)局部變量,中間運(yùn)算值等數(shù)據(jù)。
?本地方法棧是為本地方法調(diào)用服務(wù)的棧,如通過(guò)JNI、JNA接口實(shí)現(xiàn)的本地方法調(diào)用,方法棧具體是什么類型由本地方法接口的實(shí)現(xiàn)決定,如其采用C鏈接模型,那么本地方法棧就是C棧。JVM規(guī)范并沒(méi)有對(duì)本地方法棧使用的語(yǔ)言、數(shù)據(jù)結(jié)構(gòu)進(jìn)行強(qiáng)制規(guī)定,由具體虛擬機(jī)實(shí)現(xiàn)決定。當(dāng)線程調(diào)用本地方法時(shí),java虛擬機(jī)棧并不會(huì)改變,JVM只是動(dòng)態(tài)鏈接和調(diào)用本地方法,由本地方法棧完成本地方法的壓棧出棧。
JVM棧對(duì)線程并發(fā)量的影響
?由上可知,JVM會(huì)為每個(gè)線程分配虛擬機(jī)棧和本地方法棧,但受資源的約束線程數(shù)是無(wú)法無(wú)限增長(zhǎng)的,這除了操作系統(tǒng)設(shè)置的影響外,JVM棧大小對(duì)JVM能創(chuàng)建的線程數(shù)也有間接影響。
? 操作系統(tǒng)為每個(gè)進(jìn)程分配的內(nèi)存是有限的,在粗略計(jì)算下,拋開其它小的消耗,分配給JVM進(jìn)程的內(nèi)存減去JVM堆、方法區(qū)、基本就是棧區(qū)所能使用的最大內(nèi)存了。如win32下,一個(gè)JVM最大內(nèi)存是2G,如果分配給堆和方法區(qū)1G,剩下1G是棧區(qū)所能使用的內(nèi)存,每創(chuàng)建一個(gè)線程就需要分配一定的棧內(nèi)存給線程,線程數(shù)必然可盡。
?創(chuàng)建新線程無(wú)法申請(qǐng)到足夠棧內(nèi)存,容易出現(xiàn)內(nèi)存溢出錯(cuò)誤,或者JVM崩潰。這常出現(xiàn)在小內(nèi)存,大并發(fā)線程量的應(yīng)用中。如下的虛擬機(jī)崩潰問(wèn)題也許似曾相識(shí):
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 133419008 bytes for committing reserved memory.
# Possible reasons:
# The system is out of physical RAM or swap space
# In 32 bit mode, the process size limit was hit
# Possible solutions:
# Reduce memory load on the system
# Increase physical memory or swap space
# Check if swap backing store is full
# Use 64 bit Java on a 64 bit OS
# Decrease Java heap size (-Xmx/-Xms)
# Decrease number of Java threads
# Decrease Java thread stack sizes (-Xss)
# Set larger code cache with -XX:ReservedCodeCacheSize=
# This output file may be truncated or incomplete.
#
這樣的應(yīng)用很常見(jiàn),尤其是把服務(wù)架設(shè)在云服務(wù)器上用幾十塊錢一個(gè)月的機(jī)器當(dāng)服務(wù)器的初創(chuàng)團(tuán)隊(duì)。
?除了可以在服務(wù)器資源投入上解決問(wèn)題外,依然可以在現(xiàn)有的有限條件下解決問(wèn)題:一是降低其他內(nèi)存區(qū)的內(nèi)存占用量,如堆區(qū),二是采用workaround,任務(wù)調(diào)度方案,減少并發(fā)執(zhí)行量,另一個(gè)很少受到關(guān)注的方法是調(diào)整java線程棧的大小。
?-Xss可用來(lái)設(shè)置JVM單個(gè)線程的棧大小,該參數(shù)不帶單位時(shí)指字節(jié),后面可以帶k或K,m或M,g或G等單位,分別表示KB、MB、GB。JDK5以后每個(gè)java線程的棧內(nèi)存默認(rèn)是1024KB,即1M,1G的JVM棧最多支持1024個(gè)線程。如果應(yīng)用中并無(wú)很深的調(diào)用,根據(jù)應(yīng)用的特點(diǎn)降低JVM棧大小也是能達(dá)到較大的線程并發(fā)量,如-Xss256m。這樣1G的棧內(nèi)存可支持4*1024個(gè)線程。另外,需要補(bǔ)充說(shuō)明的是,Hotspot做為主流JVM,其實(shí)現(xiàn)是將java虛擬機(jī)棧和本地方法棧合二為一,因此,如Xoss針對(duì)本地方法區(qū)大小設(shè)置是無(wú)效的。
參考
? 深入理解Java虛擬機(jī) 周志明
? 深入Java虛擬機(jī) Bill Venners
? Java虛擬機(jī)規(guī)范8 https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6