一:為什麼研究這麼無聊的問題
這兩天在讀一本老書《Orange'S 一個作業系統的實作》,把丢了很長時間沒研究的作業系統又重新拾起來了,在第三章講解“保護模式”時,作者提到了調用門描述符中的Param Count隻有5位,也就是說,最多隻支援32個參數,這本來隻是一個不是特别重要的細節,但是卻勾起了我的思索:在JVM中,一個Java方法,最多能定義多少參數呢?我知道這是一個很無聊的問題,即使能定義一萬個,十萬個,誰又會真的去這麼做呢。但是作為一個Coder,最重要的不就是好奇心嗎,沒有好奇心,和一條鹹魚又有什麼差別呢?
二:實地考察
這種問題,第一步當然就是看看JVM中關于方法的定義,這裡以openJDK10中的HotSpot為例。
在ConstMethod中,代表參數數量的字段為_size_of_parameters。
u2 _size_of_parameters; // size of the parameter block (receiver + arguments) in words
_size_of_parameters的類型為u2,在JVM中,u2為2個位元組長,那麼理論上來說,HotSpot支援的方法最大參數數量為2^16 - 1,即65535。
這個答案究竟是否正确呢?實踐出真知!
當然我不會傻到真的去一個個定義65535個參數,那我豈不成了“數一億粒米”的幼稚園老師了?Coder就得按照Coder的辦法:
public static void main(String[] args) {
for (int i = 0; i < 65535; i++) {
System.out.print("int a" + i + ",");
}
}
完美解放了生産力 。
生成完參數清單,定義好方法,當我滿懷信心的開始編譯時,編譯器給了我狠狠一刀:

