浅谈Java内存区域划分和内存分配策略
作者:zycxnanwang 发布时间:2023-08-11 18:52:49
如果不知道,类的静态变量存储在那? 方法的局部变量存储在那? 赶快收藏
Java内存区域主要可以分为共享内存,堆、方法区和线程私有内存,虚拟机栈、本地方法栈和程序计数器。如下图所示,本文将详细讲述各个区域,同时也会讲述创建对象过程,内存分配策略, 和对象访问定位原理。觉得写得好的,可以点个收藏,绝对不亏。
Java内存区域
程序计数器
程序计数器,可以看作程序当前线程所执行的字节码行号指示器。字节码解释器工作时就是通过改变计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理都需要依赖计数器完成。线程执行Java方法时,记录其正在执行的虚拟机字节码指令地址,线程执行Native方法时,计数器记录为空。程序计数器时唯一在Java虚拟机规范中没有规定任何OutOfMemoryError
情况区域。
理论可知,线程是通过轮流获取CPU执行时间以实现多线程的并发。为了暂停的线程下一次获得CPU执行时间,能正常运行,每一个线程内部都需要维护一个程序计数器,用来记住暂停线程暂停的位置。
注意:光理论是不够的,在此送大家一套2020最新Java架构实战教程+大厂面试宝典,点击此处 进来获取 一起交流进步哦!
Java虚拟机栈
Java虚拟机栈同程序计数器一样,也是线程私有的,虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈、动态链接和方法出入口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。
本地方法栈
与虚拟机栈相似。虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。
Java堆
所有线程共享的一块内存区域。Java虚拟机所管理的内存中最大的一块,因为该内存区域的唯一目的就是存放对象实例。几乎所有的对象实例都在这里分配内存,同时堆也是垃圾收集器管理的主要区域。因此很多时候被称为"GC堆"
方法区
和堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、和编译器编译后的代码(也就是存储字节码文件.class)等数据。
方法区中有一个运行时常量池,编译后期生成的各种字面量和符号引用,存放在字节码文件中的常量池中。当类加载进入方法区时,就会把该常量池中的内容放入方法区中的运行时常量池。此外也可以在程序运行期间,将新的常量放入运行时常量池,比如String.intern()
方法,该方法先从运行时常量池中查找是否有该值,如果有,则返回该值的引用,否则将该值加入运行时常量池。
实例详讲
class Demo1_Car{
public static void main(String[] args) {
Car c1 = new Car();
//调用属性并赋值
c1.color = "red";
c1.num = 8;
//调用行为
c1.run();
Car c2 = new Car();
c2.color = "black";
c2.num = 4;
c2.run();
}
}
Class Car{
String color;
int num;
public void run() {
System.out.println(color + ".." + num);
}
}
首先运行程序,Demo1_car.java就会变为Demo1_car.class,Demo1_car.class加入方法区,检查是否字节码文件常量池中是否有常量值,如果有,那么就加入运行时常量池。
遇到main方法,创建一个栈帧,入虚拟机栈,然后开始运行main方法中的程序。
Car c1 = new Car(), 第一次遇到Car这个类,所以将Car.java编译为Car.class文件,然后加入方法区.然后new Car(),在堆中创建一块区域,用于存放创建出来的实例对象,地址为0X001.其中有两个属性值color和num。默认值是null和 0
然后通过c1这个引用变量去设置color和num的值,调用run方法,然后会创建一个栈帧,用来存储run方法中的局部变量等。run 方法中就打印了一句话,结束之后,该栈帧出虚拟机栈。又只剩下main方法这个栈帧。
接着又创建了一个Car对象,所以又在堆中开辟了一块内存,之后就是跟之前的步骤一样了。
创建对象过程
虚拟机在遇到一条new指令时,会首先检查这个指令的参数是否可以在方法区中定位到一个类的符号引用,并且检查这个符号引用所代表的类是否已经被加载,解析和初始化过。如果没有,则必须先执行类加载过程.
类加载完之后,需要为对象分配内存,有两种分配内存的方法
指针碰撞法(要求堆内存规整)
Java堆中空闲内存和已使用内存分别存放在堆的两边,中间存放一个指针作为分界点的指示器,在为对象分配内存时只需要将指针向空闲区域移动创建对象所需要的内存大小即可。
空闲列表法
如果堆内存中已使用内存区域和空闲区域相互交错,此时虚拟机需要维护一个列表,记录哪些内存块是可用的,在分配时从列表中找到一块足够大的内存区域划分给对象实例并更新列表上的记录。
多线程情况下,线程同时分配内存可能会造成冲突,比如使用指针碰撞法,线程A正在分配内存,还没有改变指针指向,线程B,又同时使用原来的指针进行内存分配。防止冲突有两种方法
CAS
操作:虚拟机采用CAS
操作,加上失败重试的方式保证内存分配的原子性本地线程分配缓冲(
TLAB
):预先为线程分配一部分堆内存空间(线程私有,所以不存在同步问题)用于对象实例的内存分配。只有当TLAB
用完,需要分配新的TLAB
时,才需要进行同步操作。
内存分配完之后,虚拟机需要将分配到的内存空间均初始化为零值(不包括对象头)。在虚拟机中,执行完new指令后会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来
对象在内存中的布局
对象在内存中的布局如下图所示,分为对象头、实例数据、对齐填充
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
对象头(可以参考Java锁升级)
mark Word
, 用于存储对象自身的运行时数据,如哈希码、GC
分代年龄以及锁状态标志等。类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据
对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。
对齐填充
并非必然存在,仅仅起着占位符的作用。
对象的访问定位
Java程序需要通过栈上的reference数据来操作堆上的具体对象。共有两种策略进行对象的访问定位
句柄访问
Java堆中划分出一块内存来作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息,需要两次寻址。
直接指针访问
Java堆中对象的布局中需要考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。
使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中实例数据指针,而reference本身不需要修改。
问题
只需要记住一件事,就是Java对象的内存分配均是在堆中进行的。所以对象都存储在堆中。
但是有人可能会怀疑方法的临时变量不是存储在虚拟机栈中吗?这里我要解释一下,虚拟机栈维护了一个局部变量表,表中存储的是对象的引用,而真正存储对象的地方在堆,如果局部变量都在堆里分配,那么虚拟机栈早爆满了
同样类的静态变量,有人又会怀疑在方法区中存储。其实不是的,方法区只存储引用,具体对象是存储在堆中的,具体实现可以发现,类静态对象是与class对象一起分配的内存。
参考
深入理解java虚拟机
来源:https://blog.csdn.net/zycxnanwang/article/details/106139451


