类和对象运行时在内存里是怎么样的?各种变量、方法在运行时是怎么交互的?
转载自 类和对象运行时在内存里是怎么样的?各种变量、方法在运行时是怎么交互的?
在回答这个问题之前先了解一下Java的一些基础知识。
我们知道Java程序运行在虚拟机环境里,那我们先看一下虚拟机的大致内存结构。如下图所示,虚线框为整个虚拟机内存区域,其中有颜色的区域为Java程序所占的内存区域。
图中可见Java程序所占的内存区域可划分成5个部分:程序计数器、虚拟机栈(线程栈)、本地方法栈、堆(heap)和方法区(内含常量池)。其中方法区和堆由所有线程共享。
这5个区域作用和功能分别如下:
一、程序计数器:
它类似CPU寄存器中的PC寄存器,用于存放指令地址。因为Java虚拟机是多线程的,所以每一个线程都有一个独立的程序计数器结构,它与线程共存亡。不过Java虚拟机中的程序计数器指向的是正在执行的字节码地址,而CPU的PC寄存器指向的是下一条指令的地址。当线程去执行Native方法时,程序计数器则为Undefined。
二、虚拟机栈(线程栈):
一个线程一个栈,并且生命周期与线程相同。它内部由一个个栈帧构成,一个栈帧代表一个调用的方法,线程在每次方法调用执行时创建一个栈帧然后压栈,栈帧用于存放局部变量、操作数、动态链接、方法出口等信息。方法执行完成后对应的栈帧出栈。我们平时说的栈内存就是指这个栈。
一个线程中的方法可能还会调用其他方法,这样就会构成方法调用链,而且这个链可能会很长,而且每个线程都有方法处于执行状态。对于执行引擎来说,只有活动线程栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),这个栈帧关联的方法称为当前方法(Current Method)。
栈帧的大致结构如下图所示:
每一个栈帧的结构都包括了局部变量表、操作数栈、方法返回地址和一些额外的附加信息。某个方法的栈帧需要多大的局部变量表、多深的操作数栈都在编译程序时完全确定了,并且写入到类方法表的相应属性中了,因此某个方法的栈帧需要分配多少内存,不会受到程序运行期变量数据变化的影响,而仅仅取决于具体虚拟机的实现。
栈帧结构各部分功能:
1)局部变量区域:存储方法的局部变量和参数,存储单位以slot(4 byte)为最小单位。局部变量存放的数据类型有:基本数据类型、对象引用和return address(指向一条字节码指令的地址)。其中64位长度的long和double类型的变量会占用2个slot,其它数据类型只占用1个slot。
类的静态方法和对象的实例方法被调用时,各自栈帧对应的局部变量结构基本类似。但有以下如图示区别:实例方法中第一个位置存放的是它所属对象的引用。而静态方法则没有对象的引用。另外静态方法里所操作的静态变量存放在方法区。
void test(Object object){
int i=0;
Boolean b = false;
}
static void test1(int i, Object object, boolean b){
...
}
关于局部变量,还有一点需要强调,就是局部变量不像类的实例变量那样会有默认初始化值。所以局部变量需要手工初始化,如果一个局部变量定义了但没有赋初始值是不能使用的。
2)操作数栈 所谓操作数是指那些被指令操作的数据。当需要对参数操作时如c=a+b,就将即将被操作的参数数据压栈,如将a和b压栈,然后由操作指令将它们弹出,并执行操作。虚拟机将操作数栈作为工作区。Java虚拟机没有寄存器,所有参数传递、值返回都是使用操作数栈来完成的。
Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。
例如下面这段代码:
public static int add(int a,int b){
int c=0;
c=a+b;
return c;
}
add(25,23);
主要步骤如图:
压栈的步骤如下:
0: ....
1: iload_0 // 把局部变量0压栈,int a;
2: iload_1 // 局部变量1压栈,int b;
3: iadd //弹出2个变量,求和,结果压栈48
4: istore_2 //弹出结果,放于局部变量2;int c;
5: ...
3)动态连接,它是个指向运行时常量池中该栈帧所属方法的引用。这个引用是为了支持方法调用过程中能进行动态连接。我们知道Class文件的常量池存有方法的符号引用,字节码中的方法调用指令就以指向常量池中方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。余下部分将在每一次运行期间转化为直接引用,这部分称为动态连接。
4)方法返回地址
正常退出,执行引擎遇到方法返回的字节码,将返回值传递给调用者。
异常退出,遇到Exception, 并且方法未捕捉异常,返回地址由异常处理器来确定,并且不会有任何返回值。
方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等。
5)额外附加信息,虚拟机规范没有明确规定,由具体虚拟机实现。
Java虚拟机规范规定该区域有两种异常:
StackOverFlowError:当线程请求栈深度超出虚拟机栈所允许的深度时抛出
OutOfMemoryError:当Java虚拟机动态扩展到无法申请足够内存时抛出
另外需要提醒一下,在规范模型中,栈帧相互之间是完全独立的。但在大多数虚拟机的实现里都会做一些优化处理,这样两个栈帧可能会出现一部分重叠。这样在下面的栈帧会有部分操作数栈与上面栈帧的部分局部变量表重叠在一起,这样在进行方法调用时就可以有部分数据共享,而无须进行额外的参数复制传递了。具体情形如下图所示:
三、本地方法栈
Java可以通过java本地接口JNI(Java Native Interface)来调用其它语言编写(如C)的程序,在Java里面用native修饰符来描述一个方法是本地方法。本地方法栈就是虚拟机线程调用Native方法执行时的栈,它与虚拟机栈发挥类似的作用。但是要注意,虚拟机规范中没有对本地方法栈作强制规定,虚拟机可以自由实现,所以可以不是字节码。如果是以字节码实现的话,虚拟机栈本地方法栈就可以合二为一,事实上,OpenJDK和SunJDK所自带的HotSpot虚拟机就是直接将虚拟机栈和本地方法栈合二为一的。
Java虚拟机规范规定该区域也可抛出StackOverFlowError和OutOfMemoryError。
四、堆(heap)
这个区域用来放置所有对象实例以及数组。不过在JIT(Just-in-time)情况下有些时候也有可能在栈上分配对象实例。堆也是java垃圾收集器管理的主要区域(所以很多时候会称它为GC堆)。
从GC回收的角度看,由于现在GC基本都是采用的分代收集算法,所以堆内存结构还可以分块成:新生代和老年代;再细一点的有Eden空间、From Survivor空间、To Survivor空间等。如下图:
五、方法区
它是虚拟机在加载类文件时,用于存放加载过的类信息,常量,静态变量,及jit编译后的代码(类方法)等数据的内存区域。它是线程共享的。
方法区存放的信息包括:
类的基本信息:
每个类的全限定名
每个类的直接超类的全限定名(可约束类型转换)
该类是类还是接口
该类型的访问修饰符
直接超接口的全限定名的有序列表
已装载类的详细信息:
运行时常量池:
类信息除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量、符号引用,文字字符串、final变量值、类名和方法名常量,这部分内容将在类加载后存放到方法区的运行时常量池中。它们以数组形式访问,是调用方法、与类联系及类的对象化的桥梁。
这里再讲一下,JDK1.7之前运行时常量池是方法区的一部分,JDK1.7及之后版本已经将运行时常量池从方法区中移了出来,在堆(Heap)中开辟了一块区域存放运行时常量池。
运行时常量池除了存放编译期产生的Class文件的常量外,还可存放在程序运行期间生成的新常量,比较常见增加新常量方法有String类的internd()方法。String.intern()是一个Native方法,它的作用是:如果运行时常量池中已经包含一个等于此String对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此String内容相同的字符串,并返回常量池中创建的字符串的引用。不过JDK7的intern()方法的实现有所不同,当常量池中没有该字符串时,不再是在常量池中创建与此String内容相同的字符串,而改为在常量池中记录堆中首次出现的该字符串的引用,并返回该引用。
字段信息:
字段信息存放类中声明的每一个字段(实例变量)的信息,包括字段的名、类型、修饰符。
如private String a = ""; 则a为字段名,String为描述符,private为修饰符。
方法信息:
类中声明的每一个方法的信息,包括方法名、返回值类型、参数类型、修饰符、异常、方法的字节码。(在编译的时候,就已经将方法的局部变量表、操作数栈大小等完全确定并存放在字节码中,在加载载的时候,随着类一起装入方法区。)
在运行时,虚拟机线程调用方法时从常量池中获得符号引用,然后在运行时解析成方法的实际地址,最后通过常量池中的全限定名、方法和字段描述符,把当前类或接口中的代码与其它类或接口中的代码联系起来。
静态变量:
就是类变量,被类的所有实例对象共享,我们只需知道,在方法区有个静态区,静态区专门存放静态变量和静态块。
到类ClassLoader的引用:
到该类的类装载器的引用。
到类Class的引用:
虚拟机为每一个被装载的类型创建一个Class实例,用来代表这个被装载的类。
Java虚拟机规范规定该区域可抛出OutOfMemoryError。
六、直接内存
直接内存(Direct Memory)虽然不是程序运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但这部分内存也被频繁使用,而且它也可能导致OutOfMemoryError异常出现。
在JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native方法库直接分配堆外内存,然后通过一个存储在Java堆里面的DirecByteBuffer对象作为这块内存的引用进行操作。这样能在某些应用场景中显著提高性能,因为它避免了在Java堆和Native堆中来回复制数据。
显然,本机直接内存的分配不会受到Java堆大小的限制,但是,还是会受到本机总内存(包括RAM及SWAP区或者分页文件)的大小及处理器寻址空间的限制,从而导致动态扩展时出现OutOfMemoryError异常。
七、执行引擎
将字节码即时编译 优化 为本地代码, 然后执行。
在了解完这些知识以后,就可以知道:类和对象在运行时的内存里是怎么样的,以及各类型变量、方法在运行时是怎么交互的。
在程序运行时类是在方法区,实例对象本身在堆里面。
方法字节码在方法区。线程调用方法执行时创建栈帧并压栈,方法的参数和局部变量在栈帧的局部变量表。
对象的实例变量和对象一起在堆里,所以各个线程都可以共享访问对象的实例变量。
静态变量在方法区,所有对象共享。字符串常量等常量在运行时常量池。
各线程调用的方法,通过堆内的对象,方法区的静态数据,可以共享交互信息。
各线程调用的方法所有参数传递、方法返回值的返回,都是使用栈帧里的操作数栈来完成的。
类和对象运行时在内存里是怎么样的?各种变量、方法在运行时是怎么交互的?相关推荐
- Python的类和对象的介绍,定义类和对象,定义实例方法和属性以及Python中的魔法方法
Day09新手小白学python 第九节 Python的类和对象的介绍,定义类和对象,定义实例方法和属性以及Python中的魔法方法 目录 Day09新手小白学python 前言 一.面向对象介绍 二 ...
- 学习笔记:C++初阶【C++入门、类和对象、C/C++内存管理、模板初阶、STL简介、string、vector、list、stack、queueu、模板进阶、C++的IO流】
文章目录 前言 一.C++入门 1. C++关键字 2.命名空间 2.1 C语言缺点之一,没办法很好地解决命名冲突问题 2.2 C++提出了一个新语法--命名空间 2.2.1 命名空间概念 2.2.2 ...
- c语言运行时显示内存不足,请问:c或c++运行时 遇到虚拟内存不足时咋办,帮优化下代码...
请问:c或c++运行时 遇到虚拟内存不足时咋办,帮优化下代码 [size=4][size=5]我编的一个高精度解hilbert方程组的程序段,用的是GS跌代,但运行时出现"虚拟内存不足&qu ...
- iOS 同一页面加载上百张图片,迅速滑动时导致内存暴涨程序崩溃的参考解决方法
本例中项目大致流程是先由客户端拍照或者选择图库中的图片进行上传,然后可以从详情页面中浏览所有上传的图片,由于图片是按照相册进行分类,而每个相册中最多可以有50张照片,极限的情况是详情页面最多可以有20 ...
- excel 2016 新建时 出现 内存或磁盘不足错误的解决方法
为什么80%的码农都做不了架构师?>>> 近日,绿色版的office2016,excel出现文件,网上找了很多解决方法,都不行,最后发现是保存格式问题,将默认的xlsx后缀改为 ...
- 【Java 19】反射 - 反射机制概述、获取Class实例、类的加载与ClassLoader的理解、创建运行时类的对象、获取运行时类的完整结构、调用运行时类的指定结构、动态代理
反射机制概述.获取Class实例.类的加载与ClassLoader的理解.创建运行时类的对象.获取运行时类的完整结构.调用运行时类的指定结构.动态代理 反射 1 Java反射机制概述 1.1 Java ...
- 用《叩响C#之门》复习C#基础知识 第八章 面向对象编程:类和对象(二)
1.以对象为成员 类的成员不光可以是int.double等基本类型的变量,也可以是其他类的对象.其实也就是说,类的成员可以是所有的值类型和引用类型的成员变量. 2.静态成员 1)静态变量:描述类的整体 ...
- C++核心编程4— 类和对象
C++面向对象的三大特性为:封装.继承.多态 C++认为万事万物都皆为对象,对象上有其属性和行为 例如: 人可以作为对象,属性有姓名.年龄.身高.体重-,行为有走.跑.跳.吃饭.唱歌- 车也可 ...
- java实验二:类与对象
类与对象编程练习 一.学生信息处理 二.用类描述计算机中CPU的速度和硬盘的容量 三.共饮同井水 四.正n边形 五.分析程序,给出运行结果 1.程序一 2.程序二 3.程序三 六.程序纠错 1.程序一 ...
最新文章
- matplotlib绘制图表,设置刻度标签、最大最小刻度、字体大小,label位置、刻度轴箭头等
- PHP转义Json里的特殊字符的函数
- 【HDU - 2255】奔小康赚大钱(KM算法模板,二分图最优匹配)
- buy low buy lower——伪思考
- 一款轻量级android图表组件SimpleChart-Kotlin
- 什么是云计算?这个愚蠢的流行词是什么意思?
- 如何看待并夕夕公司离职员工在脉脉上疯狂吐槽前公司?
- github上下载nacos教程并安装
- M5Product: Self-harmonized Contrastive Learning for E-commercial Multi-modal Pretraining 论文解读
- OpenCV实现角点检测(cornerHarris)
- Java后端解密微信小程序手机号数据
- ElementUI 表单单个验证
- 产教融合服务平台方案
- m基于深度学习的LTE信号检测算法matlab仿真
- 数据库小技能:Case和nvl的使用
- 用vs2013编写并调试erlang内建函数(NIF)
- 希冀平台1-4:对于如下表actor,其对应的数据为: actor_id first_name last_name 1 PENELOPE GUINESS 2 NICK WAHLBERG
- 一、MySql服务器
- Python的select.select()函数初探
- Heterogeneous Graph Transformer(中文翻译助理解)
热门文章
- 69. Sqrt(x)010(二分法求解+详解注释)
- c语言中注释部分二侧分界符为,C语言常见复习题(选择填空)及参考答案
- 二叉搜索树的插入与删除(C语言)
- C++实现dijkstra单源最短路径算法-邻接表+优先队列
- [蓝桥杯]回形取数-方向向量+模拟
- 写出TREE-PREDECESSOR的伪代码(算法导论第三版12.2-3)
- ueditor编辑器php上传配置,php版本UEditor编辑器图片上传设置
- Mac(OS X)使用brew安装软件
- 最优间隔分类器-SVM
- Servlet与线程安全