Java作为OOP语言,抽象性不言而喻。如果需要深入了解Java语言的实现机制,则不得不对Java语言中基础的概念有清晰的了解。今天是我在cnblog上写博客的第一天,希望今天的博客可以是我成为未来"大牛"跨出的第一步。

  面向对象语言中,对象概念其实挺抽象的,对于初学者甚至有开发经验的同志来说都不太容易弄明白。最近看到这篇牛人写的文章,觉得蛮受益的,和大家共同分享吧。翻译有些拙劣,"大牛"请忽略我直接看原文,嘻嘻~。

  原文出处链接:http://www.programcreek.com/2011/11/what-do-java-objects-look-like-in-memory/

  We know functions are implemented in memory as a stack of activation records. And we know Java methods are implemented as a stack of frames in JVM Stack and Java objects are allocated in Heap.

  函数在内存中通过一堆"活动记录"(activation record,活动记录也叫栈帧)实现。我们也知道Java方法在虚拟机栈中通过一堆"栈帧"(stack frame)实现,Java对象在"堆"(Heap)中分配内存空间。

COMMENT1:

  1)Java中funtion和procedure统称为method。一般来说function和procedure是有区别的(当然很多编程书上是混用的,我也是无语==!)

  funtion -- 无返回值(void或构造函数那样返回值类型都没写的)的method;     procedure -- 有返回值的method

  2)栈帧(stack frame)也称活动记录(activation record)。method在被调用(called)时会将方法区的方法压入栈帧,栈帧中保存有局部变量,返回值类型等信息。具体内容本文不深究。

  How do Java objects look like in heap? Once an object is laid out in memory, it's just a series of bytes.

  Java对象在"堆"中到底长得啥样呢?一旦对象被放到内存中,此时它仅仅就是一系列的字节。

  Then how do we know where to look to find a particular field? Keep an internal table inside the compiler containing the offsets of each field.

  那么我们是如何知道到哪里去查看、寻找这样一个特别的字段(field)的呢?在编译器内部保存有一张包含每一个字段偏移量的表。

COMMENT2:

  1)field有的译作"域"、"字段",其实都是一个意思。都是指类的成员变量(包括static成员变量,只不过static存放在方法区,属于类,所有类对象共享)

  2)对象成员变量自动寻址:编译器内部会保存一张虚拟表(Virtual Table or called Vtable),包括每个对象成员变量相对于对象空间首地址(第一个成员变量)的地址偏移量,这

样就可以轻松地访问对象的成员变量了。

  Here is an example of an object layout for class "Base"(B). This class does not have any method, how methods are laid out in memory is in the nextsection.

  举个Base类实例对象的例子。这个类没有任何的方法,它的方法如何在内存中被安排放置将在下一部分中解释。

  If we have another class "Derived"(D) extending this "Base" class. The memory layout would look like the following:

  如果我们有另一个Derived类继承了Base类。内存布局情况如下:

  Child object has the same memory layout as parent objects, except that it needs more space to place the newly added fields. The benefit of this layout is that a pointer of type B pointing at a D object still sees the B object at the beginning. Therefore, operations done on a D object through the B reference guaranteed to be safe, and there is no need to check what B points at dynamically.

  子类对象除了需要更多空间去放置新增的字段外,其余的和父类对象有相同类型的内存布局。这样布局的好处就是:当有一个Base类引用指向Derived类对象时,引用仍然能访问内存区域开始的Base类对象。因此,所有通过Base类引用对Derived类对象上的操作是确保安全的,也就没有必要动态地去检查Base类所指向的内容。

COMMENT3:

  1) pointer和reference

  其实有时我在看Java英文文献的时候也是傻傻分不清到底在说那个。如果是C++那pointer和reference(引用和指针表面上是两码事,引用在底层也是通过指针实现的)明显就是两个概念。而在Java引用和C++引用表面上根本就是不同的概念,Java中的引用更像C++的指针变量,只不过是阉割版的C++指针变量(Java引用和C++的type * const ptr等价,即ptr的值不能改变,但是ptr指向的内容可以改变)。

  2) 向上造型(也称"上溯造型")语法现象

  The benefit of this layout is that a pointer of type B pointing at a D object still sees the B object at the beginning.

  B类(父类)的引用在指向D类对象后,仍然能够访问B对象。still sees the B object at the beginning 就说明了Object B 和Object D 开头部分内存是相同的,Object D开头的内存可以认为就是Object B。

  通过上述的分析,我们看下面一道笔试题:

 import static java.lang.System.out;

 class SupClass {
int x = 10;
void show() {
System.out.println("SupClass");
}
} class SubClass extends SupClass{
int x = 20;
void show() {
System.out.println("SubClass");
}
} public class Test {
public static void main(String[] args) {
SupClass sup = new SubClass();
out.println(sup.x);
}
}

  SupClass sup = new SubClass(); 父类(SupClass)引用指向子类(SubClass)对象,由于父类引用只能访问子类中从父类继承的成分,因此x = 20是不能够被sup访问的。