猜你喜欢
- Android中实现进度条有很多种方式,自定义进度条一般是继承progressBar或继承view来实现,本篇中讲解的是第二种方式。先上效果
- 假定你已经了解了运行时的数据区域和常用的垃圾回收算法,也了解了Hotspot支持的垃圾回收器。一、cpu占用过高cpu占用过高要分情况讨论,
- 我们经常会使用springboot创建web应用,在springboot中金静态资源是如何存放的呢?静态资源映射规则我们先创建一个sprin
- Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的
- ObjectUtils.isEmpty()和null区别分配内存和赋值的区别isEmpty():判断值是否为空,即使已经分配内存,但没有赋值
- 1.泛型概念泛型就是将类型参数化所谓类型参数化就是将类型定义成参数的形式,然后在使用此类型的时候的时候再传入具体的类型到这我们可以看出来:泛
- 今天遇到一个需求,需要处理通过接口传过来的一个参数,参数内容为一个拼接好的Url地址,且该地址还会携带了一些额外的参数,包括但不限于数字,字
- 引言CardView是Android 5.0系统之后引入的众多控件之一,实现之后的效果也是比较酷的,它经常被用在RecyclerView和L
- 1.关于7z首先在这里先介绍一下7z压缩软件,7z是一种主流的 压缩格式,它拥有极高的压缩比。在计算机科学中,7z是一种可以使用多种压缩算法
- 一、前言介绍本系统前端框架采用了比较流行的渐进式JavaScript框架Vue.js。使用Vue-Router和Vuex实现动态路由和全局状
- 场景女朋友最近被安排了一个企业微信添加客户的沙雕活,然后跟我吐槽说,每天都要加,都想离职了,为了不让女朋友那么难受,突然想到可以使用Robo
- 一、题目描述题目:有五个学生,每个学生有 3 门课的成绩,从键盘输入以上数据(包括学生号,姓名,三门课成绩),把这些数据存放在磁盘文件 &q
- 引言垃圾收集技术并不是Java语言首创的,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。垃圾收集技术需要
- 目录问题为每个request设置超时值Http Handler给Request加上超时处理抛出正确的异常使用Handler总结HttpCli
- 目录MeasureSpecLayoutParamViewViewGroupTextColumn使用总结
- 前言在阅读本文之前, 希望你可以思考一下下面几个问题, 带着问题去阅读文章会获得更好的效果。发送消息的时候, 当Broker挂掉了,消息体还
- 装箱和拆箱是值类型和引用类型之间相互转换是要执行的操作。 1. 装箱在值类型向引用类型转换时发生2. 拆箱在引用类型向值
- ava:采用大端字节序存储数据【低地址存放数据的高位,高地址存放数据的低位,数据高位存放在数组的前面】windows(intel平台):采用
- 目录LinkedHashMap 实现继承 LinkedHashMap组合 LinkedHashMap链表 + HashMap 实现LRU,即
- 注:作者使用IDEA + Gradle注:需要有一定的java SpringBoot and SSM+Springcloud基础程序测试错误