Java Class文件的结构
在*.class文件中,以Byte流的形式进行Class的存储,通过一系列Load,Parse后,Java代码实际上可以映射为下图的C结构体,这里可以用javap -v -p
命令或者IDE插件进行查看。
typedef struct {
u4 magic;/*0xCAFEBABE*/
u2 minor_version; /*网上有表可查*/
u2 major_version; /*网上有表可查*/
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
//重要
u2 fields_count;
field_info fields[fields_count];
//重要
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}ClassBlock;
-
常量池(constant pool):类似于C中的DATA段与BSS段,提供常量、字符串、方法名等值或者符号(可以看作偏移定值的指针)的存放
-
access_flags: 对Class的flag修饰
typedef enum { ACC_PUBLIC = 0x0001, ACC_FINAL = 0x0010, ACC_SUPER = 0x0020, ACC_INTERFACE = 0x0200, ACC_ACSTRACT = 0x0400 }AccessFlag
-
this class/super class/interface: 一个长度为u2的指针,指向常量池中真正的地址,将在Link阶段进行符号解引。
-
filed: 字段信息,结构体如下
typedef struct fieldblock { char *name; char *type; char *signature; u2 access_flags; u2 constant; union { union { char data[8]; uintptr_t u; long long l; void *p; int i; } static_value; u4 offset; } u; } FieldBlock;
-
method: 提供descriptor, access_flags, Code等索引,并指向常量池:
它的结构体如下,详细在这里
method_info { u2 access_flags; u2 name_index; //the parameters that the method takes and the //value that it return u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
以上具体内容可以参考
Class与Object
typedef struct object Class;
typedef struct object {
uintptr_t lock;
Class *class;
} Object;
本文指的Class
指
前提: 已经获取到Class
结构体对应的指针
下面是经过删减与注释的代码(删去了状态判断、Lock与异常处理),并替换宏变量为字符串
// class.c
Class *initClass(Class *class) {
ClassBlock *cb = CLASS_CB(class);
ConstantPool *cp = &cb->constant_pool;
FieldBlock *fb = cb->fields;
MethodBlock *mb;
Object *excep;
int state, i;
linkClass(class);
cb->state = CLASS_INITING;
cb->initing_tid = threadSelf()->id;
if(!(cb->access_flags & ACC_INTERFACE) && cb->super
&& (CLASS_CB(cb->super)->state != CLASS_INITED)) {
initClass(cb->super);
}
/* Never used to bother with this as only static finals use it and
the constant value's copied at compile time. However, separate
compilation can result in a getstatic to a (now) constant field,
and the VM didn't initialise it... */
for(i = 0; i < cb->fields_count; i++,fb++)
if((fb->access_flags & ACC_STATIC) && fb->constant) {
if((*fb->type == 'J') || (*fb->type == 'D'))
fb->u.static_value.l = *(u8*)&(CP_INFO(cp, fb->constant));
else
fb->u.static_value.u = resolveSingleConstant(class, fb->constant);
}
if((mb = findMethod(class, "<clinit>", "()V")) != NULL)
executeStaticMethod(class, mb);
return class;
}
贴出上面主要是为了让你明白
- 调用
linkClass
进行连接 - 调用
super
的class中的initClass
- 调用
<clinit>
方法,也就是static代码段
上面部分实际上是对Class进行模式匹配(pattmatch)的遍历,伪代码如下
(define initClass
(lambda (exp)
(linkClass exp)
(match [(?isSuperInited superClass) (initClass superClass)])
(clinit exp)))
在平时开发中,只需要背住就可以了。
Class与依赖注入
很多人认为学习JVM是“高手”才去做的,平时写业务时没用,下面举一个Spring断点调试技巧。在分析Spring的依赖注入时,很多人看到复杂源码就无法接着分析了。其实可以这样,首先在Bean中加入static代码段,并打上断点,然后启动程序。
@Bean(name="SSR")
public class DemoBean{
static{
// 此处打上断点
System.out.println("class loaded");
}
....
}
等断点跳到这里时,可以发现是Class.newInstance()
方法被调用,进而调用<clinit>
,此时再向上分析Spring的代码堆栈,阅读源码与流程就轻而易举了。
附录
下面2个是常见面试题
求打印顺序
实例化AChildChild后,求输出顺序
public class AParent {
static {println("AParent clinit");}//1
public AParent() {System.out.println("AParent init");}//4
}
public class AChild extends AParent {
static {println("AChild clinit");}//2
public AChild() {println("AChild init");}// 5
}
public class AChildChild extends AChild {
static {println("AChildChild clinit");}//3
public AChildChild() {println("AChildChild init");}//6
}
AChildChild acc = new AChildChild();
其中1,2,3的顺序本文已经可以解释,4,5,6下次讲解init
时进行分析
JDBC的加载
下图是加载mysql的例子,当程序员调用Class.forName
时,static代码段就会执行
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {}
}