答案就是10,而不是20。

  3)为什么父类引用可以指向子类对象而编译器不报错?

  Therefore, operations done on a D object through the B reference guaranteed to be safe, and there is no need to check what B points at dynamically.

  因此,所有通过Base类引用堆D对象的操作确保是安全的,不需要动态地检查Base类到底引用了啥。

  这句话其实可以很好地解释为什么这种操作编译器不报错。其实仔细理解起来是这么回事,由于子类从父类继承的那部分结构与父类相同(通过super关键字,此时子类对象中多了父类的那部分内容)。父类引用可以通过子类的对象来访问子类从父类继承来的那部分,因而编译器不会报错。而且这种访问形式是安全的,既不能改变父类的内容,也不会去访问子类多出来的那部分。

  4)深入堆内存

  堆内存是什么?堆内存就是物理机上的一段虚拟内存(Virtual Memory)。哪啥叫虚拟内存?虚拟内存实际是硬盘的东西,只是用部分硬盘的储存容量来作为JVM的内存,显然这个内存是假的,和物理机的内存是两码事。当然由于JVM(Java Virtual Machine)就是一个虚拟计算机,因此不光是堆,其实JVM的所有内存(包括Java栈等)都是物理机中的虚拟内存,即实际硬盘中的储存空间。

  JVM堆内存实际上是链表(Linked List)这种数据结构维护的,因此堆内存中的连续内存往往是指逻辑连续,物理堆内存上是不连续。

  Following the same logic, the method can be put at the beginning of the objects.

  根据相同的逻辑,方法可以被放在对象的开头。

  

  However, this
approach is not efficient. If a class has many methods(e.g. M), then each
object must have O(M) pointers set. In addition, each object needs to have
space for O(M) pointers. Those make creating objects slower and objects bigger.

  然而,此方法效率并不高。如果一个类有很多的方法,那么每个对象必须有一个指针集合(就是指很多指针变量而已)。除此之外,每个对象需要有空间去储存这些指针变量。这些会导致创建对象变得更慢,同时对象空间变得更大。

COMMENT4:

  1)理论上我们在创建类对象时将方法写在对象内存的开头,但是相比有限的对象成员变量来说,对象的方法可能有很多,如果每次创建一个对象就需要许多指向方法的指针,那么无疑堆内存将会变得很大,同时在创建对象时会变得很慢(对象创建是从首地址开始一个一个创建的)。

  2)对象不包括方法,而是包括了指向方法的指针变量。方法储存在方法区中。在这种模型(JVM不使用这种模型)中,对象在调用非静态方法时,堆中的指针变量在需要的时候通过堆中的指针变量访问方法区中相应的方法,并将其放入Java栈中,对应一个栈帧。

  The optimization
approach is to create a virtual function table
(or vtable) which is an array of
pointers to the member function
implementations for a particular class. Create
a single instance of the vtable for each class and make each object store a
pointer to the vtable
.

  优化方法是创建一个特定类虚函数表,这个虚函数表是指向成员函数实现的指针数组。为每个类创建虚表的单一实例,同时让每个对象储存一个指向虚函数表的指针。

  

