- 浏览: 369363 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
surpassno:
很不错,学习了
一个对象占用多少字节? -
ysyzww:
你这么牛逼,你父母知道吗
maven使用技巧 -
妖人不要跑:
JDK中反序列化对象的过程(ObjectInputStream#readObject) -
lanhz:
谢楼主,构建成功了
Mac OSX 10.9 上build openjdk8和openjdk7 -
zqb666kkk:
通过了吗 ?????
淘宝北京专场java面试题(2011-12-31)
面向对象三大特征封装、继承和多态,此处我们一般都知道方法的多态性,覆盖和重载。但是字段呢?当然根据定义,跟字段无关,也就是不能覆盖?先看一个小程序:
package com.yymt.jvm.method.dispatch; public class DispatchTest { public static void main(String[] args) { Base b = new Sub(); System.out.println(b.x); } } class Base { int x = 10; public Base() { this.printMessage(); x = 20; } public void printMessage() { System.out.println("Base.x = " + x); } } class Sub extends Base { int x = 30; public Sub() { this.printMessage(); x = 40; } public void printMessage() { System.out.println("Sub.x = " + x); } }
输出是什么?也许你已经见到过类似题目了,答案也很明显。但是为什么是这样呢?还是先输出下答案吧:
Sub.x = 0 Sub.x = 30 20
来仔细分析下这个过程,首先从main方法入手:
Base b = new Sub();
此处创建一个Sub实例,会调用Sub的构造函数:
public Sub() { this.printMessage(); x = 40; }
但是根据jvm规范,构造函数会被编译成名称为<init>的方法。构造函数如果没有显式调用this(xxx)或者super(xxx),即当前类别的构造函数或者超类构造函数,编译时调用超类的无参构造函数作为<init>方法第一条指令,也就是说此处会调用Base的无参构造函数:
public Base() { this.printMessage(); x = 20; }
注意,作为构造函数第一条语句。而类的实例字段直接在声明的时候初始化的话,或者是代码块,会被收集起来放到<init>方法里,按照语句赋值顺序放进来,当然也是放在超类构造函数之后的。后续才是构造函数里边的代码内容。所以此处相当于:
public Sub() { super();//Base() x = 30; this.printMessage(); x = 40; }
顺便看下字节码,跟上边源码一致吧?
// Method descriptor #8 ()V // Stack: 2, Locals: 1 public Sub(); aload_0 [this] invokespecial com.yymt.jvm.method.dispatch.Base() [10] aload_0 [this] bipush 30 putfield com.yymt.jvm.method.dispatch.Sub.x : int [12] aload_0 [this] invokevirtual com.yymt.jvm.method.dispatch.Sub.printMessage() : void [14] aload_0 [this] bipush 40 putfield com.yymt.jvm.method.dispatch.Sub.x : int [12] return Line numbers: [pc: 0, line: 26] [pc: 4, line: 24] [pc: 10, line: 27] [pc: 14, line: 28] [pc: 20, line: 29] Local variable table: [pc: 0, pc: 21] local: this index: 0 type: com.yymt.jvm.method.dispatch.Sub
调用哪个方法?
但是由于java的方法是动态分派的,会根据运行时实例类型来决定调用谁的方法,此处this为Sub的实例,我们在new Sub()嘛,所以super()中调用的是Sub的printMessage方法,此处输出就是Sub.x = ?。
调用哪个字段?
继续分析Base的构造函数,其等价于:
public Base() { super();//Object() x = 10; this.printMessage(); x = 20; }
来看下Base()的字节码,上边源码一致的:
// Method descriptor #8 ()V // Stack: 2, Locals: 1 public Base(); aload_0 [this] invokespecial java.lang.Object() [10] aload_0 [this] bipush 10 putfield com.yymt.jvm.method.dispatch.Base.x : int [12] aload_0 [this] invokevirtual com.yymt.jvm.method.dispatch.Base.printMessage() : void [14] aload_0 [this] bipush 20 putfield com.yymt.jvm.method.dispatch.Base.x : int [12] return Line numbers: [pc: 0, line: 13] [pc: 4, line: 11] [pc: 10, line: 14] [pc: 14, line: 15] [pc: 20, line: 16] Local variable table: [pc: 0, pc: 21] local: this index: 0 type: com.yymt.jvm.method.dispatch.Base
在此处给x赋值为10,然后this.printMessage()是不是就是输出Sub.x = 10呢?慢着慢着,为什么会是Sub.x = 10,如果我根据方法调用的逻辑来推导,应该是Sub.x = Sub实例中x此刻的值啊!此处推导用的是方法的动态分派,这种方法适用于字段么??我们换个角度,用静态派发来分析下,即编译时决定调用的版本!我们已经知道,重载是静态派发,重写是动态派发的。好吧,我们根据静态类型来分析(Base的构造函数中)this.printMessage(),此处this是Sub的实例。上边我们已经分析,此处(Base的构造函数中)this.printMessage调用的是Sub的printMessage方法:
public void printMessage() { System.out.println("Sub.x = " + x); }
那编译时,x就是Sub的x的,因为在Sub方法中调用的嘛!看字节码:
// Method descriptor #8 ()V // Stack: 4, Locals: 1 public void printMessage(); getstatic java.lang.System.out : java.io.PrintStream [21] new java.lang.StringBuilder [27] dup ldc <String "Sub.x = "> [29] invokespecial java.lang.StringBuilder(java.lang.String) [31] aload_0 [this] getfield com.yymt.jvm.method.dispatch.Sub.x : int [12] invokevirtual java.lang.StringBuilder.append(int) : java.lang.StringBuilder [34] invokevirtual java.lang.StringBuilder.toString() : java.lang.String [38] invokevirtual java.io.PrintStream.println(java.lang.String) : void [42] return Line numbers: [pc: 0, line: 32] [pc: 25, line: 33] Local variable table: [pc: 0, pc: 26] local: this index: 0 type: com.yymt.jvm.method.dispatch.Sub
忽略掉编译时把String相加转换成了StringBuilder,只看后续对x的调用:
aload_0 [this] getfield com.yymt.jvm.method.dispatch.Sub.x : int [12]
看到了,编译器决定调用的是Sub.x,那运行期就是Sub.x了么?如果是静态分派,的确已经是了,如果是动态分派,aload_0的this也是Sub,所以还是Sub.x?乱了乱了!当然,派发本身就是决定方法的调用版本,定义上就没有把决定字段的调用版本归结到派发里!用派发来推导本身就没有理论上的支撑的。
我们还是来分析下getfield指令吧,这个比较靠谱,但是,但是查了一下,getField指令只是从获取类的实例域,放入栈中,完全没有像方法调用一样,还分为invokevirtual是运行期根据类型来决定,invokespecial是调用私有、构造、超类方法,invokestatic、invokeinterface这种分类,也没有说明白是对象字段在内存中存放时候是不存在像方法一样只有一个表项,实际测试来看,超类和子类的同名同类型实例字段是存储了两份,根据静态类型的不同,调用是不同的。getfield也是根据指令后边操作数指向的常量池中实际的类型类决定调用哪一个的。此处推导getfield是根据静态类型决定字段调用!实际上,方法的静态分派也就是编译期决定方法的调用版本,跟编译期决定字段的调用版本意思上是一致的,只是决定方法的调用才叫分派。
所以此处就是调用Sub.x了。在Sub构造函数中由于x的赋值在super之后,所以调用super的时候x还是默认值,即为0。所以Base里边this.printMessage()时B的实例x=0。
最后的b.x,按照上边推断,是根据字段的静态类型来决定调用的,这条语句运行完后,x是Base中x的值,所以此处就是20了!
System.out.println(b.x);
aload_1 [b] getfield com.yymt.jvm.method.dispatch.Base.x : int [25]
发表评论
-
springboot程序错误排查
2016-12-12 10:37 11512.0.0版本的springboot程序,在eclipse中 ... -
debug Java进程的debug参数
2016-08-25 21:13 1552前几天给java应用设置debug参数,发现有两个参数:- ... -
Java NIO Socket通信需要考虑的问题(持续更新)
2016-04-27 19:57 281. NIO 多个线程同时往 ... -
Fastjson反序列化泛型类型时候的一个问题
2015-01-21 15:34 32204import static org.junit.Asser ... -
HTTP 50X code实例
2014-10-31 19:24 01、500 Internal Server Err ... -
一次Direct buffer memory引发的OutOfMemoryError问题排查
2014-10-28 17:22 0留坑位 -
netty3.6.2中写数据的过程,以及写数据写不出去后怎么处理
2014-08-11 17:37 3088netty写数据的时候,会先放到一个缓存队 ... -
在用Netty 3.6.2发数据,发现内核缓冲区满的时候.....
2014-08-11 16:06 2143用nettys收发网络数据的时候,一般不会注 ... -
JDK中反序列化对象的过程(ObjectInputStream#readObject)
2014-06-10 20:10 4093此处,对象描述信息即ObjectStre ... -
jvm运行期打印汇编信息
2014-04-09 23:00 3100如果只在jvm参数中加入-XX:+Prin ... -
Mac OSX 10.9 上build openjdk8和openjdk7
2014-03-29 18:29 14179先分享下自己build出来的fastdeb ... -
内存充足情况下应用一直CMS GC的问题分析
2014-03-26 22:39 0前几天日常上线发布后,收到大量的CMS GC ... -
查看java对象在内存中的布局
2014-03-20 22:39 12961接着上篇《一个对象占用多少字节?》中遇到的 ... -
一个对象占用多少字节?
2014-03-18 21:56 34840老早之前写过一篇博客,是关于一个Integ ... -
maven使用技巧
2014-03-18 15:34 38541、pom打jar包的时候设置MANIFEST.MF的ke ... -
cpu字长、操作系统字长和jvm中各数据类型占用的字节数关系
2014-03-16 02:05 4653cpu字长是指cpu同时参与运算的二进制位 ... -
比反射更高效的修改字段值的方法
2014-03-13 20:49 1316开发过程中,不少情况下都会遇到需要通过反射 ... -
cache line对内存访问的影响
2014-03-12 20:48 1316cache line对内存访问的影响很早就 ... -
Java Web应用Web层异步化应该考虑的问题
2014-01-25 17:45 5772之前做了一 ... -
jvisualvm jmx方式远程监控tomcat
2013-10-10 20:38 19371、如果用jmx方式监控,不需运行服务器上的jstatd进 ...
相关推荐
完美解决distinct中使用多个字段的方法,完美解决distinct中使用多个字段的方法完美解决distinct中使用多个字段的方法完美解决distinct中使用多个字段的方法完美解决distinct中使用多个字段的方法
微信小程序加密字段解密工具代码,真的没搞懂微信怎么想的,微信退款,公众号消息,小程序,微信支付的加密解密方式全都不一样,每一个都要单独调试,简直要死人,那我就调试好一个就传一个上来
关于字段增强关于字段增强关于字段增强关于字段增强关于字段增强关于字段增强
sql行列转换、一个字段包含另一个字段.sql
要求:查询一个字段的数据,将每个数据拆分,取第一个字符,将第一个字符遍历出来,替换到另一个字段里面
SQL Server连接字段的方法 SQL Server连接字段的方法
主要描述了SAP标准报表要如何增加自定义字段的方法介绍
sql语句:按照某一个字段进行去重后获取全部字段。
最近在做一个小程序的demo。由于不向后台请求数据,所以就涉及到对本地数据的操作,也遇到了一些坑,本文就以数组的增删改查为例,给新手分享一些经验。 首先这是原始数据,json的数组。 我们尝试对改数据进行...
oracle实现多字段匹配一个关键字查询的两种方法
小程序名称为心邮,这是一款发布日志和心情的小程序。用来倾诉烦恼、分享快乐。 使用步骤: 第一步:创建项目,记得填入你自己的AppId(必须填入AppId,不然无法调用wx.login())。 第二步:下载该demo。 第三步:在...
一个不错的处理shp小程序, 实现shp加载及后期渲染,字段分析.
这个示例展示了如何使用C#类来表示一个人,并使用字段、属性、构造函数和方法来操作和展示人的相关信息。在Person类中,有三个字段和一个属性: name字段用于存储人的姓名。 age字段是一个私有字段,用于存储人的...
一个小程序,由于数据库服务器容量有限,若长时间从拥有大量数据的数据库中读数据,无疑会给服务器带来很大负担,所以,应先将一个数据库中经常用到的数据统计到另一个数据库或另一个表中,以后可以查询这些就可以了...
1、加字段: alter table 表名 ADD 字段名 类型; eg: alter table sys_cwzd ADD SCCLLJ VARCHAR2(50); 2、加备注: comment on column 表名.字段名 is '备注名'; eg: comment on column sys_cwzd.SCCLLJ is ...
你有过这样的烦恼吗?你曾经因为这而停住你前进的脚步吗?那快点来看吧。
但是这会产生一个小问题(真不知道到底是个问题不),新添加的字段只会显示在最后面,可能让用户看着非常不爽,由于他们在实际工作中习惯于某个字段的位置,这就出现了调整要素类字段显示顺序的要求。
java实体类字段自定义-数据库字段和程序实体类属性不一致解决方案.docx
EntityFramework 多关键词联合搜索一个字段。一个关键词数组搜索一个字段。该字段下包含任意一个或多个关键词则被筛选出来。通过动态拼接 Lamdba 表达式实现。
采购订单BAPI增强数值型字段转换方法