Java内存模型

本文详细介绍了Java内存的五大部分:程序计数器、虚拟机栈、本地方法栈、堆和方法区,阐述了各区域的功能、异常情况以及对象创建过程。特别讨论了堆区的内存分配、对象访问定位的两种方式,并对比了方法区在不同JDK版本的变化。
摘要由CSDN通过智能技术生成


Java内存布局介绍

Java内存布局共有 5 大块,它们分别是堆区(Java Heap)、虚拟机栈(Virtual Machine Stacks)、本地方法栈(Native Method Stacks)、元空间(Meta Spaces)、程序计数器(Program Counter Register)。
在这里插入图片描述

程序计数器:

特点:

占用的 JVM 内存空间较小
每个线程生命周期内独享自己的程序计数器(内部存放的是字节码指令的地址引用)
不会发生 OOM

程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。

作用:
1、字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、异常处理。
2、在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪个位置。

注意:程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,
它的生命周期随着线程的创建而创建,随着线程的结束而死亡。

虚拟机栈

特点:

内部结构是栈帧,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法返回地址等信息
某方法在调用另一个方法是通过动态链接在常量池中查询方法的引用,进而完成方法调用
某方法在调用另一个方法的过程,即是一个栈帧在虚拟机中的入栈到出栈的过程
虚拟机中的方法入栈的顺序和方法的调用顺序是一致的

与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型。
在这里插入图片描述
局部变量表
主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
在这里插入图片描述
Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。
StackOverFlowError:
若Java虚拟机栈的内存大小不允许动态扩展,当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。
OutOfMemoryError:
若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。

Java 虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,
而且随着线程的创建而创建,随着线程的死亡而死亡。

本地方法栈

和虚拟机栈所发挥的作用非常相似,

区别是:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。

本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。方法执行完毕后相应的栈帧也会出栈并释放内存空间,
也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。

特点:

存放 Java 对象和数组
虚拟机中存储空间比较大的区域
可能出现OutOfMemoryError异常区域
该区域是 GC 的主要区域,堆区由年轻代和老年代组成,年轻代又分为 Eden 区、S0区(from survivor)、S1 区(to survivor);新生代对应 Minor GC(Young GC),老年代对应 Full GC(Old GC)。

Java 虚拟机所管理的内存中最大的一块, Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:
在细致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
在这里插入图片描述

方法区

方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。

相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。
运行时常量池
运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,
还有常量池信息(用于存放编译期生成的各种字面量和符号引用)
既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,

当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
方法区与永久代
方法区被所有线程共享。采用永久代的方式实现了方法区。
jdk 8 以前(不包括 jdk8)存在永久代(Perm区),jdk 8 以后(包括 jdk 8)移除了永久代。如下图所示。
在这里插入图片描述
方法区在不同 JDK 版本的变化
请见下图:在这里插入图片描述
方法区和元空间的区别
请见下图
在这里插入图片描述

示例:new Object()过程

对象在 JVM 虚拟机中是如何创建的,在什么地方分配内存,又是如何分配的,对象是如何定位的,以及对象的内存布局,最后又是如何回收的。
1)对象的创建
先在虚拟机栈创建栈帧,栈帧内创建对象的引用,在方法区进行类的加载,然后去 Java 堆区进行分配内存并内存初始化,再回到栈帧中初始化对象的数据,完成对象的创建。见下图:
在这里插入图片描述
对象创建过程
2)Java 堆内存分配过程
在堆空间中找到空闲区域进行对象分配
3)对象的访问定位
句柄访问,见下图所示:
在这里插入图片描述
句柄访问
注:句柄池是 Java 堆分配用于存放对象指针的内存空间。
优点:在垃圾回收的时候对象要经常转移,这时候只需改变句柄中指向对象实例数据的指针即可(不用修改 reference)。
直接访问,见下图所示:
在这里插入图片描述
直接访问
优点:相对于句柄访问定位的方式,减少了一次指针定位的开销(也减少了句柄池的存储空间),HotSpot JVM 实现采用的是直接访问的方式进行对象访问定位。
4)对象的内存布局
在这里插入图片描述
对象的内存布局

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值