本文主要是介绍01 一些关于java编译器的问题(init, clinit的生成, 自己实现javap?),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言
呵呵 最近看到了一系列跟 java编译器 相关的一系列的问题, 所以整理了一下
一下部分代码, 截图 基于 : jdk7u40, idea2019 的 bytecode viewer, jls7, jdk7 的 javac
1. 关于 javap 里面看不到 "<init>", "<clinit>"
https://hllvm-group.iteye.com/group/topic/35224
看到这篇文章的时候, 去搜索了一下 javap 对应的代码
我这里搜到的结果和 R大 的回答似乎是不一致的, 可能是看的代码的版本不一样吧, 呵呵
com.sun.tools.javap.ClassWriter. writeMethod
writeModifiers(flags.getMethodModifiers());if (methodType != null) {writeListIfNotEmpty("<", methodType.typeParamTypes, "> ");}if (getName(m).equals("<init>")) {print(getJavaName(classFile));print(getJavaParameterTypes(d, flags));} else if (getName(m).equals("<clinit>")) {print("{}");} else {print(getJavaReturnType(d));print(" ");print(getName(m));print(getJavaParameterTypes(d, flags));}
测试代码如下
/*** Test11InitAndClinit** @author Jerry.X.He <970655147@qq.com>* @version 1.0* @date 2019/11/16 17:00*/
public class Test11InitAndClinit {// xprivate int x = 1;public Test11InitAndClinit() {int x = 4;}public Test11InitAndClinit(int arg) {int x = arg;}// <init>{int x = 3;}// <clinit>static {int x = 2;}// Test11InitAndClinit// refer : https://hllvm-group.iteye.com/group/topic/35224public static void main(String[] args) {int x = 6;}}
这样就有了 javap 里面看到的 这样的结果, 也就是 题主所问的东西
// <init>
public com.hx.test11.Test11InitAndClinit();
public com.hx.test11.Test11InitAndClinit(int);// <clinit>
static {};
2. 关于 "<init>", "<clinit>" 的构造
另外对于这个问题, 还有一些 扩展的地方 : {int x = 3; } 是怎么被merge到 多个构造方法里面的呢?, 多个 static {}, 怎么 merge 到一个 <clinit> 的呢 ?
我们定位到 Gen. genClass 方法上面看一下
normalizeDefs 之前, 我们发现 defs 列表, 和我们代码结构是一致的
normalizeDefs 之后, 就变成了 两个 <init> 加一个 <clinit> 加一个 main 方法了, 这是怎么回事呢 ?
normalizeDefs 到底做了什么呢 ?
Gen.normalizeDefs
/** Distribute member initializer code into constructors and <clinit>* method.* @param defs The list of class member declarations.* @param c The enclosing class.*/List<JCTree> normalizeDefs(List<JCTree> defs, ClassSymbol c) {ListBuffer<JCStatement> initCode = new ListBuffer<JCStatement>();ListBuffer<JCStatement> clinitCode = new ListBuffer<JCStatement>();ListBuffer<JCTree> methodDefs = new ListBuffer<JCTree>();// Sort definitions into three listbuffers:// - initCode for instance initializers// - clinitCode for class initializers// - methodDefs for method definitionsfor (List<JCTree> l = defs; l.nonEmpty(); l = l.tail) {JCTree def = l.head;switch (def.getTag()) {case JCTree.BLOCK:JCBlock block = (JCBlock)def;if ((block.flags & STATIC) != 0)clinitCode.append(block);elseinitCode.append(block);break;case JCTree.METHODDEF:methodDefs.append(def);break;case JCTree.VARDEF:JCVariableDecl vdef = (JCVariableDecl) def;VarSymbol sym = vdef.sym;checkDimension(vdef.pos(), sym.type);if (vdef.init != null) {if ((sym.flags() & STATIC) == 0) {// Always initialize instance variables.JCStatement init = make.at(vdef.pos()).Assignment(sym, vdef.init);initCode.append(init);if (endPositions != null) {Integer endPos = endPositions.remove(vdef);if (endPos != null) endPositions.put(init, endPos);}} else if (sym.getConstValue() == null) {// Initialize class (static) variables only if// they are not compile-time constants.JCStatement init = make.at(vdef.pos).Assignment(sym, vdef.init);clinitCode.append(init);if (endPositions != null) {Integer endPos = endPositions.remove(vdef);if (endPos != null) endPositions.put(init, endPos);}} else {checkStringConstant(vdef.init.pos(), sym.getConstValue());}}break;default:Assert.error();}}// Insert any instance initializers into all constructors.if (initCode.length() != 0) {List<JCStatement> inits = initCode.toList();for (JCTree t : methodDefs) {normalizeMethod((JCMethodDecl)t, inits);}}// If there are class initializers, create a <clinit> method// that contains them as its body.if (clinitCode.length() != 0) {MethodSymbol clinit = new MethodSymbol(STATIC, names.clinit,new MethodType(List.<Type>nil(), syms.voidType,List.<Type>nil(), syms.methodClass),c);c.members().enter(clinit);List<JCStatement> clinitStats = clinitCode.toList();JCBlock block = make.at(clinitStats.head.pos()).Block(0, clinitStats);block.endpos = TreeInfo.endPos(clinitStats.last());methodDefs.append(make.MethodDef(clinit, block));}// Return all method definitions.return methodDefs.toList();}
可以看到, 这里是 将 instance variable initializer 和 instance initializer 放到了 initCode, 将 class variable initializer 和 static initializer 放到了 clinitCode 里面
其他的方法(包括构造方法), 放到 methodDefs 里面
然后针对构造方法, 插入 initCode 的相关代码
以及 构造 <clinit> 方法(class variable initializer + static initializer), 加入 methodDefs
构造方法 结合 initCode 部分处理如下
/** Insert instance initializer code into initial constructor.* @param md The tree potentially representing a* constructor's definition.* @param initCode The list of instance initializer statements.*/void normalizeMethod(JCMethodDecl md, List<JCStatement> initCode) {if (md.name == names.init && TreeInfo.isInitialConstructor(md)) {// We are seeing a constructor that does not call another// constructor of the same class.List<JCStatement> stats = md.body.stats;ListBuffer<JCStatement> newstats = new ListBuffer<JCStatement>();if (stats.nonEmpty()) {// Copy initializers of synthetic variables generated in// the translation of inner classes.while (TreeInfo.isSyntheticInit(stats.head)) {newstats.append(stats.head);stats = stats.tail;}// Copy superclass constructor callnewstats.append(stats.head);stats = stats.tail;// Copy remaining synthetic initializers.while (stats.nonEmpty() &&TreeInfo.isSyntheticInit(stats.head)) {newstats.append(stats.head);stats = stats.tail;}// Now insert the initializer code.newstats.appendList(initCode);// And copy all remaining statements.while (stats.nonEmpty()) {newstats.append(stats.head);stats = stats.tail;}}md.body.stats = newstats.toList();if (md.body.endpos == Position.NOPOS)md.body.endpos = TreeInfo.endPos(md.body.stats.last());}}
可以看出 处理之后的构造方法 大致分为以下几个批次, 并且顺序如下 : 调用构造方法之前生成的代码, 调用构造方法, 调用构造方法之后生成的代码, instance variable initializer + instance initializer, 用户自定义的构造方法代码
相关引用如下
关于 instance variable initializer 和 class variable initializer : https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.2
关于 instance initializer 和 class initializer : https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6 & https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7
3. 自己实现 javap?
https://hllvm-group.iteye.com/group/topic/35386
呵呵呵, 自己实现 javap 似乎是没有什么太大的难度, 因为 javac 已经将相关数据分析好了, 放到了 class 里面, 只需要按照规范读就行
h还是像 R大 说的, 直接参考 jdk 的 javap 就行,
可以参考 jvms4.7 : https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7
以及 javap 解析出来的对象, 呵呵呵 何必自己写呢, 用现有的大佬写好的东西 不好么 (若是之前, 可能会去动手弄一下, 但是随着空闲时间越来越有限, 呵呵 就懒得去动手了)
不过这里引申出了一个 讨论, 关于class文件常量池
----------------------------------------------------------------------
chenjingbo 2013-01-14 说道 :
借这个宝地请教一下问题吧.我发现如果我的代码是,
Java代码 收藏代码
int a = 2000 ;
在javap 查看以后,发现javac并不会在常量池中生成一个2000的Integer类型的常量..
但是,
Java代码 收藏代码
final int a = 2000 ;
这样子就会生成一个2000的Integer类型的常量.
我的问题是,既然前面那种情况是用 sipush的指令直接赋值2000,那么,为什么在final的情况下不也直接如此赋值呢.这样的话,常量池类型就可以减少4个(CONSTANT_Integer CONSTANT_Float CONSTANT_Long CONSTANT_Double) RednaxelaFX 2013-01-14 说道 :
您没把代码写完整。实际上您的例子是类似这样的吧:
Java代码 收藏代码
public class Foo { int a = 2000;
}
Java代码 收藏代码
public class Foo { final int a = 2000;
}
您可以试试把例子改为在局部作用域里赋值,就会发现加上final不会生成值为2000的CONSTANT_Integer。
Java里类上的常量(static final的、原始类型或String的、有常量初始值的)的元数据必须记录在Class文件里;成员变量如果是final的、原始类型或String的、有常量初始值的,则这种常量信息也要被记录在Class文件里。这样的话就需要在常量池记录下常量值,然后在字段元数据里记录下ConstantValue属性,让它引用那个常量值。
而且您说的那四中常量池类型即便在其它场景也是必须的。例如说大于2字节的int常量就只能放常量池里用ldc指令来加载。
----------------------------------------------------------------------
首先我们先看一下 这个问题, 然后 在这个问题的基础上面 再扩展一下
测试代码如下
/*** Test12FinalVarInit** @author Jerry.X.He <970655147@qq.com>* @version 1.0* @date 2019/11/16 18:33*/
public class Test12FinalVarInit {final int x = 200;// Test12VarInitpublic static void main(String[] args) {System.out.print(" end .. ");}}
在 "final int x = 200;" 调试如下
往常量池里面添加 这个常量 200 的时候, 是在 往class里面写出字段 x 的时候, 发现 x 是常量, 然后写出了 x 对应的值 放到常量池
那么什么情况下 变量是 constantsValue 呢 ?
如果变量是 final 变量, 并且 初始化值 不是 new 创建的实例[final x = new Integer(200); ]
那么扩展一下 classfile 常量池 里面的内容有哪些呢 ?
查看一下 Pool.put 发现调用的地方主要是集中在两个类 ClassWriter 和 Items
ClassWriter 里面的主要的业务, 可以从 writeClassFile 作为入口查看, 这里面主要是采集了字节码里面所需要的常量, 可以参照 字节码文件规范 : https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7
Items 里面使用了常量池的地方主要是包含了三处地方 : MemberItem, StaticItem, ImmediateItem, 这三个 Items 主要是在遍历方法, 为方法生成字节码的时候使用, 生成相关字节码对应的字节码[Code属性]
MemberItem 主要是加载使用到的 成员变量, 方法 加载到常量池里面
StaticItem 主要是加载使用到的 静态变量, 静态方法 加载到常量池里面
ImmediateItem 主要是支持 ldc 指令的相关操作数, 将相关操作数存放到 常量池里面
当然有一些细节没有提及, 就好比我们这里上面的例子 "final int x = 200;", 200 进入了常量池, 细节层面的东西, 还是等需要的时候再去看吧, 可以在 ClassWriter 和 Items 里面去搜索 "pool.put"
4. 属性表中属性的区别难道是用字符串匹配?
https://hllvm-group.iteye.com/group/topic/35496
问题本身 好像是没有什么太大的争议, 规范如此, 为什么这么设计, R大 似乎也做了一些介绍
呵呵, 我比较感兴趣的事 作者定位到了 两个字段对应在字节码中的数据, 是怎么做的呢 ?
我比较倾向于直接使用 javap 来读取吧, 直接拿到 解析之后的 "ClassFile" 对象
测试代码如下
/*** Test13FieldAttribute** @author Jerry.X.He <970655147@qq.com>* @version 1.0* @date 2019/11/16 19:12*/
public class Test13FieldAttribute {final float b = 1.2f;final int c = 1;}
读取class文件, 解析之后的 "ClassFile" 如下
javap 反编译如下
G:\tmp\javac>javap -v Test13FieldAttribute.class
Classfile /G:/tmp/javac/Test13FieldAttribute.classLast modified 2019-11-16; size 341 bytesMD5 checksum ebdf79d6791914e583f9856778fcb932Compiled from "Test13FieldAttribute.java"
public class com.hx.test11.Test13FieldAttributeSourceFile: "Test13FieldAttribute.java"minor version: 0major version: 51flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref #6.#19 // java/lang/Object."<init>":()V#2 = Float 1.2f#3 = Fieldref #5.#20 // com/hx/test11/Test13FieldAttribute.b:F#4 = Fieldref #5.#21 // com/hx/test11/Test13FieldAttribute.c:I#5 = Class #22 // com/hx/test11/Test13FieldAttribute#6 = Class #23 // java/lang/Object#7 = Utf8 b#8 = Utf8 F#9 = Utf8 ConstantValue#10 = Utf8 c#11 = Utf8 I#12 = Integer 1#13 = Utf8 <init>#14 = Utf8 ()V#15 = Utf8 Code#16 = Utf8 LineNumberTable#17 = Utf8 SourceFile#18 = Utf8 Test13FieldAttribute.java#19 = NameAndType #13:#14 // "<init>":()V#20 = NameAndType #7:#8 // b:F#21 = NameAndType #10:#11 // c:I#22 = Utf8 com/hx/test11/Test13FieldAttribute#23 = Utf8 java/lang/Object
{final float b;flags: ACC_FINALConstantValue: float 1.2ffinal int c;flags: ACC_FINALConstantValue: int 1public com.hx.test11.Test13FieldAttribute();flags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: ldc #2 // float 1.2f7: putfield #3 // Field b:F10: aload_011: iconst_112: putfield #4 // Field c:I15: returnLineNumberTable:line 10: 0line 12: 4line 13: 10
}
两个变量的输入采集如下
varName accessFlags nameIdx descIdx attrCount attrNameIdx attrLength constantValueIdx
b 0010 0007 0008 0001 0009 0000 0002 0002
c 0010 000A 000B 0001 0009 0000 0002 000C
采集的方法, 就是调试咯, 解析 字段的时候, 调试查询对应的偏移区间, 进而进行查询
相关引用如下
classFile规范 : https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4
字段信息的存储 : https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.5
属性信息的存储 : https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7
属性ConstantValue : https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.2
完
引用
所有的引用已经在文档中已经提及到了
这篇关于01 一些关于java编译器的问题(init, clinit的生成, 自己实现javap?)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!