01 一些关于java编译器的问题(init, clinit的生成, 自己实现javap?)

2024-05-28 15:32

本文主要是介绍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?)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1010960

相关文章

在Ubuntu上部署SpringBoot应用的操作步骤

《在Ubuntu上部署SpringBoot应用的操作步骤》随着云计算和容器化技术的普及,Linux服务器已成为部署Web应用程序的主流平台之一,Java作为一种跨平台的编程语言,具有广泛的应用场景,本... 目录一、部署准备二、安装 Java 环境1. 安装 JDK2. 验证 Java 安装三、安装 mys

Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二

oracle数据库索引失效的问题及解决

《oracle数据库索引失效的问题及解决》本文总结了在Oracle数据库中索引失效的一些常见场景,包括使用isnull、isnotnull、!=、、、函数处理、like前置%查询以及范围索引和等值索引... 目录oracle数据库索引失效问题场景环境索引失效情况及验证结论一结论二结论三结论四结论五总结ora

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

python使用watchdog实现文件资源监控

《python使用watchdog实现文件资源监控》watchdog支持跨平台文件资源监控,可以检测指定文件夹下文件及文件夹变动,下面我们来看看Python如何使用watchdog实现文件资源监控吧... python文件监控库watchdogs简介随着Python在各种应用领域中的广泛使用,其生态环境也

el-select下拉选择缓存的实现

《el-select下拉选择缓存的实现》本文主要介绍了在使用el-select实现下拉选择缓存时遇到的问题及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录项目场景:问题描述解决方案:项目场景:从左侧列表中选取字段填入右侧下拉多选框,用户可以对右侧

element-ui下拉输入框+resetFields无法回显的问题解决

《element-ui下拉输入框+resetFields无法回显的问题解决》本文主要介绍了在使用ElementUI的下拉输入框时,点击重置按钮后输入框无法回显数据的问题,具有一定的参考价值,感兴趣的... 目录描述原因问题重现解决方案方法一方法二总结描述第一次进入页面,不做任何操作,点击重置按钮,再进行下

SpringCloud集成AlloyDB的示例代码

《SpringCloud集成AlloyDB的示例代码》AlloyDB是GoogleCloud提供的一种高度可扩展、强性能的关系型数据库服务,它兼容PostgreSQL,并提供了更快的查询性能... 目录1.AlloyDBjavascript是什么?AlloyDB 的工作原理2.搭建测试环境3.代码工程1.

Java调用Python代码的几种方法小结

《Java调用Python代码的几种方法小结》Python语言有丰富的系统管理、数据处理、统计类软件包,因此从java应用中调用Python代码的需求很常见、实用,本文介绍几种方法从java调用Pyt... 目录引言Java core使用ProcessBuilder使用Java脚本引擎总结引言python