居然不是65535?那應該是多少呢?難道是一個位元組長?廢話不多說,我立即來實驗了下255個參數,編譯通過,再試了一下256,和65535時一樣報錯。那麼結果很明顯了,Java方法最多可以定義255個參數。
我檢視了下Javac源碼,在生成方法的位元組碼時,有方法參數數量限制判斷:
if (Code.width(types.erasure(env.enclMethod.sym.type).getParameterTypes()) + extras > ClassFile.MAX_PARAMETERS) {
log.error(tree.pos(), "limit.parameters");
nerrs++;
}
其中 ClassFile.MAX_PARAMETERS = 255。
事情到這裡我很不甘心,HotSpot中明明是用兩個位元組長來定義的方法參數數量,莫非隻是Javac在編譯過程中做了限制?隻要能成功編譯出一個有256個參數的java方法,在虛拟機中一試便知,但是怎麼才能繞過Javac呢?我覺得主要有以下兩種辦法:
- 修改Javac源碼,幹掉以上參數限制這一段代碼,再重新編譯;
- 利用位元組碼修改工具,硬改位元組碼,加上一個擁有256個參數的方法。
第一種方法看似簡單,但是其實從openJDK中提取出來的Javac項目不能直接run,需要很多配置,而且源碼依賴了很多jdk中的不可見類,操作起來很麻煩。是以這裡我采用了第二種方法,工具選用的是老朋友javassist。
其實javassist使用起來很簡單,這裡我隻需要對一個已有的class檔案加上一個新方法即可:
try {
StringBuilder sb = new StringBuilder();
sb.append("public static void testMax(");
for (int i = 0; i < 256; i++) {
sb.append("int a" + i);
if(i < 255) {
sb.append(",");
}
}
sb.append("){}");
ClassPool cPool = new ClassPool(true);
cPool.insertClassPath("/Users/wanginbeijing/Documents/MyProgramings/java/Mine/test/src");
CtClass cClass = cPool.get("com.wangxiandeng.test.Test");
CtMethod newMethod = CtNewMethod.make(sb.toString(), cClass);
cClass.addMethod(newMethod);
cClass.writeFile("/Users/wanginbeijing/Documents/MyProgramings/java/Mine/test/src");
} catch (NotFoundException e) {
e.printStackTrace();
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (
IOException e) {
e.printStackTrace();
}
以上就通過javassist成功的給Test.class 檔案加上了一個擁有256個參數的方法testMax()。現在讓我們運作下Test.class試試:
java com.wangxiandeng.test.Test
沒想到這次雖然瞞過了編譯器,卻沒有過的了虛拟機這一關,運作直接報錯了:
錯誤: 加載主類 com.wangxiandeng.test.Test 時出現 LinkageError
java.lang.ClassFormatError: Too many arguments in method signature in class file com/wangxiandeng/test/Test
看樣子Java不僅僅在編譯期會對方法參數數量做限制,在虛拟機運作期間同樣會幹這件事。
本着一查到底的精神,我在HotSpot源碼中搜尋了下上面報的錯誤,找到了虛拟機檢查參數數量的地方:
Method* ClassFileParser::parse_method(const ClassFileStream* const cfs,
bool is_interface,
const ConstantPool* cp,
AccessFlags* const promoted_flags,
TRAPS) {
......
if (_need_verify) {
args_size = ((flags & JVM_ACC_STATIC) ? 0 : 1) +verify_legal_method_signature(name, signature, CHECK_NULL);
if (args_size > MAX_ARGS_SIZE) {
classfile_parse_error("Too many arguments in method signature in class file %s", CHECK_NULL);
}
}
......
}
可見虛拟機在解析class檔案中的方法時,會判斷參數數量args_size是否大于MAX_ARGS_SIZE,如果大于則就會報錯了。MAX_ARGS_SIZE為255。
這裡有一點需要注意,在計算args_size時,有判斷方法是否為static方法,如果不是static方法,則會在方法原有參數數量上再加一,這是因為非static方法會添加一個預設參數到參數清單首位:方法的真正執行者,即方法所屬類的執行個體對象。
事情到這裡總算大概明白了,Java static方法的參數最多隻能有255個,非static方法最多隻能有254個。雖然遠不及我剛開始推測的65535個,但是這也完全夠用了,畢竟你敢在你的項目裡定義一個255個參數的方法而保證不被人打死嗎。
有人可能要問,如果我定義的方法參數是變長參數呢?還有這種限制嗎?這當然是沒有的,因為變成參數的本質其實就是傳遞一個數組,你傳再多的參數,編譯後其實都隻是一個數組而已。
其實Java不是限制方法的參數個數,而是限制參數的機關數量,我又回頭看了下源碼,Javac在編譯期計算方法的參數個數時,調用的是這個方法:
Code.width(types.erasure(env.enclMethod.sym.type).getParameterTypes())
Code.width()會根據參數類型,計算出以位元組為機關的參數數量:
public static int width(int typecode) {
switch (typecode) {
case LONGcode: case DOUBLEcode: return 2;
case VOIDcode: return 0;
default: return 1;
}
}
可見對于long、doubule,會占用兩個機關數,是以說java方法最多隻能有255個參數是不準确的,隻能說java方法容許的參數總機關數為255。其實想想也對,java方法調用前會将參數進行壓棧,虛拟機本身就應該基于方法傳參占用的棧深度去進行限制。
另有知友提醒,《Java虛拟機規範》中其實已經做了說明,我翻了一下,在《Java虛拟機限制》這一小節中有提到:
方法的參數最多有255個,它是由方法描述符(§4.3.3)的定義所限制,如果方法調 用是針對執行個體或接口方法,那麼這個限制也包着占有一個單元的 this。注意對于定義 在方法描述符中的參數長度來說,每個 long 和 double 參數都會占用兩個長度機關,所 以如果有這些類型的話,最終的限制的最大值将會變小。
書中自有黃金屋啊!不過紙上得來終覺淺,絕知此事要躬行,你說是不是呢?
三:一切都結束了
嗯,做完實驗,寫完文章,我總算把這件事搞明白了,女朋友早已在呼呼大睡,好像我确實很無聊,好像我确實還是一條鹹魚。
聽說喜歡點關注的同學都長得帥