- 浏览: 374626 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
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 11992.0.0版本的springboot程序,在eclipse中 ... -
debug Java进程的debug参数
2016-08-25 21:13 1596前几天给java应用设置debug参数,发现有两个参数:- ... -
Java NIO Socket通信需要考虑的问题(持续更新)
2016-04-27 19:57 281. NIO 多个线程同时往 ... -
Fastjson反序列化泛型类型时候的一个问题
2015-01-21 15:34 32277import 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 3133netty写数据的时候,会先放到一个缓存队 ... -
在用Netty 3.6.2发数据,发现内核缓冲区满的时候.....
2014-08-11 16:06 2211用nettys收发网络数据的时候,一般不会注 ... -
JDK中反序列化对象的过程(ObjectInputStream#readObject)
2014-06-10 20:10 4171此处,对象描述信息即ObjectStre ... -
jvm运行期打印汇编信息
2014-04-09 23:00 3148如果只在jvm参数中加入-XX:+Prin ... -
Mac OSX 10.9 上build openjdk8和openjdk7
2014-03-29 18:29 14276先分享下自己build出来的fastdeb ... -
内存充足情况下应用一直CMS GC的问题分析
2014-03-26 22:39 0前几天日常上线发布后,收到大量的CMS GC ... -
查看java对象在内存中的布局
2014-03-20 22:39 13058接着上篇《一个对象占用多少字节?》中遇到的 ... -
一个对象占用多少字节?
2014-03-18 21:56 35099老早之前写过一篇博客,是关于一个Integ ... -
maven使用技巧
2014-03-18 15:34 39351、pom打jar包的时候设置MANIFEST.MF的ke ... -
cpu字长、操作系统字长和jvm中各数据类型占用的字节数关系
2014-03-16 02:05 4709cpu字长是指cpu同时参与运算的二进制位 ... -
比反射更高效的修改字段值的方法
2014-03-13 20:49 1376开发过程中,不少情况下都会遇到需要通过反射 ... -
cache line对内存访问的影响
2014-03-12 20:48 1374cache line对内存访问的影响很早就 ... -
Java Web应用Web层异步化应该考虑的问题
2014-01-25 17:45 5821之前做了一 ... -
jvisualvm jmx方式远程监控tomcat
2013-10-10 20:38 19771、如果用jmx方式监控,不需运行服务器上的jstatd进 ...
相关推荐
完美解决distinct中使用多个字段的方法,完美解决distinct中使用多个字段的方法完美解决distinct中使用多个字段的方法完美解决distinct中使用多个字段的方法完美解决distinct中使用多个字段的方法
微信小程序加密字段解密工具代码,真的没搞懂微信怎么想的,微信退款,公众号消息,小程序,微信支付的加密解密方式全都不一样,每一个都要单独调试,简直要死人,那我就调试好一个就传一个上来
sql行列转换、一个字段包含另一个字段.sql
在数据库管理过程中,经常会遇到需要对数据进行整理和优化的情况,其中一个常见的需求就是将数据库中的两个字段合并为一个字段。这种操作不仅可以简化数据结构,还能提高数据查询的效率。接下来,我们将详细介绍如何...
### SAP标准报表增加字段的方法详解 #### 一、引言 在SAP系统中,标准报表为用户提供了一种快速获取企业关键数据的方式。然而,在实际业务操作过程中,有时标准报表提供的信息并不能完全满足用户的需求,这时就...
要求:查询一个字段的数据,将每个数据拆分,取第一个字符,将第一个字符遍历出来,替换到另一个字段里面
在Oracle数据库中,有时我们需要对多个字段进行联合搜索,即多字段匹配一个关键字查询。本文将详细介绍两种在Oracle中实现这种查询的方法。 ### 一、使用管道符号(||)连接字段 这种方法通过使用Oracle中的字符串...
在上述代码中,`MyClass`是一个包含一个公共字段`age`、一个私有字段`name`、一个公共属性`Name`以及一个构造函数和一个方法`DisplayInfo`的类。字段是存储数据的地方,而属性提供了对字段的安全访问,通常用get和...
当一个输入字段与特定的功能代码关联时,系统会在DCI(DATA COMMUNICATIONS INPUT)阶段分支到一个函数模块。函数模块的命名规则如下: - 前缀:FIELD_EXIT_ - 中间部分:对应的数据元素名称 - 后缀:_0到_9(可选)...
标题中的“一个不错的处理shp小程序”指的是一个用于处理Shapefile格式数据的程序,这种程序通常用于地理信息系统(GIS)领域。Shapefile是一种常见的矢量数据格式,用于存储地理空间特征,如点、线和多边形。它由多...
例如,实体类中有一个字段名为 "userName",而数据库表中的字段名为 "USER_NAME"。这种情况下,需要实现实体类字段的自定义,以便与数据库字段保持一致。 二、解决方案 解决 Java 实体类字段自定义问题的思路是...
SQLServer 中将一个字段的多个记录值合并到一行显示的实现方法 SQL Server 是一种关系型数据库管理系统,具有强大的数据处理能力和存储能力。在实际应用中,我们经常需要将一个字段的多个记录值合并到一行显示,以...
该程序使用 Java 的图形用户界面(GUI)组件来创建一个带有按钮和文本字段的计算器界面,用户可以通过点击按钮来输入数字和运算符,最后显示计算结果。 Java 图形用户界面(GUI)组件 在本程序中,我们使用了 Java...
* 请求分派只能将请求转发给同一个 Web 应用程序中的其他组件,而重定向不仅可以定向到当前应用程序中的其他资源,也可以重定向到其他站点的资源上。 * 重定向的访问过程结束后,浏览器地址栏中显示的 URL 会发生...
在Android应用开发中,Dalvik Executable (DEX) 文件是一个至关重要的组成部分,它包含了应用程序的所有字节码、类定义、字段和方法。本教程将深入探讨DEX文件的字段和方法定义,并通过Python脚本进行解析。我们将...
总的来说,“微信小程序项目实例——今日美食”是一个集前端技术、后端数据管理、用户体验设计于一体的综合项目,对于学习微信小程序开发和移动应用设计的人员来说,是一个很好的实践案例。通过分析和实施这个项目,...
在这个例子中,首先通过WITH子句创建了一个名为`temp_table`的临时表,该表中包含了一个由多个字段拼接而成的新字段`combined_fields`。接下来,在主查询中对该临时表进行查询,实现了对多个字段同时进行关键字匹配...
Oracle 表字段或是视图字段添加备注方法 ...添加备注信息到表字段或视图字段中是一个非常有用的方法,可以提高数据库的可读性和可维护性。但是,需要注意权限问题和添加备注信息的时间和精力成本。
### C#中的字段、属性与方法的区别 #### 字段 字段是类的组成部分之一,用于存储数据。在C#中,通常使用`private`关键字来声明字段,这意味着这些字段只能在定义它们的类内部访问。这种做法有助于隐藏类的内部状态...