- 浏览: 3052793 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (430)
- Programming Languages (23)
- Compiler (20)
- Virtual Machine (57)
- Garbage Collection (4)
- HotSpot VM (26)
- Mono (2)
- SSCLI Rotor (1)
- Harmony (0)
- DLR (19)
- Ruby (28)
- C# (38)
- F# (3)
- Haskell (0)
- Scheme (1)
- Regular Expression (5)
- Python (4)
- ECMAScript (2)
- JavaScript (18)
- ActionScript (7)
- Squirrel (2)
- C (6)
- C++ (10)
- D (2)
- .NET (13)
- Java (86)
- Scala (1)
- Groovy (3)
- Optimization (6)
- Data Structure and Algorithm (3)
- Books (4)
- WPF (1)
- Game Engines (7)
- 吉里吉里 (12)
- UML (1)
- Reverse Engineering (11)
- NSIS (4)
- Utilities (3)
- Design Patterns (1)
- Visual Studio (9)
- Windows 7 (3)
- x86 Assembler (1)
- Android (2)
- School Assignment / Test (6)
- Anti-virus (1)
- REST (1)
- Profiling (1)
- misc (39)
- NetOA (12)
- rant (6)
- anime (5)
- Links (12)
- CLR (7)
- GC (1)
- OpenJDK (2)
- JVM (4)
- KVM (0)
- Rhino (1)
- LINQ (2)
- JScript (0)
- Nashorn (0)
- Dalvik (1)
- DTrace (0)
- LLVM (0)
- MSIL (0)
最新评论
-
mldxs:
虽然很多还是看不懂,写的很好!
虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 -
HanyuKing:
Java的多维数组 -
funnyone:
Java 8的default method与method resolution -
ljs_nogard:
Xamarin workbook - .Net Core 中不 ...
LINQ的恶搞…… -
txm119161336:
allocatestlye1 顺序为 // Fields o ...
最近做的两次Java/JVM分享的概要
有人问下面这段代码里,main()方法里的outer.new Inner()部分为什么会生成了一个对outer.getClass()的调用:
javac编译它生成的main方法的代码是:
其中,对应outer.new Inner()的部分是:
可以看到里面有一处对outer.getClass()的调用,然而得到的结果却马上被pop指令抛弃掉了。
这个问题通过调试javac很容易解决。调试javac的方法可以参考以前一帖。
设置好调试环境后,在调试器里把上面的代码交给javac去编译。
简单猜测就可以知道,生成的对outer.getClass()方法的调用是在最终生成代码的时候才做的,而不是在更早阶段被解除的语法糖,所以我们要注意的目标就是com.sun.tools.javac.jvm.Gen类,其中的visitNewClass(JCNewClass tree)方法。
在这个方法设上断点,然后开始调试。
第一次碰到断点会是main()方法里的new Outer(),这个跳过。
然后第二次进来的时候,观察调试器的变量窗口,可以看到:
从javac的角度来看,源码里的
被改写成了这种形式:
(内部类被改名和改写为顶层类、隐式的外部类参数改写为显式参数)
接下来,有趣的点就是那个<*nullchk*>注释。
传给Inner()构造器的实际参数并不是原本的outer局部变量,而是outer局部变量外加一个空指针检查——要的值还是outer的值,不过如果outer为null的话,这里要抛出NullPointerException。
从javac的角度看,直接读outer局部变量可以用一个JCTree.JCIdent节点来表示,而这里则多包装了一个tag为JCTree.NULLCHK的JCTree.JCUnary节点。
正是在生成这个outer<*nullchk*>节点的代码时,会执行到Gen.visitUnary()的下述部分:
其中的genNullCheck()是:
也就是说那个对getClass()的调用只不过是借invokevirtual指令来帮忙做null检查而已。getClass()本身得到的值其实是没用到的。
这个行为在Java语言规范里有相应的规定。在Java语言规范第三版,
15.9.2 Determining Enclosing Instances
该小节规定了应该使用什么对象作为外部类的实例
15.9.3 Choosing the Constructor and its Arguments
该小节规定了外部类实例在参数列表中的位置
15.9.4 Run-time Evaluation of Class Instance Creation Expressions
该小节规定了上面提到的空指针检查的行为:
规范里明确了“要抛出NullPointerException”的行为,至于是如何实现null检查的则没规定,可以由实现自由发挥。用普通的if...else来做这个检查当然也可以,只不过生成的字节码就比调用getClass()的办法更长一些。
====================================================================
无独有偶,ECJ(Eclipse Compiler for Java)在编译这种代码的时候同样会生成对getClass()方法的调用:
这当然不是偶然,因为getClass()方法是在Object上声明的(因此所有对象上必然存在),而且是final的(保证了它有确定的行为),而且运行开销比较低。
同样是Object上声明的方法,toString()、hashCode()之类其实也可以用,但它们都不是final的,有潜在可能性会引发较大的运行开销;这么分析一圈下来,Object上最好用的就剩下getClass()了。
public class Outer { public class Inner { } public static void main(String[] args) { Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); } }
javac编译它生成的main方法的代码是:
public static void main(java.lang.String[]); Code: Stack=4, Locals=3, Args_size=1 0: new #2; //class Outer 3: dup 4: invokespecial #3; //Method "<init>":()V 7: astore_1 8: new #4; //class Outer$Inner 11: dup 12: aload_1 13: dup 14: invokevirtual #5; //Method java/lang/Object.getClass:()Ljava/lang/Class; 17: pop 18: invokespecial #6; //Method Outer$Inner."<init>":(LOuter;)V 21: astore_2 22: return LineNumberTable: line 4: 0 line 5: 8 line 6: 22
其中,对应outer.new Inner()的部分是:
8: new #4; //class Outer$Inner 11: dup 12: aload_1 13: dup 14: invokevirtual #5; //Method java/lang/Object.getClass:()Ljava/lang/Class; 17: pop 18: invokespecial #6; //Method Outer$Inner."<init>":(LOuter;)V
可以看到里面有一处对outer.getClass()的调用,然而得到的结果却马上被pop指令抛弃掉了。
这个问题通过调试javac很容易解决。调试javac的方法可以参考以前一帖。
设置好调试环境后,在调试器里把上面的代码交给javac去编译。
简单猜测就可以知道,生成的对outer.getClass()方法的调用是在最终生成代码的时候才做的,而不是在更早阶段被解除的语法糖,所以我们要注意的目标就是com.sun.tools.javac.jvm.Gen类,其中的visitNewClass(JCNewClass tree)方法。
在这个方法设上断点,然后开始调试。
第一次碰到断点会是main()方法里的new Outer(),这个跳过。
然后第二次进来的时候,观察调试器的变量窗口,可以看到:
从javac的角度来看,源码里的
outer.new Inner()
被改写成了这种形式:
new Outer$Inner(outer<*nullchk*>)
(内部类被改名和改写为顶层类、隐式的外部类参数改写为显式参数)
接下来,有趣的点就是那个<*nullchk*>注释。
传给Inner()构造器的实际参数并不是原本的outer局部变量,而是outer局部变量外加一个空指针检查——要的值还是outer的值,不过如果outer为null的话,这里要抛出NullPointerException。
从javac的角度看,直接读outer局部变量可以用一个JCTree.JCIdent节点来表示,而这里则多包装了一个tag为JCTree.NULLCHK的JCTree.JCUnary节点。
正是在生成这个outer<*nullchk*>节点的代码时,会执行到Gen.visitUnary()的下述部分:
public void visitUnary(JCUnary tree) { // ... Item od = genExpr(tree.arg, operator.type.getParameterTypes().head); switch (tree.tag) { // ... case JCTree.NULLCHK: result = od.load(); code.emitop0(dup); genNullCheck(tree.pos()); break; } }
其中的genNullCheck()是:
/** Generate a null check from the object value at stack top. */ private void genNullCheck(DiagnosticPosition pos) { callMethod(pos, syms.objectType, names.getClass, List.<Type>nil(), false); code.emitop0(pop); }
也就是说那个对getClass()的调用只不过是借invokevirtual指令来帮忙做null检查而已。getClass()本身得到的值其实是没用到的。
这个行为在Java语言规范里有相应的规定。在Java语言规范第三版,
15.9.2 Determining Enclosing Instances
该小节规定了应该使用什么对象作为外部类的实例
15.9.3 Choosing the Constructor and its Arguments
该小节规定了外部类实例在参数列表中的位置
15.9.4 Run-time Evaluation of Class Instance Creation Expressions
该小节规定了上面提到的空指针检查的行为:
Java Language Specification, 3rd Edition 写道
At run time, evaluation of a class instance creation expression is as follows.
First, if the class instance creation expression is a qualified class instance creation expression, the qualifying primary expression is evaluated. If the qualifying expression evaluates to null, a NullPointerException is raised, and the class instance creation expression completes abruptly. If the qualifying expression completes abruptly, the class instance creation expression completes abruptly for the same reason.
First, if the class instance creation expression is a qualified class instance creation expression, the qualifying primary expression is evaluated. If the qualifying expression evaluates to null, a NullPointerException is raised, and the class instance creation expression completes abruptly. If the qualifying expression completes abruptly, the class instance creation expression completes abruptly for the same reason.
规范里明确了“要抛出NullPointerException”的行为,至于是如何实现null检查的则没规定,可以由实现自由发挥。用普通的if...else来做这个检查当然也可以,只不过生成的字节码就比调用getClass()的办法更长一些。
====================================================================
无独有偶,ECJ(Eclipse Compiler for Java)在编译这种代码的时候同样会生成对getClass()方法的调用:
public static void main(java.lang.String[]); Code: Stack=3, Locals=2, Args_size=1 0: new #1; //class Outer 3: dup 4: invokespecial #13; //Method "<init>":()V 7: astore_1 8: new #14; //class Outer$Inner 11: aload_1 12: dup 13: invokevirtual #16; //Method java/lang/Object.getClass:()Ljava/lang/Class; 16: pop 17: invokespeci8al #20; //Method Outer$Inner."<init>":(LOuter;)V 20: return LineNumberTable: line 4: 0 line 5: 8 line 6: 20
这当然不是偶然,因为getClass()方法是在Object上声明的(因此所有对象上必然存在),而且是final的(保证了它有确定的行为),而且运行开销比较低。
同样是Object上声明的方法,toString()、hashCode()之类其实也可以用,但它们都不是final的,有潜在可能性会引发较大的运行开销;这么分析一圈下来,Object上最好用的就剩下getClass()了。
发表评论
-
The Prehistory of Java, HotSpot and Train
2014-06-02 08:18 0http://cs.gmu.edu/cne/itcore/vi ... -
MSJVM and Sun 1.0.x/1.1.x
2014-05-20 18:50 0当年的survey paper: http://www.sym ... -
Sun JDK1.4.2_28有TieredCompilation
2014-05-12 08:48 0原来以前Sun的JDK 1.4.2 update 28就已经有 ... -
IBM JVM notes (2014 ver)
2014-05-11 07:16 0Sovereign JIT http://publib.bou ... -
class data sharing by Apple
2014-03-28 05:17 0class data sharing is implement ... -
Java 8与静态工具类
2014-03-19 08:43 16293以前要在Java里实现所谓“静态工具类”(static uti ... -
Java 8的default method与method resolution
2014-03-19 02:23 10465先看看下面这个代码例子, interface IFoo { ... -
HotSpot Server VM与Server Class Machine
2014-02-18 13:21 0HotSpot VM历来有Client VM与Server V ... -
Java 8的lambda表达式在OpenJDK8中的实现
2014-02-04 12:08 0三月份JDK8就要发布首发了,现在JDK8 release c ... -
GC stack map与deopt stack map的异同
2014-01-08 09:56 0两者之间不并存在包含关系。它们有交集,但也各自有特别的地方。 ... -
HotSpot Server Compiler与data-flow analysis
2014-01-07 17:41 0http://en.wikipedia.org/wiki/Da ... -
基于LLVM实现VM的JIT的一些痛点
2014-01-07 17:25 0同事Philip Reames Sanjoy Das http ... -
《自制编程语言》的一些笔记
2013-11-24 00:20 0http://kmaebashi.com/programmer ... -
字符串的一般封装方式的内存布局 (1): 元数据与字符串内容,整体还是分离?
2013-11-07 17:44 22408(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局
2013-11-01 12:55 0(Disclaimer:未经许可请 ... -
关于string,内存布局,C++ std::string,CoW
2013-10-30 20:45 0(Disclaimer:未经许可请 ... -
对C语义的for循环的基本代码生成模式
2013-10-19 23:12 21884之前有同学在做龙书(第二版)题目,做到8.4的练习,跟我对答案 ... -
Java的instanceof是如何实现的
2013-09-22 16:57 0Java语言规范,Java SE 7版 http://docs ... -
oop、klass、handle的关系
2013-07-30 17:34 0oopDesc及其子类的实例 oop : oopDesc* ... -
Nashorn各种笔记
2013-07-15 17:03 0http://bits.netbeans.org/netbea ...
相关推荐
- 匿名内部类是在创建对象时直接定义的一个内部类,通常用于实现接口或继承类。 **10. This与this()的区别** - `this`: 表示当前对象的引用。 - `this()`/`super()`: 用于在构造函数中调用其他构造函数。 **11....
通过反射,你可以获取类的信息(如字段、方法等),并能够动态地创建对象和调用方法。虽然反射非常灵活,但它也存在一定的局限性和潜在风险,比如可能会破坏封装性和降低性能。 ### Class 类的作用与获取方式 ####...
13. **编译时多态和运行时多态**:多态性体现在方法重载和方法覆盖上,编译时多态通过方法签名确定,运行时多态通过对象实际类型决定。 14. **equals()方法**:默认比较对象引用,重写后可比较对象内容。 15. **...
- **重写示例**:当打印一个对象时,系统会默认调用`toString()`方法,未重写前输出的是内存地址,重写后可自定义输出内容。 - **`equals()`方法**: - **源码**:`public boolean equals(Object o) { return ...
当开发者使用`javac`编译`.java`源文件时,会产生`.class`字节码文件,这些字节码可以在任何支持JVM的平台上运行,实现了"一次编写,到处运行"的理念。 JVM内部的运作机制包括类加载器(ClassLoader),它负责加载`...
通过编写简单的程序,调用`new A().getClass().getClassLoader().toString()`可以查看当前类的加载器,从而直观地了解类加载的路径。理解这一机制对于解决“找不到类文件”的错误至关重要。 再者,JDK(Java ...
Java反射机制允许程序在运行时检查类的结构,包括类的属性、方法和构造器等信息,并能动态地创建对象和调用其方法。这种能力使得开发者能够在不预先知道具体类名的情况下,对类进行操作,增强了代码的通用性和可...
了解了反射机制,我们就可以在运行时动态创建对象,调用私有方法,甚至修改私有字段,这在许多场景下都非常有用,比如序列化、框架开发、单元测试等。然而,反射也带来了一些负面影响,如性能损失、安全性问题和增加...
String str="i"创建了一个字符串常量池中的对象,而String str=new String("i")则在堆中创建了一个新的对象。 9. **字符串反转**: 可以使用StringBuilder或StringBuffer的reverse()方法来反转字符串。 10. **...
通过`new A().getClass().getClassLoader().toString()`可以获取当前类的加载器信息。如果返回`null`,表示该类是由Boot ClassLoader加载的。 #### 3. Java开发工具链简介 选择合适的开发工具可以极大地提高开发...
- `finalize`:Object类中的一个方法,当垃圾回收机制准备回收对象时,可能会调用此方法进行清理工作,但这不是保证的。 6. **静态与实例变量**:静态变量属于类,所有类实例共享,可通过类名直接访问;实例变量...