本實(shí)驗(yàn)平臺(tái)主要是基于本人的MacbookPro碗淌,之后會(huì)考慮測(cè)試其他操作系統(tǒng)版本
基于jdk1.8馆截,HotSpot
macOS Catalina 10.15
內(nèi)存 8 GB 2133 MHz LPDDR3
$ uname -a
Darwin MacBook-Pro.local 19.0.0 Darwin Kernel Version 19.0.0: Wed Sep 25 20:18:50 PDT 2019; root:xnu-6153.11.26~2/RELEASE_X86_64 x86_64
$ java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
為什么變量名(方法名,類名)最大長(zhǎng)度是65535
如果你起了一個(gè)65536長(zhǎng)度的變量名硼砰,在idea中不會(huì)提示報(bào)錯(cuò)且蓬,但是運(yùn)行就會(huì)獲得一個(gè)錯(cuò)誤
Error:(3, 8) java: 對(duì)于常量池來說, 字符串 "aaaaaaaaaaaaaaaaaaaa..." 的 UTF8 表示過長(zhǎng)
因?yàn)闊o論是字段名還是方法名或者是類名,最終在常量池中都會(huì)用一個(gè)Utf8的常量去表示夺刑,但是class文件中只給了u2個(gè)字節(jié)去表示隨后的Utf8字節(jié)的長(zhǎng)度缅疟。
而u2字節(jié)能表示最大的十進(jìn)制數(shù)就是65535
FFFF = 65535
為什么方法參數(shù)最多是255
其實(shí)這個(gè)說法有問題,其實(shí)Java中最多能有255個(gè)單位(4個(gè)字節(jié)為1個(gè)單位)的參數(shù)數(shù)量遍愿,
不足4個(gè)字節(jié)也會(huì)當(dāng)成4個(gè)字節(jié)存淫,所以long和double類型會(huì)占2個(gè)單位
// 256個(gè)String
Error:(5, 25) java: 參數(shù)過多
// 255個(gè)long
Error:(5, 25) java: 參數(shù)過多
// 256個(gè)byte
Error:(5, 25) java: 參數(shù)過多
// 127個(gè)long
ok
// 255個(gè)byte,String
ok
而且靜態(tài)和非靜態(tài)的方法是有區(qū)別的,靜態(tài)方法能比非靜態(tài)方法多一個(gè)參數(shù)(可能)沼填,是因?yàn)榉庆o態(tài)方法會(huì)有第一個(gè)參數(shù)默認(rèn)指向當(dāng)前實(shí)例對(duì)象桅咆,就是this
方法的參數(shù)其實(shí)會(huì)作為方法內(nèi)部局部變量來使用
而Jvm中Code屬性有一個(gè)max_locals屬性,u2的長(zhǎng)度來記錄整個(gè)方法的局部變量Slot數(shù)量坞笙,
那么u2明明可以記錄65535個(gè)Slot岩饼,為什么方法參數(shù)只能是255個(gè)呢?
讓我們走進(jìn)jdk
// langtools/src/share/classes/com/sun/tools/javac/jvm/Gen.java 這個(gè)是jdk javac命令相關(guān)源碼
public class Gen extends JCTree.Visitor {
...
// genMethod方法里會(huì)對(duì)方法參數(shù)數(shù)量進(jìn)行校驗(yàn)
void genMethod(JCMethodDecl tree, Env<GenContext> env, boolean fatcode) {
if (Code.width(types.erasure(env.enclMethod.sym.type).getParameterTypes()) + extras >
ClassFile.MAX_PARAMETERS) {
log.error(tree.pos(), "limit.parameters");
nerrs++;
}
}
...
}
// langtools/src/share/classes/com/sun/tools/javac/jvm/ClassFile.java
public class ClassFile {
...
// 十六進(jìn)制FF 就是 255
public final static int MAX_PARAMETERS = 0xff;
...
}
既然javac不允許我們?cè)谠创a中聲明一個(gè)超過255參數(shù)的方法薛夜,如果直接通過字節(jié)碼的技術(shù)籍茧,直接生成一個(gè)class文件,能突破這個(gè)255的限制嗎
有請(qǐng)javassist
public class JavaassistDemo {
public static void main(String[] args) {
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);
CtClass cClass = cPool.makeClass("TestManyWhy");
cClass.addMethod(CtNewMethod.make(sb.toString(), cClass));
cClass.addMethod(CtNewMethod.make("public static void main(String[] args) {\n" +
" System.out.println(\"testmain\");\n" +
" }", cClass));
cClass.writeFile("/Users/junjiexun/IdeaProjects/jvmtest/target/classes/jvmtest");
} catch (Exception e) {
e.printStackTrace();
}
}
}
先通過javap查看下class文件梯澜,可以看到testMax是生成了
$ javap TestManyWhy
public class TestManyWhy {
public static void testMax(int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int);
public static void main(java.lang.String[]);
public TestManyWhy();
}
運(yùn)行試試
$ java TestManyWhy
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.ClassFormatError: Too many arguments in method signature in class file TestManyWhy
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
還是獲得了一個(gè)錯(cuò)誤寞冯,證明jvm在運(yùn)行的時(shí)候也會(huì)對(duì)參數(shù)長(zhǎng)度進(jìn)行校驗(yàn)
通過錯(cuò)誤提示,再去扒openjdk的源碼
// hotspot/src/share/vm/classfile/classFileParser.cpp
#define MAX_ARGS_SIZE 255
methodHandle ClassFileParser::parse_method(bool is_interface,
AccessFlags *promoted_flags,
TRAPS) {
...
int args_size = -1; // only used when _need_verify is true
if (_need_verify) {
// 這里也體現(xiàn)了靜態(tài)和非靜態(tài)晚伙,參數(shù)上限是不同的
args_size = ((flags & JVM_ACC_STATIC) ? 0 : 1) +
verify_legal_method_signature(name, signature, CHECK_(nullHandle));
if (args_size > MAX_ARGS_SIZE) {
classfile_parse_error("Too many arguments in method signature in class file %s", CHECK_(nullHandle));
}
}
...
}