Java内存区域记录

jvm运行图

这里写图片描述

程序计数器(Program counter Regiter)PC
  1. 程序计数器是一块较小的内存空间,可以把它看做是当前线程所执行的字节码的行号指示器。

  2. 字节码解释器工作就是通过它来选取下一条需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能。

  3. Java虚拟机是多线程,所以每一条线程都有一个这样的PC,互不影响,独立存储。这类内存区域是“线程私有的”。

  4. 如果执行的是Java方法,pc记录的是正在执行的字节码指令的地址。如果是本地方法,则为空。所有是唯一一个没有规定内存溢出(OutOfMemoeryError)情况的区域。


java 虚拟机栈
  1. ​ 线程私有,它的生命周期与线程相同。
  2. 描述的是Java方法执行的内存模型:每个方法执行时都会创建一个栈帧用于存储局部表量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程
  3. 局部变量表存放了编译器可知的各种基本数据类型(boolean、byte,char,short、int …….)、对象引用类型(reference类型)和retuenAddress类型(一条字节码指令的地址)。
  4. 在Java虚拟机规范中,对这个区域规定了两种异常情况:
    • 堆栈溢出(Stack OverflowError):如果线程请求的栈深度大于虚拟机所允许的深度,将抛出此异常。
    • 内存溢出(OutOfMemoryError):运行动态扩展的情况下,无法申请到足够的内存,将抛出此异常。
本地方法栈
  1. 与虚拟机栈所发挥的作用非常相似,他们之间的区别不过是虚拟机为虚拟机执行Java方法服务,本地方法栈为虚拟机所用到的本地方法服务。
  2. 在虚拟机规范中对本地方法栈中方法中使用的语言、使用方法与数据结构并没有强制规定,所以有的虚拟机直接把本地方法栈和虚拟机栈合二为一。同样也会抛出与虚拟机栈同样的异常。
Java堆
  1. ​ 被所有线程共享的一块内存区域,在虚拟机启动时创建。

  2. 唯一的目的是存放对象实例,几乎所有的对象实例都在这里分配内存。(随着JIT编译器的发展和逃逸分析技术逐渐成熟,所有对象的分配在堆上也渐渐的不是那么“绝对”

  3. 是垃圾收集器管理的主要区域,因此也叫GC堆。

  4. 从内存回收的角度来看,现在收集器基本采用分代收集算法,所以细分为新生代老年代;再细致一点的有:

    Eden空间、From Survivor空间、To Survivor空间等。无论怎么划分都与存放内容无关,存储仍然是对象实例,进一步划分只是为了更好的回收内存。

  5. Java堆参数设置 -Xms(最小值)、——Xmx(最大值)。

方法区(Method Area)
  1. 与堆栈一样,是各个线程共享的内存区域。
  2. 用于存储已被虚拟机加载的类信息、常量、静态常量、即时编译器编译后的代码等数据。
  3. Java虚拟机规范把方法区描述为堆得一个逻辑部分,但是它却有一个别名叫做Non-Heep(非堆),目的应该是与Java堆区分开。
  4. 这个区域的内存回收目标主要针对常量池的回收和对类型的卸载,一般来说回收比较难以令人满意。

运行时常量池(Runtime Constant Pool)
  1. 方法区的一部分。Class文件除了类信息外,还有一项就是常量池,用于存放编译器形成的各种字面量和符号引用,这部分内容将在类加载后进入运行时常量池中存放。
  2. java虚拟机对class文件格式有着严格的规范要求,只有符合规范的class文件才能被虚拟机装载和执行,但对于运行时常量池,java虚拟机规范没有做任何要求,不同实现的虚拟机可以自己实现这个内存区域。
直接内存
  1. 并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但也被经常使用。
  2. 在jdk1.4中新加入了NIO(New Input/Output)类,引入了基于通道与缓冲区的I/O方式,他可以利用本地函数库直接分配堆外内存,然后通过存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。能在一些场合显著提高性能。
  3. 会受到本机总内存大小以及处理器寻址空间的限制,所以一定要考虑这块。
对象是如何创建的
  1. 创建对象通常仅仅是一个new关键字而已,当虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有则必须先执行类加载的过程。

    类加载检查通过后,接下来虚拟机将为新生对象分配内存,对象所需要的内存大小在类加载完成后便可以完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。

  • 指针碰撞:假设Java堆中内存是绝对规整的,所有用过的内存放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存仅仅是把那个指针向空闲空间那边移动一段与对象大小相等的距离。
  • 空闲列表:假设Java堆中内存不是规整的,虚拟机就必须维护一个列表,记录上那些内存块是可用的,在分配的时候从列表中找到一块和对象内存大小足够大的空间进行划分,并更新列表上记录。

选择哪种分配方式是由Java堆是否规整决定,而Java堆是否完整又由所采用的垃圾处理器是否带有压缩整理功能决定。


  1. 除划分空间之外,还需要考虑线程安全的问题,解决这类问题有两种方案:

    • 对分配内存空间的动作进行同步处理—虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。

    • 本地线程分配缓冲(TLAB):把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存。

      虚拟机是否使用TLAB 可以通过 -XX:+/-UserTLAB 参数来设定。

  2. 内存完成分配之后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),如果需要TLAB,这一工作也可以提前至TLAB分配时进行。
    虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象哈希码、对象的GC分代年龄,这些信息存放在对象的对象头中。

至此,从虚拟机的角度来看,一个新的对象已经产生了,但从Java程序来看还需要执行 **

对象的内存布局
  • 对象头(Header)
  1. 第一部分用于存储对象本身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这些数据的长度官方称它为:“Mark Down

    Mark Down被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的信息,它会根据对象的状态复用自己的存储空间。

  2. 另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

  • 实例数据(Instance Data)

    对象真正存储的有效信息,从分配的策略来看,相同宽度的字段总是被分配在一起。

  • 对齐填充(padding)

    仅仅起着占位符的作用,由于HotSpot VM 的自动内存管理系统要求对象起始地址必须是8字节的整数倍,就是说对象的大小必须是8字节的整数倍。

    当对象实例部分数据部分没有对齐时,就需要通过对其填充来补全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值