COMMENT5:

  1)JVM实际使用就是上面第二种模型(方法区在存放方法的同时时,也会存放一份类虚函数表,虚函数表放方法代码)。

  Code for Base.sayHi指的是方法区中的方法代码信息;sayHi指的是堆中创建的指向方法区方法的方法指针;Vtable*指的是方法指针数组集合,用于创建动态指针数组(这些动态指针指向栈中的具体栈帧),被所有类对象共享(储存在方法区)。

  2)对象可以访问堆中常量池内容,说明啥?是不是说明堆中还存在一个指针指向常量池。而且访问常量池的内容仅限某个类的内容。因此可以看出,实际上堆中对象还存有.class句柄指针。当然这个是我猜的,没有啥依据。

  3)我想,看到这你我都明白啥是真正意义上的对象了。对象=.class信息句柄指针(能指向方法区常量池)+虚函数表指针变量(指向方法区的方法)+对象成员变量(数据)

  4)类的方法储存在方法区,被所有类的对象所共享。具体对象的虚函数表内的方法指针被创建时,会将方法区中的方法放入Java栈,这个具体的指针和栈帧绑定。这样每个类对象通过虚函数表解决了必须调用众多方法的问题,创建对象速度比第一种模型快,对象所需的堆内存空间也比第一种要小。

  通过上面部分讨论,可以解决以下这几道面试题:

 public class TestClass {
public static void main(String[] args) {
SupClass sup = new SubClass();
sup.show1();
sup.show2();
}
} class SupClass {
int x = 10;
public void show1() {
System.out.println("SupClass");
}
public void show2() {
System.out.println(x);
}
} class SubClass extends SupClass{
int x = 20;
public void show1() {
System.out.println("SubClass");
}
public void show2() {
System.out.println(x);
}
}

  试题的答案是:

  SubClass

  20

  分析:本题考查的是完全是方法的覆盖(重写override)知识点。

  1)在同一个类中,如果有两个方法的方法签名相同,编译器会报错。从计算机思维来说,机器处理的必须是明确的东西,不能是不确定量,如果出现不确定量,机器会报错,因为你把它搞晕了,它不知道该选择那个。

  2)在父子类关系这两个类中,子类方法签名和父类方法签名相同,属于方法覆盖。那么为什么父类引用访问子类覆盖父类的方法时只能访问子类重写版本而不能是父类版本,而且也不会出现问题?其实,在方法区中子类并没有包含父类的方法(包括构造方法),而是拥有各自代码中所写的方法。使用父类引用指向子类对象时,如果子类覆盖父类的方法,那么就会在子类方法区寻找这个方法,找到就将其放入栈中;如果找不到,由于有extends关键字,此时就会通过super指针指向父类方法区,在父类方法区寻找相应的方法。

  3)父类哪些东西不能被子类继承?

  Java继承机制中,超类的方法(包括构造方法)不能被派生类继承,而是通过调用机制实现。具体的内容会在后续的文章中讨论。

 public class TestClass {
public static void main(String[] args) {
SupClass sup = new SubClass();
sup.show1();
sup.show2();
}
} class SupClass {
int x = 10;
public void show1() {
System.out.println("SupClass");
}
public void show2() {
System.out.println(x);
}
} class SubClass extends SupClass{
int x = 20;
public void show1() {
System.out.println("SubClass");
}
public void show2(int a) {
System.out.println(x);
}
}

  试题的答案是:

  SubClass

  10

  分析:本题是上题的变式,SupClass和SubClass都有2个方法,但是在子类中父类show1()被覆盖,而父类的show2()方法未被覆盖。在方法区中父类方法是父类方法,子类方法是子类方法,都是实际写的方法中哪些代码,子类方法区空间中并没有父类的方法。但在具体对象创建时,子类的虚函数表却拥有指向父类方法区方法的指针。当子类重写父类方法后,这张表其实是更新的,也就是说表中父类的方法指针被子类方法指针覆盖了。 

  This is the optimized approach.

  上面的就是优化后的方法。

References:
1. Stanford Compilers Lectures
2. JVM

学习感悟:

1)如果国内的博客没有你需要的答案,或者不满意,最好能够去国外的社区走走看看,当然Java官方文档是一切的基础吧。

2)一些专业名词确实需要积累,比如activation record我一开始没有翻译准确,后来是通过浏览器查询才翻译准确的。

3)不要怕说错,就像可能我这篇文章有许多错的。重在和别人交流,在交流中提升自己吧。

4)有些概念可能没有表述清晰,需要进一步加强自己的语言表述能力。毕竟任何东西,自己懂和让别人懂事两码事,两种能力。

