Java二进制指令代码以以下格式紧凑排列Q?/span>opcode占一个字节)Q?/span>
opcode operand*
除了tableswitch?/span>lookupswitch两条指o中间存在填充字节以外Q其他指令都没有填充字节Q即使在两条指o之间也没有。因而在d指o的时候,要根据指令的定义d?/span>
通过对上?/span>Java指o集的分析可以知道Q?/span>Java指o集中很大一部分没有操作敎ͼ因而对q部分指令,只需要读取一个字节的操作码,操作码映射成助记符卛_?/span>
而对其他带操作数的指令,则需要根据不同类型分析(׃apache中的bcelQ?/span>Binary Code Engineering LibraryQ对字节码的支持Q操作码和助记符的映可以用com.sun.org.apache.bcel.internal.Constats中提供的映射表数l来完成Q?/span>
1. 处理两条Ҏ的指?/span>tableswitch?/span>lookupswitch指o?/span>
对这两条指oQ首先都要去掉填充字W以?/span>defaultbyte1索引h字对齐的?/span>
private static void make4ByteAlignment(ByteSequence codes) {
int usedBytes = codes.getIndex() % 4;
int paddingBytes = (usedBytes == 0) ? 0 : 4 - usedBytes;
for(int i = 0;i < paddingBytes;i++) {
codes.readByte();
}
}
?/span>tableswitch指oQ读?/span>defaultoffset|最项的|最大项的g及在最项和最大项之间每一的offset倹{ƈ且将d到的offset值和当前指o的基地址相加Q?/span>
int defaultOffset1 = baseOffset + codes.readInt();
builder.append("\tdefault = #" + defaultOffset1);
int low = codes.readInt();
int high = codes.readInt();
int npair1 = high - low + 1;
builder.append(", npairs = " + npair1 + "\n");
for(int i = low;i <= high;i++) {
int match = i;
offset = baseOffset + codes.readInt();
builder.append(String.format("\tcase %d : #%d\n", match, offset));
}
?/span>lookupswitch指oQ读?/span>defaultoffset|键值对数|npairsQ,以及npairs对的键值对Q将得到?/span>offset值和当前指o的基地址相加Q?/span>
int defaultOffset2 = baseOffset + codes.readInt();
builder.append("\tdefault = #" + defaultOffset2);
int npairs2 = codes.readInt();
builder.append(", npairs = " + npairs2 + "\n");
for(int i = 0;i < npairs2;i++) {
int match = codes.readInt();
offset = baseOffset + codes.readInt();
builder.append(String.format("\tcase %d : #%d\n", match, offset));
}
2. 所有条件蟩转指令都有两个字节的偏移量操作数Q?/span>if<cond>, if_icmp<cond>, ifnull, ifnonnull, if_acmp<cond>Q。无条g跌{指ogoto和子例程跌{指ojsr也都是两个字节的偏移量作为操作数?/span>
offset = baseOffset + codes.readShort();
builder.append(String.format("\t\t#%d\n", offset));
3. 对宽偏移量的跌{指ogoto_w和子例程跌{指ojsr_w的操作数是四个字节的偏移量?/span>
offset = baseOffset + codes.readInt();
builder.append(String.format("\t\t#%d\n", offset));
4. wide指oQ则l箋d下一条指令,q将wide参数讄?/span>true?/span>
byteCodeToString(codes, pool, verbose, true);
5. q有一些指令g一个字节的局部变量烦引号作ؓ操作数的Q如果有wide修饰Q则用两个字节作为操作数Q代表局部变量烦引号。这L指o有:aload, iload, fload, lload, dload, astore, istore, fstore, lstore, dstore, ret?/span>
if(wide) {
index = codes.readUnsignedShort();
} else {
index = codes.readUnsignedByte();
}
builder.append(String.format("\t\t%%%d\n", index));
6. iinc指oQ以一个字节的局部变量烦引号和一个自q帔R作ؓ参数Q如果以wide修饰Q则该指令的局部变量烦引号和常量都占两个字节?/span>
if(wide) {
index = codes.readUnsignedShort();
constValue = codes.readShort();
} else {
index = codes.readUnsignedByte();
constValue = codes.readByte();
}
builder.append(String.format("\t\t%d %d\n", index, constValue));
7. 对象操作指oQ它们的操作数都是常量池中的索引Q长度ؓ两个字节。指?/span>CONSTANT_Class_infocd的结构,q些指o?/span>new, checkcast, instanceof, anewarray?/span>
index = codes.readUnsignedShort();
builder.append("\t\t" + pool.getClassInfo(index).toInstructionString(verbose) + "\n");
8. 所有字D|作指令,它们的操作数都是帔R池中的烦引,长度Z个字节。指?/span>CONSTANT_Fieldref_infocdl构Q这些指令有getfield, putfield, getstatic, putstatic?/span>
index = codes.readUnsignedShort();
builder.append("\t\t" + pool.getFieldRefInfo(index).toInstructionString(verbose) + "\n");
9. 非接口方法调用指令,也都是以两个字节的烦引号作ؓ操作敎ͼ指向帔R池中?/span>CONSTANT_Methodref_infocdl构Q这些指令有invokespecial, invokevirtual, invokestatic?/span>
index = codes.readUnsignedShort();
builder.append("\t\t" + pool.getMethodRefInfo(index).toInstructionString(verbose) + "\n");
10. 接口Ҏ调用指oinvokeinterfaceQ它有四个字节的操作敎ͼ前两个字节ؓ帔R池的索引P指向CONSTANT_InterfaceMethodref_infocdQ第三个字节?/span>countQ表C参数的字节敎ͼ最后一个字节ؓ0倹{?/span>
index = codes.readUnsignedShort();
int nargs = codes.readUnsignedByte(); //Historical, redundant
builder.append("\t\t" + pool.getInterfaceMethodRefInfo(index).toInstructionString(verbose));
builder.append(" : " + nargs + "\n");
codes.readUnsignedByte(); //reserved should be zero
11. 基本cd的数l创建指?/span>newarrayQ它的操作数Z个字节的cd标识?/span>
String type = Constants.TYPE_NAMES[codes.readByte()];
builder.append(String.format("\t\t(%s)\n", type));
12. 多维数组的创建指?/span>multianewarrayQ它有三个字节的操作敎ͼ前两个字节ؓ索引P指向CONSTANT_Class_infocdQ表C数l的cdQ最后一个字节指定数l的l度?/span>
index = codes.readUnsignedShort();
int dimensions = codes.readUnsignedByte();
builder.append(String.format("\t\t%s (%d)\n", pool.getClassInfo(index).getName(), dimensions));
13. 帔R入栈指oldcQ以一个字节的索引号作为参敎ͼ指向CONSTANT_Integer_info?/span>CONSTANT_Float_info?/span>CONSTANT_String_info?/span>CONSTANT_Class_infocdQ表C入栈的常量|intcd倹{?/span>floatcd倹{?/span>String引用cd值或对象引用cd|?/span>
index = codes.readUnsignedByte();
builder.append("\t\t" + pool.getPoolItem(index).toInstructionString(verbose) + "\n");
14. 宽烦引的帔R入栈指oldc_wQ以两个字节的烦引号作ؓ参数Q指?/span>CONSTANT_Integer_info?/span>CONSTANT_Float_info?/span>CONSTANT_String_info?/span>CONSTANT_Class_infocdQ表C入栈的常量|intcd倹{?/span>floatcd倹{?/span>String引用cd值或对象引用cd|?/span>
index = codes.readUnsignedShort();
builder.append("\t\t" + pool.getPoolItem(index).toInstructionString(verbose) + "\n");
15. 宽烦引的帔R入栈指oldc2_wQ以两个字节的烦引号作ؓ参数Q指?/span>CONSTANT_Long_info?/span>CONSTANT_Double_infocdQ表C入栈的常量|longcd倹{?/span>doublecd|?/span>
index = codes.readUnsignedShort();
builder.append("\t\t" + pool.getPoolItem(index).toInstructionString(verbose) + "\n");
16. bipush指oQ以一个字节的帔R作ؓ操作数?/span>
byte constByte = codes.readByte();
builder.append(“\t” + constByte);
17. sipush指oQ以两个字节的常量作为操作数?/span>
short constShort = codes.readShort();
builder.append(“\t” + constShort);
以上q有一些没有完成的代码Q包括字D(ҎQ的{和描q符没有解析Q有一些解析的格式q需要调整等。不怎么PM的结构就是这样了Q其它的都是l节问题Q这里不讨论了?/span>
参见bcel目?/span>org.apache.bcel.classfile.Utilityc?
2010q?0??/span>
Java虚拟Z内部pȝ构包?/span>class文g、类装蝲子系l、运行时数据区、之行引擎、本地方法调用结构,其中q行时数据区包括Ҏ区、堆?/span>Java栈、程序计数器、本地方法栈{。具体结构如下图所C(摘自Inside Java Virtual MachineQ:
1. class文g
?/span>Java中,所有源文g都编译成二进制的字节码,然后p拟机装蝲q行。一般这L字节码是?/span>class文g的Ş式存在。在q行Ӟ?/span>ClassLoaderc(System ClassLoader or User-defined ClassLoaderQ找到对应的class文gQ读取其中的字节码,然后交由虚拟析运行?/span>
?/span>class文g中,包含了定义一个类或接口的所有信息,包括cd、访问权限、父cd、承的所有接口、所有字Dc所有方法、方法中的代码、属性等信息Qƈ且每?/span>class文g的开头还包含了魔术值和版本信息Q魔术值用以标识当前的字节码是合法的字节码Q版本表C生成当前字节码的编译器版本Q从而虚拟机L其版本而做特定处理Q如果对于虚拟机不支持的字节码版本号拒绝加蝲?/span>
?/span>class文g中,很多信息都是以字W串的Ş式存放,比如对外部类成员或方法的引用Q这些字W串信息在链接的时候由虚拟析。每?/span>Javac,不管是包成员c还是内部类都会生成一个单独的class文gQ因?/span>class文g是相对独立的。详l信息参?/span>class文g格式?/span>
2. c装载子pȝ
c装载子pȝ负责查找class文gQ读取字节码Q做部分单的验,如魔数是否正,版本是否受支持,各种数据格式是否正确{。部分解析后的字节码数据存放到方法区中,最后创建字节码代表的类或接口的Class实例?/span>
?/span>Java中,c装载系l是通过ClassLoader来完成的。虚拟机规范中,定义了启动类装蝲器和用于定义c装载器。在sun提供的虚拟机中,包括了启动类装蝲器、扩展类装蝲器、系l类装蝲器、用户定义类装蝲器。他们以父子铄方式l织在一赗除了启动类装蝲器,其他的装载器都是ClassLoader的子cR?/span>ClassLoader定义了一些方法可以帮助用户定义自qc装载器Q如defineClass{。详情参?/span>Java中的ClassLoader?/span>
如何卸蝲cL据?Q第七章Q?/span>
3. q行时数据区
q行时数据区保存了所有在q行时的信息。包括方法区?/span>Java栈、堆、程序寄存器、本地方法栈{。其中方法区和堆只在虚拟Z保存一份实例,因而需要处理多U程的同步问题;Java栈、程序寄存器是每个线E中有单独的实例Q因而对不同的线E,他们的数据是U有的?/span>
3.1 Ҏ?/span>
ҎZ保存了读取的字节码信息(包括帔R池,静态方法和静态成员信息)、字节码代表?/span>Classcd例、一个指向加载它?/span>ClassLoader实例?/span>
JavaE序可以有两U方式来获取某个cȝClass实例Q?/span>
1. Class.forName()Ҏ
2. Object.getClass()Ҏ
通过Class实例获取和该cL接口相关的Q何信息。参?/span>Classcȝ定义?/span>
Q注Q对有启?/span>ClassLoader加蝲的类Q?/span>ClassҎ中的getClassLoaderҎq回nullQ?/span>
为加快执行速度Q可以在ҎZ引入Ҏ表机Ӟ记录能被外界调用的该cȝ实例ҎQ包括父cMl承下来的方法?strong>Q第八章详细介绍Q)
ҎZҎcd搜烦cM息,法Q散列、搜索树{?/span>
3.2 Java?/span>
虚拟Zؓ每个U程生成一?/span>Java栈,因而对不同的线E,栈内的数据都是私有的?/span>Java栈由栈l成Q?/span>Java栈的操作只有两种Q压入栈帧和弹出栈。线E中每个Ҏ的调用都会在Java栈压入一个栈帧;每次Ҏq回Q正常方法或抛异常返回)Q该Ҏ对应的栈帧都会从栈中弹出?/span>
3.2.1 栈
栈由操作数栈、局部变量区和栈帧数据组成。由?/span>Java中的指o是基于栈而设计的Q因而很多指令的默认操作数就是操作数栈中的数据。操作数栈用于保存指令的操作数和指o操作后的l果?/span>
局部变量区用于保存当前Ҏ的局部变量?/span>
栈数据区则保存当前栈的信息,如指向当前类帔R池的指针Q用于操作数为常量池索引的指令;q有一些和特定虚拟机实现相关的信息和调试信息?/span>
3.3 E序寄存?/span>
每个U程在执行时都会保存当前指o的下一条指令的地址Q以控制E序的之行流E?/span>
3.4 ?/span>
堆保存了E序在运行时的所有对象。在Java中,所有的对象都是保存在堆中的Q而外部通过对象的引用来讉K对象。由?/span>Java存在垃圾回收器,因?/span>Java对象可能被移动,以减内存碎片。其中一U实现可以很好的解决Ud对象而需要改变所有该对象的引用变量的技术,卛_堆分为句柄池和对象池。对象池中的对象保存了对象的真正内容Q而句柄池中的包含两个指针,一个指向对象,一个指向类数据。一个对象引用就是指向句柄的之战。这样当需要移动对象时Q只要改变句柄池中指向对象的指针值即可。然而这U设计是以牺牲速度ZLQ因hơ访问对象就要多l历一ơ指针定位?/span>
在某些垃圑֛收器实现中,对象需要额外的信息Q如果引用计数的垃圾攉器,需要ؓ每个对象记录引用计数信息Q而对另外有些机制Q则可能需要暂时保存某些数据。这些额外的数据可以保存在类中,也可以在记录在其他地斏V类似的q有同步机制中的数据和记录是否已l调用过finalizeҎ的信息?/span>
?/span>Java中有指o用于在内存中分配对象Q却没有昑ּ的指令来释放内存中的对象?/span>
3.5 本地Ҏ?/span>
?/span>JavaҎ调用本地Ҏ的时候,当前U程的程序寄存器是不定的倹{程序的执行也{向本地方法。本地方法可以正常返回,也可以抛出异常。抛出的异常会在调用该本地方法的指o中重新抛出?/span>
4. 执行引擎
每个用户U程Q即不包括垃圑֛收线E等Q都有一个执行引擎实例,用以执行字节码指令?/span>
5. 本地Ҏ接口
JavaE序可以通过本地Ҏ接口来调用本地方法?br />
?010-10-06