专栏首页kevindroidJVM学习笔记——Java内存区域与内存溢出异常

JVM学习笔记——Java内存区域与内存溢出异常

运行时数据区域

其中,其中Method Area 和 Heap 是线程共享的 ,VM Stack,Native Method Stack 和Program Counter Register 是非线程共享的。JVM初始运行的时候都会分配好 Method Area(方法区) 和Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器) , VM Stack(虚拟机栈)和Native Method Stack (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。

程序计数器

程序计数器是一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。为了线程切换后能够恢复到正确的位置。 如果线程执行java方法,计数器记录正在执行的虚拟机字节码指令的地址,如果是native方法,值为空。 简单地讲,一个Native Method就是一个java调用非java代码的接口。

Java虚拟机栈

描述的是一个java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量,操作数栈,动态链接,方法出口等信息。 这个区域有两种异常情况:线程请求的栈深度>JVM允许的深度,导致stackoverflow错误; 扩展时无法获取足够的内存,导致OutOfMemoryError错误。

本地方法栈

与java虚拟机栈类似,不同的是存储的是本地方法。

java堆

线程共享的内存区域,在虚拟机启动时创建,用来存放对象实例。 java堆是垃圾收集器管理的主要区域,由于垃圾收集器都采用分代收集算法,一般分为新生代与老年代,再细致一点分为Eden空间,From Survivor空间,To Survivor空间。 java堆可以处于物理上不连续但逻辑上连续的空间。

方法区

线程共享的内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。又被称为“永久代”,因为GC分代扩展到了方法区,或者说使用永久代来实现方法区而已。 和java堆一样,只需要逻辑上连续的空间。还可以选择不实现垃圾收集,因为这个区域的内存回收目标主要针对常量池的回收以及类型的卸载,但是类型的卸载条件相当严格,所以回收效率不高。

运行时常量池

方法区的一部分,存放编译器生成的各种字面量和符号引用。JVM规范并未对这部分做严格要求,所以提供商可以按照自己的要求实现这部分。运行时常量池具有动态性,运行期的常量也可以放入池中,如String类的intern()方法。

直接内存

这部分并不是虚拟机运行数据区的一部分,也不是JVM规范中定义的内存区域。虽然本机直接内存的分配不会受到java堆的影响,但是还会受到本机总内存以及处理器寻址空间的限制。

HotSpot对象

对象的创建

指针碰撞:假设java堆中内存是绝对规整的,中间放着一个指针作为分界点的指示器,分配内存只需把指针向空闲空间那边移动一段与对象大小相等的距离。 空闲列表:内存不规整,虚拟机必须维护一个表,记录哪些内存块是可用的,从中找到一块足够大的内存分配给实例。 对象创建时的冲突:创建对象是一个非常频繁的行为,并发情况下修改指针位置并不是线程安全的,可能出现给A对象分配内存是指针还未来得及修改B对象就是用这个指针进行下一步操作。有两种解决方案:

  • 对分配内存空间的动作进行同步处理
  • 把内存分配的动作按照线程划分到不同的空间中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB)

对象的内存布局

对象中的内存布局可以分为三个区域:对象头(Header),实例数据(Instance Data),对齐填充(Padding)。

对象头

对象头包括两部分信息,一部分用于存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等,被称为“Mark Word”。在32位与64位的虚拟机中分别位32bit以及64bit。在32bit状态下,25bit用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0,其他情况下存储状况如下:

存储内容

标志位

状态

对象的哈希码,分代年龄

01

未锁定

指向锁记录的指针

00

轻量级锁定

指向重量级锁的指针

10

膨胀(重量级锁定)

空,不需要记录信息

11

GC标记

偏向线程ID,偏向时间戳,对象分代年龄

01

可偏向

对象头的另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针判断这个对象是哪一个类的实例。并不是所有虚拟机都必须这一部分。

实例数据

对象真正存储的有效信息,即程序代码中定义的各种类型的字段内容,无论是父类继承的还是子类定义的都需要记录。存储顺序受虚拟机分配参数以及字段在java源码中的定义顺序的影响。

对齐填充

不是必须存在的,因为虚拟机的内存管理系统要求对象的起始地址必须是8字节的整数倍,即对象的大小必须是8字节的整数倍,当对象实例数据部分没有对齐时,需要通过对齐填充来补全。

对象的访问定位

java程序通过栈上的reference数据来操作堆上具体的对象,但是虚拟机并没有定义该通过何种方式区定位,访问堆中的对象的具体位置。目前的主流访问方式有使用句柄和直接访问两种。

句柄访问

直接指针访问

句柄访问的优势:reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,reference本身不需要修改。 直接指针访问的优点:速度快,节省了一次指针定位的时间开销。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JVM学习笔记——垃圾收集器与内存分配策略(1)

    上一篇文章介绍了java运行时内存的各个区域,其中虚拟机栈,程序计数器,本地方法栈三个区域随线程而生,随线程而灭。栈中的栈帧随着方法的进入和退出有条不紊的执行着...

    用户1665735
  • java多线程——线程的状态

    用户1665735
  • JVM学习笔记——java内存模型与线程(1)

    多任务处理出现的重要原因是计算机的运算速度与存储及通信子系统的速度差距太大,大量的时间花费在磁盘I/O,数据库访问或者数据库访问上。除了充分利用计算机处理器的能...

    用户1665735
  • Java中的内存泄漏学习

    Java中的内存泄漏学习   Java语言的一个关键的优势就是它的内存管理机制。你只管创建对象,Java的垃圾回收器帮你分配以及回收内存。然而,实际的情况并没...

    用户1289394
  • 深入理解Java中的内存泄漏内存泄漏内存泄漏发生的原因造成内存泄露的常见情形内存泄露的解决方案

    Java的一个最显著的优势是内存管理。你只需要简单的创建对象而不需要负责释放空间,因为Java的垃圾回收器会负责内存的回收。然而,情况并不是这样简单,内存泄露还...

    desperate633
  • 【专业技术】介绍Java中的内存泄漏

    Java语言的一个关键的优势就是它的内存管理机制。你只管创建对象,Java的垃圾回收器帮你分配以及回收内存。然而,实际的情况并没有那么简单,因为内存泄漏在Jav...

    程序员互动联盟
  • 深入理解java虚拟机笔记(一)-java内存区域与内存溢出

    前言 这是深入理解Java虚拟机一书的笔记,来自第二章。因为这本书讲的比较深奥,这是第二次看,需要记录一下笔记。 2. 运行时数据区域 java虚拟机所管理的内...

    奋斗蒙
  • jvm gc 线程

    缺点:对 CPU 资源敏感、无法收集浮动垃圾、标记 —— 清除 算法带来的空间碎片

    Dean0731
  • Java编程思想学习录(连载之:一切都是对象)

    CodeSheep
  • jvm系列:jvm知识点总览

    在江湖中要练就绝世武功必须内外兼备,精妙的招式和深厚的内功,武功的基础是内功。对于武功低(就像江南七怪)的人,招式更重要,因为他们不能靠内功直接去伤人,只能靠招...

    Java高级架构

扫码关注云+社区

领取腾讯云代金券