Java Object Model(一)的更多相关文章

  1. JAVA读取XML,JAVA读取XML文档,JAVA解析XML文档,JAVA与XML,XML文档解析(Document Object Model, DOM)

    使用Document Object Model, DOM解析XML文档 也可参考我的新浪博客:http://blog.sina.com.cn/s/blog_43ac5543010190w3.html ...

  2. Page Object Model (Selenium, Python)

    时间 2015-06-15 00:11:56  Qxf2 blog 原文  http://qxf2.com/blog/page-object-model-selenium-python/ 主题 Sel ...

  3. selenium page object model

    Page Object Model (POM) & Page Factory in Selenium: Ultimate Guide 来源:http://www.guru99.com/page ...

  4. POM(project Object Model) Maven包管理依赖 pom.xml文件

    什么是POM POM全称为“Project Object Model”,意思是工程对象模型.Maven工程使用pom.xml来指定工程配置信息,和其他文本信息.该配置文件以xml为格式,使用xml语法 ...

  5. BOM(Browser Object Model) 浏览器对象模型

    JavaScript 实现是由 3 个部分组成:核心(ECMAScript),文档对象模型(DOM),浏览器对象模型(BOM) BOM(Browser Object Model) 浏览器对象模型BOM ...

  6. 有关BOM(Browser Object Model)的内容

    包括: BOM概述 BOM模型 Window对象(常用属性和方法,窗口的打开,窗口的关闭,模态对话框,定时器) Navigator对象(遍历navigator对象的所有属性,Navigator 对象集 ...

  7. Selenium的PO模式(Page Object Model)[python版]

     Page Object Model 简称POM  普通的测试用例代码: .... #测试用例 def test_login_mail(self): driver = self.driver driv ...

  8. 在C#开发中如何使用Client Object Model客户端代码获得SharePoint 网站、列表的权限情况

    自从人类学会了使用火,烤制的方式替代了人类的消化系统部分功能,从此人类的消化系统更加简单,加速了人脑的进化:自从SharePoint 2010开始有了Client Side Object Model ...

  9. Selenium的PO模式(Page Object Model)|(Selenium Webdriver For Python)

            研究Selenium + python 自动化测试有近两个月了,不能说非常熟练,起码对selenium自动化的执行有了深入的认识. 从最初无结构的代码,到类的使用,方法封装,从原始函数 ...

随机推荐

  1. Word直接发表博客测试

    这是我现在使用的VSCode! 这是另一段测试内容!

  2. python接口测试,第三方包xlrd和xlutils,怎么安装

    第1个:xlrd的下载地址:https://pypi.python.org/pypi/xlrd/0.9.2 安装过程:下载后解压文件夹,使用cd命令行进入该文件夹后,用命令:python setup. ...

  3. 《Java多线程编程核心技术》读后感(十一)

    方法join的使用 在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前结束.这时,如果主线程想等待子线程执行完之后再结束,比如子线程处理一个数据,主 ...

  4. Java异常控制机制和异常处理原则【转】

    原文:https://www.jianshu.com/p/15872cba211d Java异常控制机制又被称为“违例控制机制”. 捕获程序错误最理想的时机是在编译阶段,这样可以彻底避免错误的代码运行 ...

  5. 基于Go实现的秒杀系统

    这是基于Go语言的一个秒杀系统,这个系统分三层,接入层.逻辑层.管理层.项目源码:https://github.com/BlueSimle/SecKill 系统架构图 秒杀接入层 从Etcd中加载秒杀 ...

  6. c#实现最简快速排序,你绝对可以看懂

    原创文章,转载请注明出处 算法对于程序员的重要性不言而喻,今天我和大家分享算法中的一个基础算法,快速排序.作为一名程序员,相信大家都不陌生,但是要大家徒手一次性写出来,我估计还是有难度的.那么废话不多 ...

  7. 51nod1202【DP-树状数组维护】

    思路: DP[i]代表从1 到 i 以 a[i] 为末尾的子序列个数,dp[i]=dp[i]+dp[j](a[i]!=a[j]) +1 利用树状数组维护以值 a[i] 结尾的子序列个数. #inclu ...

  8. memcached 扩展安装(windows)

    在脚本之家里下载扩展压缩包 https://www.jb51.net/softs/392873.html 安装扩展前得先安装memcached并启动 下载完扩展压缩包解压并找到对应自己php版本 复制 ...

  9. ue4 简单数据储存

  10. Unity3D 自动添加Fbx Animation Event

    http://blog.csdn.net/aa20274270/article/details/52528449 using UnityEngine; using System.Collections ...