`
gao_xianglong
  • 浏览: 467799 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

透过OOP-Klass模型来看实例变量与类变量的存储

阅读更多

《透过OOP-Klass模型来看实例变量与类变量的存储》

 

前言

很久没有写过博客了,但今天在Iteye上看见有朋友提问静态变量是存储在方法区还是存储在哪里的一篇帖子,然后又搜索了其他的一些相关帖子,看后心里不免有些蛋疼,十有八九很多人从心里根本不清楚JVM是如何存储的,或者说,很多人根本就没有区分开什么是变量存储什么是值存储,这2个压根就不是同一个概念,导致很多人产生疑问,那么笔者今天这篇博客,要的效果就是深入到JVM底层来分析这个问题。当然由于本人能力有限,难免会有语义错误的地方,请大家指出。

 

正文

言归正传,再谈存储之前,首先做一个扫盲。从虚拟机内部表示形式来看,变量可以划分为实例变量和类变量(即静态变量)。实例变量与对象实例相关,也就是说,任何一个实例变量,在使用之前,首先要做的事情就是目标类型必须要实例化,然后才能够使用;而类变量与对象实例无关,但是却以类相关,也就是说,使用一个被static关键字标示的类变量是不用实例化对象的,直接可用。

 

当大家明白实例变量和类变量之前的区别后,我们再接从语法层面开始讨论引用类型的变量和原始类型的变量之间的区别。引用类型的变量所持有的值是什么?相信大家都应该知道,所谓引用指的就是一个指向目标对象实例的类似于“指针”的东西,而原始类型的变量所持有的值就是实际的原始值。

 

关于变量的概念已经介绍了一大堆后,接下来我们来再来看看变量值的存储。以粗粒度的方式来看待变量的存储尽管有些不妥,但为了让更多不理解JVM底层的人能够看懂,也是情有可原。Java的运行时数据区中,栈帧中的局部变量表用于存储方法体内的局部变量和方法参数,如果变量的类型是原始数据类型,则在局部变量表中存储实际的原始值,反之存储对象引用(reference),当然还会存储returnAddress类型。那么如果是成员变量,就存储在Java堆区中(内存布局中的实例数据中)。简单来说,与线程上下文相关的存储在Java栈中,反之存储在Java堆中,这个是常识。

图1 对象访问定位

之所以先说变量值的存储而没有先说变量的存储,笔者是可以安排的,因为这2个概念是非常多的开发人员容易弄混淆的。变量仅仅只是一个存储介质,所负责的任务很简单,就是牵引数据存储到正确的位置上,但它自己本身没有任何能力存储数据,这一点要明确。这里要引入一个概念,那就是JVM中对象的底层表示形式,OOP-Klass模型。对象内存布局中的内存头是由instanceOopDesc来表示(数组类型则用arrayOopDesc对象来进行表示),而对象头中的元数据指针所指向的当前对象的目标类型则是由Klass中的instanceKlass对象进行表示(数组则用arrayKlass对象进行表示)。OOPKlass其实是2个相互独立但是却又彼此相互关联的模块,这2个模块均包含在/openjdk/hotspot/src/share/vm/oops模块中,那么OOP-Klass模型与对象的内存布局之间又有什么关系呢?在JVM中对象头就是由OOP对象instanceOopDesc来表示(数组类型则用arrayOopDesc对象来进行表示),而对象头中的元数据指针所指向的当前对象的目标类型则是由Klass中的instanceKlass对象进行表示(数组则用arrayKlass对象进行表示),用于在JVM中表示一个Java类的对等体。当明确OOP-Klass模型的作用后,请大家思考一下JVM是如何通过栈帧中的对象引用访问到其内部的对象实例的呢?如图1所示,JVM可以通过对象引用准确定位到Java堆区中的instanceOopDesc对象,这样既可成功访问到对象的实例信息,当需要访问目标对象的具体类型时,JVM则会通过存储在instanceOopDesc中的元数据指针定位到存储在方法区中的instanceKlass对象上。

 

扯了一大堆,说白了,类变量既然不予对象实例相关,那么它则存储在方法区中的instanceKlass对象内,当然仅仅只是变量!

7
7
分享到:
评论
7 楼 gao_xianglong 2014-11-06  
RednaxelaFX 写道
嗯标题写错了,oop写成了OPP。

改过来了
6 楼 RednaxelaFX 2014-11-05  
嗯标题写错了,oop写成了OPP。
5 楼 duoduodeai 2014-11-05  
点踩的死逼们,给点建设性意见
4 楼 gao_xianglong 2014-11-04  
那家渔村 写道
gao_xianglong 写道
那家渔村 写道
    博主的文章很好,赞一下!
    但是感觉博主的结论好像有点说得不太对,“说白了,实例变量就是存储在对象内存布局中的实例数据内?这里只是说变量,不是说变量持有的值!”。
    我的理解是,java对象就是分配在java堆中逻辑上连续的内存片(类似于结构体,其中也存在边界对其的填充)。而这个连续的内存片中存放的就是java对象的实例域(实例变量)的值,另外说一句,每个java对象都有2个机器字长的头,用来定位对象所属的类型信息。其中java对象的内存布局可以看这个http://www.importnew.com/1305.html帖子。
    另外,说到java对象的实例变量与类变量,就顺便提一下<clinit>与<init>方法。<clinit>是类初始化方法,用来初始化类型静态域的,<init>是实例初始化方法,是初始化对象实例域的。


谢谢提出的宝贵意见,文章内容作了小的调整。
在HotSpot中,除了java.lang.Class对象实例时存储在方法区外,基本上所有的对象实例都会存储在Java堆区中(当然不排除栈上分配和其余的堆外存储技术),这里的堆是个大概的概念,细化的话,基于线程安全的考虑,如果一个类在分配内存之前已经成功完成类装载步骤之后,JVM就会优先选择在TLAB(Thread Local Allocation,本地线程分配缓冲区)中为对象实例分配内存空间,TLAB在Java堆区中是一块线程私有区域,它包含在Eden空间内,除了可以避免一系列的非线程安全问题外,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略。反之在Eden中分配,如果是大对象则直接在老年代中分配。

实例变量,语法层面又可以划分为成员和局部,作用域划分后,还要划分类型。总之成员变量的值才是存储在对象内存布局中的实例数据内,而局部变量都是存储在局部变量表内(引用类型的局部变量持有引用)。

"实例变量,语法层面又可以划分为成员和局部,作用域划分后,还要划分类型。总之成员变量的值才是存储在对象内存布局中的实例数据内,而局部变量都是存储在局部变量表内(引用类型的局部变量持有引用."
博主,最好不要这样进行类比,java语言中的实例域(也称为成员或者实例变量)与局部变量完全不是一个概念,就是两个层面的东西,不要混为一谈。java语言中的局部变量就是指方法内部声明的局部变量和方法的参数,编译时统一存在为方法字节码的局部变量表中。如果方法为非静态方法,则局部变量表的第一位为this引用。
java对象的实例域和方法的局部变量无论从语义上还是存储上都没有任何关系。实例域存放在java对象所在堆空间的连续地址上,java对象的内存分布已经给了贴子的地址。而局部变量编译是存放在方法字节码的局部变量表中,而运行时分配在线程栈空间中。
java对象的实例域和java方法的局部变量完全没有关系!
唯一相同的就是值的分配。如果实例域(局部变量)的类型为基本类型,那么存储的值就是字面量值;如果实例域(局部变量)的类型为引用类型,那么存储的就是该引用所“指向java对象在堆中地址值”的副本。java方法调用中,局部变量的传递方式也是如此。



可能我理解的有些“偏差”,谢谢提出的宝贵意见!
3 楼 那家渔村 2014-11-04  
gao_xianglong 写道
那家渔村 写道
    博主的文章很好,赞一下!
    但是感觉博主的结论好像有点说得不太对,“说白了,实例变量就是存储在对象内存布局中的实例数据内?这里只是说变量,不是说变量持有的值!”。
    我的理解是,java对象就是分配在java堆中逻辑上连续的内存片(类似于结构体,其中也存在边界对其的填充)。而这个连续的内存片中存放的就是java对象的实例域(实例变量)的值,另外说一句,每个java对象都有2个机器字长的头,用来定位对象所属的类型信息。其中java对象的内存布局可以看这个http://www.importnew.com/1305.html帖子。
    另外,说到java对象的实例变量与类变量,就顺便提一下<clinit>与<init>方法。<clinit>是类初始化方法,用来初始化类型静态域的,<init>是实例初始化方法,是初始化对象实例域的。


谢谢提出的宝贵意见,文章内容作了小的调整。
在HotSpot中,除了java.lang.Class对象实例时存储在方法区外,基本上所有的对象实例都会存储在Java堆区中(当然不排除栈上分配和其余的堆外存储技术),这里的堆是个大概的概念,细化的话,基于线程安全的考虑,如果一个类在分配内存之前已经成功完成类装载步骤之后,JVM就会优先选择在TLAB(Thread Local Allocation,本地线程分配缓冲区)中为对象实例分配内存空间,TLAB在Java堆区中是一块线程私有区域,它包含在Eden空间内,除了可以避免一系列的非线程安全问题外,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略。反之在Eden中分配,如果是大对象则直接在老年代中分配。

实例变量,语法层面又可以划分为成员和局部,作用域划分后,还要划分类型。总之成员变量的值才是存储在对象内存布局中的实例数据内,而局部变量都是存储在局部变量表内(引用类型的局部变量持有引用)。

"实例变量,语法层面又可以划分为成员和局部,作用域划分后,还要划分类型。总之成员变量的值才是存储在对象内存布局中的实例数据内,而局部变量都是存储在局部变量表内(引用类型的局部变量持有引用."
博主,最好不要这样进行类比,java语言中的实例域(也称为成员或者实例变量)与局部变量完全不是一个概念,就是两个层面的东西,不要混为一谈。java语言中的局部变量就是指方法内部声明的局部变量和方法的参数,编译时统一存在为方法字节码的局部变量表中。如果方法为非静态方法,则局部变量表的第一位为this引用。
java对象的实例域和方法的局部变量无论从语义上还是存储上都没有任何关系。实例域存放在java对象所在堆空间的连续地址上,java对象的内存分布已经给了贴子的地址。而局部变量编译是存放在方法字节码的局部变量表中,而运行时分配在线程栈空间中。
java对象的实例域和java方法的局部变量完全没有关系!
唯一相同的就是值的分配。如果实例域(局部变量)的类型为基本类型,那么存储的值就是字面量值;如果实例域(局部变量)的类型为引用类型,那么存储的就是该引用所“指向java对象在堆中地址值”的副本。java方法调用中,局部变量的传递方式也是如此。
2 楼 gao_xianglong 2014-11-04  
那家渔村 写道
    博主的文章很好,赞一下!
    但是感觉博主的结论好像有点说得不太对,“说白了,实例变量就是存储在对象内存布局中的实例数据内?这里只是说变量,不是说变量持有的值!”。
    我的理解是,java对象就是分配在java堆中逻辑上连续的内存片(类似于结构体,其中也存在边界对其的填充)。而这个连续的内存片中存放的就是java对象的实例域(实例变量)的值,另外说一句,每个java对象都有2个机器字长的头,用来定位对象所属的类型信息。其中java对象的内存布局可以看这个http://www.importnew.com/1305.html帖子。
    另外,说到java对象的实例变量与类变量,就顺便提一下<clinit>与<init>方法。<clinit>是类初始化方法,用来初始化类型静态域的,<init>是实例初始化方法,是初始化对象实例域的。


谢谢提出的宝贵意见,文章内容作了小的调整。
在HotSpot中,除了java.lang.Class对象实例时存储在方法区外,基本上所有的对象实例都会存储在Java堆区中(当然不排除栈上分配和其余的堆外存储技术),这里的堆是个大概的概念,细化的话,基于线程安全的考虑,如果一个类在分配内存之前已经成功完成类装载步骤之后,JVM就会优先选择在TLAB(Thread Local Allocation,本地线程分配缓冲区)中为对象实例分配内存空间,TLAB在Java堆区中是一块线程私有区域,它包含在Eden空间内,除了可以避免一系列的非线程安全问题外,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略。反之在Eden中分配,如果是大对象则直接在老年代中分配。

实例变量,语法层面又可以划分为成员和局部,作用域划分后,还要划分类型。总之成员变量的值才是存储在对象内存布局中的实例数据内,而局部变量都是存储在局部变量表内(引用类型的局部变量持有引用)。
1 楼 那家渔村 2014-11-04  
    博主的文章很好,赞一下!
    但是感觉博主的结论好像有点说得不太对,“说白了,实例变量就是存储在对象内存布局中的实例数据内?这里只是说变量,不是说变量持有的值!”。
    我的理解是,java对象就是分配在java堆中逻辑上连续的内存片(类似于结构体,其中也存在边界对其的填充)。而这个连续的内存片中存放的就是java对象的实例域(实例变量)的值,另外说一句,每个java对象都有2个机器字长的头,用来定位对象所属的类型信息。其中java对象的内存布局可以看这个http://www.importnew.com/1305.html帖子。
    另外,说到java对象的实例变量与类变量,就顺便提一下<clinit>与<init>方法。<clinit>是类初始化方法,用来初始化类型静态域的,<init>是实例初始化方法,是初始化对象实例域的。

相关推荐

    16M-OOP-源码.rar

    标题中的"16M-OOP-源码"表明...总结,"16M-OOP-源码"可能包含了大量的面向对象编程实例,涵盖各种类定义、继承关系、多态实现以及可能的设计模式。通过深入研究这个代码库,开发者可以增强对OOP的理解,提升编程能力。

    project-oop-1-源码.rar

    标题中的"project-oop-1-源码"表明这是一个关于面向对象编程(Object-Oriented Programming,简称OOP)的项目,可能是一个教学实例或者一个实际应用的代码库。源码通常包含了项目的全部代码文件,我们可以从中学习到...

    javaOOP---1.2.docx

    Java面向对象编程(OOP)是Java编程的核心概念,它将现实世界中的实体抽象为类,然后通过实例化这些类来创建对象,以此来模拟现实世界的模型。以下是关于Java OOP的一些关键知识点: 3.1 类: 类是Java OOP的基本...

    oop--cplusplus.rar_OOP C++

    面向对象编程(Object-Oriented Programming,简称OOP)是C++语言的核心特性之一,它是一种编程范式,强调通过对象来组织代码,使得程序更易于理解和维护。本资料"oop--cplusplus.rar"包含了深入学习C++面向对象编程...

    oop-abstractclass.zip

    - **成员变量**:抽象类可以有实例变量,而接口只能有常量(final static)。 - **方法**:抽象类可以有抽象方法和非抽象方法,接口只能有抽象方法(在Java 8之后可以有默认方法和静态方法)。 - **继承关系**:...

    JavaOOP-myPrime.txt

    根据提供的文件信息:“JavaOOP-myPrime.txt”,我们可以推断出这份文档主要关注的是Java面向对象编程(Object-Oriented Programming, OOP)中的某个特定主题或案例——可能是与质数(prime numbers)相关的实现。...

    JavaOOP-pet.txt

    根据提供的文件信息,“JavaOOP-pet.txt”,我们可以推测这份文档可能涉及面向对象编程(Object-Oriented Programming,简称OOP)在Java中的应用,并且可能包含了一个宠物类(Pet class)或者与宠物相关的示例代码。...

    ABAP-OOP-Library, 面向对象的编程库.zip

    ABAP-OOP-Library, 面向对象的编程库 ABAP对象定位程序库安装手动创建下面列出的字典类型使用基于源类编辑器和常规编辑器来导入所有类。接口和程序。某些软件包使用邮件类。 这些消息保存在。messageclass 。txt文件...

    basic-oop-java-master.zip_Nice Work_cryb8x_d_drawypo

    1. **类与对象**:Java是一种面向对象的语言,它通过类来定义数据结构和操作数据的方法。类是对象的蓝图,而对象则是类的实例。 2. **封装**:Java通过访问修饰符(如private, public, protected)实现封装,将数据...

    php-oop-fundamentals, 这个库伴随着Tuts+课程 PHP OOP基础.zip

    php-oop-fundamentals, 这个库伴随着Tuts+课程 PHP OOP基础 php-oop-fundamentals这个库伴随着Tuts+课程 PHP OOP基础什么是 OOP?在本课中,我们将看看面向对象的编程是什么,完全正确。 这看起来可以能很多,但是你...

    C OOp-intro.pdf C++ OOP2-ex.cpp 面向对象Part.pdf

    面向对象编程(Object-Oriented Programming,简称OOP)是C++语言中的核心特性,它是一种编程范式,强调程序数据结构与程序控制结构的结合,以提高代码的可重用性和可维护性。在本文件集合中,我们可以通过三个文件...

    OOP-java.zip_java programming

    它们决定了类、方法和变量的可见性和可访问范围。 8. **异常处理**:Java通过异常处理机制来处理程序运行时可能出现的错误。我们可以使用try-catch-finally语句块来捕获和处理异常。 9. **集合框架**:Java集合...

    JavaOOP-04-static关键字笔记.pdf

    静态方法与类相关联,而不是与类的任何特定实例相关联。这意味着静态方法不能直接访问非静态的成员(变量或方法),因为非静态成员是与每个对象实例相关的。静态方法通常用于不需要访问对象状态的工具函数或者操作,...

    作业答案oop-day02.rar

    在“作业答案oop-day02”这个文件中,可能包含了以上知识点的具体应用示例,包括但不限于如何定义和实例化类、如何实现封装、如何使用继承和多态等。通过分析这些解答,学习者可以检查自己的理解是否正确,或者借鉴...

    java课件-7-OOP-3-方法.pptx

    堆是存储对象实例的地方,而栈则保存方法调用时的局部变量。在上述代码中,`math` 变量在栈中创建,而 `Math` 对象实例在堆中创建。当构造方法执行完毕,局部变量 `math` 会保留对堆中对象的引用,但构造方法的参数...

    JavaOOP-01-类和对象笔记.pdf

    首先,从标题和描述来看,这是一份关于Java面向对象编程的笔记,重点介绍了类和对象的概念。面向对象编程与面向过程编程的主要区别在于,面向过程编程关注的是执行步骤,而面向对象编程则关注对象的交互。在Java中,...

    面向对象编程(OOP-PDF文件)

    类的实例化过程就是创建对象。属性用来存储数据,而方法则是操作数据的函数。类的封装性保证了对象内部状态的隐私,只对外提供必要的接口进行交互。 继承是OOP的另一个关键特性,它允许一个类(子类)继承另一个类...

    【IT十八掌徐培成】Java基础第04天-04.OOP-堆栈的溢出与设置-private关键字.zip

    本课程由IT十八掌徐培成讲解的"Java基础第04天-04.OOP-堆栈的溢出与设置-private关键字"深入探讨了两个关键主题:堆栈溢出和private关键字的使用。 首先,让我们来理解堆栈溢出。在计算机编程中,内存分为堆和栈两...

    Java逻辑基础题6-OOP -答案.docx

    4. **类和成员变量**:类中的变量被称为成员变量,它们在其他类中不能直接使用,需要通过对象来访问。局部变量只在其定义的方法或代码块中有效。调用类的方法需要使用对象名.方法名,如果是类方法(静态方法),则...

    exo-oop-oop-1

    "exo-oop-oop-1"这个标题可能是指一个关于JavaScript面向对象编程(Object-Oriented Programming, OOP)的学习项目或者教程,其中"oop"是OOP的缩写,暗示我们将深入探讨JavaScript中的类、对象、封装、继承和多态等...

Global site tag (gtag.js) - Google Analytics