- 泛型在字节码中的表现
- 反射
- javac是如何编译的
泛型
泛型在字节码中
1 2 3 4 5 6
| public class Main { public static void main(String[] args) {
} public void test(List<String> list) { } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public void test(java.util.List<java.lang.String>); descriptor: (Ljava/util/List;)V flags: ACC_PUBLIC Code: stack=0, locals=2, args_size=2 0: return LineNumberTable: line 14: 0 LocalVariableTable: Start Length Slot Name Signature 0 1 0 this Lcom/yuda/test/Main; 0 1 1 list Ljava/util/List; LocalVariableTypeTable: Start Length Slot Name Signature 0 1 1 list Ljava/util/List<Ljava/lang/String;>; Signature: #22 // (Ljava/util/List<Ljava/lang/String;>;)V
|
可以看到Code
里面没有任何String
相关的表示, 说明JVM处理泛型时, 会自己删除掉这种类型, 只能识别为List
, 这样就导致了无法用下面方式重载方法:
1 2
| public void test(List<String> list) { } public void test(List<Integer> list) { }
|
但是他多了一个Signature
里面记录了参数的类型, 通过Signature
, 可以在反射中取到类型.
反射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Main { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { Class<?> clz = Class.forName("com.yuda.test.Main"); Method[] methods = clz.getMethods(); for (Method method : methods) { if (method.getName().equals("test")){ Parameter[] parameters = method.getParameters(); Type[] genericParameterTypes = method.getGenericParameterTypes(); for (Type genericParameterType : genericParameterTypes) { System.out.println(genericParameterType.getTypeName()); } } } } public void test(List<String> list) { } }
|
上面是通过反射取泛型的例子.
反射原理
故意让反射执行的方法报错, 看看它的异常栈.
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Main { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class<?> clz = Class.forName("com.yuda.test.Main"); Method method = clz.getDeclaredMethod("test"); for (int i = 0; i < 20; i++) { method.invoke(null); } } private static int count = 0; public static void test() { new Exception("test#" + (count++)).printStackTrace(); } }
|
循环了20次, 报错日志中搜索sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
却发现只有16次调用, 剩余4次使用了sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
.
应该是超过了某个阈值(16), 反射使用另外一种方式.
NativeMethodAccessorImpl
1 2 3 4 5 6 7 8
| public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException { if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) { MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers()); this.parent.setDelegate(var3); }
return invoke0(this.method, var1, var2); }
|
ReflectionFactory.inflationThreshold()
进去发现默认是15(1+15=16), 16次都会调到private static native Object invoke0(Method var0, Object var1, Object[] var2);
一个native的方法,之后就会进入新的逻辑, 通过sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
来执行, GeneratedMethodAccessor1
是通过 ASM 生成新的类.
为啥这么做? 小于15的认为使用率比较少, 用native方法比较划算, 大于15次认为经常使用, 使用生成字节码的方式比较划算. 可见native的方式执行起来比较慢, 生成字节码方式执行比较快, 但是多了一步生成字节码的操作, 这一步增加了耗时.
-Dsun.reflect.noInflation=true
设置是否不使用native, -Dsun.reflect.inflationThreshold=20
设置阈值
javac编译
javac
- parse : 读取java文件, 做词法分析和语法分析, 生成抽象语法树(AST).
- enter : 填充符号表(symbol table), 方便后续从里面取东西. 由标识符与标识符相关信息构成
- process : 注解的处理, 在编译过程中立即代码中的注解, 影响生成的字节码.
- attr : 语义合法性检查和进行逻辑判断.
- 冲突的类定义
- 访问权限, 检查用的对不对
- 重载方法的使用选择
- 折叠常量.
- flow : 数据流分析.
- 非void方法是否有return
- 受检异常是否有处理
- 局部变量是否初始化
- final是否有赋值
- 是否有语句不可达
- desugar : 去除语法糖, 转换成标准的写法, 以便统一的处理.
- 泛型
- 内部类
- foreach
- 包装类与基本类型隐式转换
- 字符串与枚举的switch
i++
和++i
- 变长参数
test(String... strs)
- generate : 生成最终的 Class文件.