今天这个问题就比较卷了,也是一位面试被虐得体无完肤的小伙伴提供的。放心哈,我已经安抚住他想砍面试官的心了。
其实看到这个问题,我还是挺感叹的:现在的面试题已经难到这个程度了吗?这个问题可是需要你完整得理解JVM是如何实现OOP的封装机制才能答出来的。
所有呢,给小伙伴们一个建议:简历不要凡尔赛,带来关注的同时,也带来了高期待。直接的结果就是问超难的面试题,一上来就给你打蒙圈了。
我们先来分析下这个问题。如果你想知道怎么访问对象实例属性的底层原理,就得知道是如何存储的。存储搞明白了,访问就是一句话的事。而想搞明白存储,恰恰不是一件简单的事情。JVM中对象实例属性的存储甚至比原生的C++对象还要复杂。为什么这么说呢?往后看。
对于面向对象类型的语言来说,有两个很重要的概念:类、对象。类的所有信息在编译时就已经确定下来了。但是对象是运行时结构,它的实例属性信息,只有在执行完当前方法及其父类的构造方法才能知晓。针对这个情况,你可以事先定义一个list<T>用来存储对象的实例属性。但是实例属性的类型不确定,有可能是char、int、double、指针…你这个T好像只能用8字节的数据类型来接收才能兼容所有情况。这带来的问题就是严重的内存浪费。
那C++及Java是怎么做的呢?内存编织。解释下这个名词:编织,抽象来说,就是精细化构造。内存编织,即精细化构造内存。即在创建对象时,为了节省内存,根据不同类型的数据,精细化地向内存中填充数据。
为什么说JVM的对象实例属性存储机制比C++更难呢?因为JVM的内存编织需要考虑的点更多:一、JVM有运行时数据结构:数组。什么意思呢?就是说非数组类的元信息是在编译时确定的,而数组的元信息是在运行时确定的;二、JVM为了节省内存,开发了指针压缩技术。一开一关,两套机制需要研究;三、JVM为了比C++更节省内存,引入了字段重排机制,又给研究增加了难度。
OK!问题已经分析完了,开始展开来说。本篇文章只把谜底揭开,不会面面俱到地讲到所有。剩余的情况,有能力有好奇心的小伙伴就自行去研究啦。
其实这个问题的本质就是:JVM是如何实现OOP的封装机制的。看懂了,你对Java的理解就比别人高好几个维度。
对比学习效果更佳!先来看看C++中的实例属性是如何存储的,上代码
class Person {
private:
short s;
int i;
long l;
};
这段代码生成的对象在内存中长这个样子,占用16B,浪费了2B
如果我把代码改一下,int移到long的下方,又完全不一样了。就变成了24字节,浪费了10字节。为什么会这样呢?因为C++这块只做到了这个程度。它是希望你在写代码的时候定规范解决这个问题。怎么规范呢?往后看。
所以高手都知道:定义属性要遵循占字节少的数据类型放前面,占字节多的数据类型放后面。是不是好麻烦?我们写Java代码完全不需要考虑这个问题,JVM已经通过技术手段帮我们解决了。怎么解决的呢?往后看。
说完了C++的对象内存模型,再来说说JVM的对象内存模型,两句话:
创建对象进行属性编织时,按照8字节、4字节、2字节、1字节、指针的顺序进行编织。其中指针可以在最后,也可以在最前,可以通过参数控制。默认是放在最后。
编织时会将相同类型的属性放在一起。这样敲代码时就不需要关注属性定义的顺序。这项就是JVM独有的字段重排。这个实现起来也不是那么容易的,需要考虑很多很多。
还是上面的代码,对应的JVM对象的内存模型长这个样子
如果上面的图比较抽象,那看这张图
JVM很牛叉有木有?反正我是很喜欢研究JVM的,研究过程中真的给了我很多惊喜。不得不赞叹,跟这些个大佬相比,我怎么好意思睡觉!^_^
JVM的对象内存模型已分析完毕,可以给出答案了。其实这里还要一个问题,就是内存是无态的,比如对象中有两个int,我要取第2个,取的时候怎么知道取的是哪一个呢?这时候就要找到对象的类信息,找到类信息中存储的属性表,然后才能完成取值。上伪代码
属性表 = 对象.类型指针.属性表
属性地址 = 属性表[int][1
我是子牙老师,喜欢钻研底层,深入研究Windows、Linux内核、JVM。喜欢分享硬核知识,如果你也喜欢研究底层,喜欢硬核知识,关注我。