关于Java的十万个为什么
2020-04-16 本文已影响0人
老荀
本实验平台主要是基于本人的MacbookPro,之后会考虑测试其他操作系统版本
基于jdk1.8,HotSpot
macOS Catalina 10.15
内存 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)
为什么变量名(方法名,类名)最大长度是65535
如果你起了一个65536长度的变量名,在idea中不会提示报错,但是运行就会获得一个错误
Error:(3, 8) java: 对于常量池来说, 字符串 "aaaaaaaaaaaaaaaaaaaa..." 的 UTF8 表示过长
因为无论是字段名还是方法名或者是类名,最终在常量池中都会用一个Utf8的常量去表示,但是class文件中只给了u2个字节去表示随后的Utf8字节的长度。
而u2字节能表示最大的十进制数就是65535
FFFF = 65535
为什么方法参数最多是255
其实这个说法有问题,其实Java中最多能有255个单位(4个字节为1个单位)的参数数量,
不足4个字节也会当成4个字节,所以long和double类型会占2个单位
// 256个String
Error:(5, 25) java: 参数过多
// 255个long
Error:(5, 25) java: 参数过多
// 256个byte
Error:(5, 25) java: 参数过多
// 127个long
ok
// 255个byte,String
ok
而且静态和非静态的方法是有区别的,静态方法能比非静态方法多一个参数(可能),是因为非静态方法会有第一个参数默认指向当前实例对象,就是this
方法的参数其实会作为方法内部局部变量来使用
而Jvm中Code属性有一个max_locals属性,u2的长度来记录整个方法的局部变量Slot数量,
那么u2明明可以记录65535个Slot,为什么方法参数只能是255个呢?
让我们走进jdk
// langtools/src/share/classes/com/sun/tools/javac/jvm/Gen.java 这个是jdk javac命令相关源码
public class Gen extends JCTree.Visitor {
...
// genMethod方法里会对方法参数数量进行校验
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 {
...
// 十六进制FF 就是 255
public final static int MAX_PARAMETERS = 0xff;
...
}
既然javac不允许我们在源码中声明一个超过255参数的方法,如果直接通过字节码的技术,直接生成一个class文件,能突破这个255的限制吗
有请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();
}
运行试试
$ 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)
还是获得了一个错误,证明jvm在运行的时候也会对参数长度进行校验
通过错误提示,再去扒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) {
// 这里也体现了静态和非静态,参数上限是不同的
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));
}
}
...
}