JVM(2)-Java内存区域与内存溢出异常

本文主要介绍Java运行时数据区域,包括程序计数器、Java虚拟机栈、本地方法栈、Java堆和方法区,阐述各区域的功能和特点。还揭秘了HotSpot对象,涵盖对象创建的流程、内存布局及访问定位方式,涉及线程安全等问题。

运行时数据区域

我们这节关注中间灰色部分,注意绿色部分 线程共享,黄色部分 线程私有

程序计数器

一块较小的内存,是当前线程(线程私有)执行的字节码的行号指示器

  • 如果执行的是Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果是Native(本地方法),计数器值为空(Undefined)
  • 唯一没有OutOfMemoryError的区域

Java虚拟机栈

虚拟机栈是描述Java方法执行的内存模型

每个方法调用至完成,对应一个栈帧在虚拟机栈中入栈到出栈的过程。

局部变量表:

  • 存放基本数据类型对象引用returnAddress
  • 局部变量表所需的内存空间在编译期完成,该空间是确定的,方法运行期间不改变

两种异常:

  • StackOverflowError: 请求的栈深度超过允许范围
  • OutOfMemoryError: 需要的内存超过允许范围

本地方法栈

本地方法栈是描述本地方法执行的内存模型。它和Java虚拟机栈很类似,区别就是它是为本地方法服务的。

Java堆

存放对象示例,也称作(GC堆),是垃圾回收器管理的主要区域。

  • 细分 新生代、老生代;Eden空间、From Survivor空间、To Survivor空间等
  • 线程共享的堆里可能划分出私有的分配缓冲区(TLAB)。

方法区

存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

运行时常量池 : 存放编译器生成的各种字面常量和符号引用。

例子:String类在常量池的数据结构类似于HashSet,是唯一的。

String s1 = "abc";
String s2 = "abc"; // 存放在方法区的常量池(字节码常量)唯一

System.out.println(s1 == s2); // true

String s3 = new String("abc"); // 用了new,所以在堆中创建

System.out.println(s1 == s3); // false

System.out.println(s1 == s3.intern()); // true
复制代码

注意哦jdk1.8 String常量池搬到了堆中。如果想对intern()了解更多,可以看我的这篇博客

HotSpot对象揭秘

对象创建

对象创建简单描述:

new -> 根据常量池中符号看是否需要加载 -> 类加载 -> 分配内存 -> 初始化 ->构造方法

给对象分配内存的两种方法:

  • 指针碰撞
  • 空闲列表

解决创建对象时线程安全问题两种方法:

  • 分配内存动作进行同步处理
  • TLAB(共享堆中按线程划分的线程私有部分)上分配,它分配完,再同步锁定

对象的内存布局

3块区域:

  • 对象头

    • 自身运行时的数据(MarkWord):HashCode、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等
    • 类型指针:对象指向它的类元数据的指针,虚拟机可以根据它来确定该对象是哪个类的实例。

    注意如果对象是数组,那么对象头中还有一块用于记录数组长度的数据。

  • 实例数据

  • 对齐填充 :当实例部分没对齐时,通过对齐填充来补全

对象的访问定位

  • 句柄访问(2级指针)

可以看出来要到实例数据必须再经过一个指针,这么做到好处是,当对象移动时,我们只需要改变局柄池里的指针,不需要改变reference;缺点是速度慢。

  • 直接指针

直接指针顾名思义直接指向实例数据,好处是速度快;缺点就是对象移动要改reference。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值