JVM堆内存分配策略

之前不了解JVM的时候总是能找到一些JVM内存回收时候的一些策略,为了更好的学习JVM内存回收,所以我想到了既然操作系统在JVM启动的时候把内存分配给了JVM,JVM也会在内存满的时候除法回收,那JVM是怎么把内存分配的呢?

new 构造方法()

写java代码的都知道new对象,但是这个对象是怎么在new的时候分配内存的呢?

  • 在使用了new关键字之后JVM会先检测当前的类是否能在常量池中找到一个符号引用,如果没有就会执行类加载过程
  • 在类加载完成后,对象所需的内存空间大小就会确定下来
  • 内存分配
  • 并发new会出现的问题
  • 调用class文件的init方法进行对象的初始化

内存分配

首先new出来的对象会被分配到JVM的堆空间当中,这里JVM堆空间会出现两种状况,一种是内存空间连续的,一种是内存空间是非连续的

  • 内存空间连续的
    内存空间连续的是指内存空间一侧是已经分配出去的内存,一侧是未被分配出去的内存空间,他们中间有一个指针来标记当前分配出去的内存和未被分配内存的"界线",然后当某一个对象需要被分配出内存空间的时候会将当前的指针向未分配内存空间的方向挪动对象所需内存空间相等的大小距离,这样分配内存的过程被称为"指针碰撞",分配好了之后会将当前的指针的位置置为"0"。

我的疑问:连续内存空间的分配策略不会像操作系统连续空间存放一样出现碎片么?

  • 内存空间是非连续的
    内存空间非连续的时候JVM怎么管理内存呢?JVM会维护一个小本本,其实就是空闲列表(free list),在请求分配内存的时候JVM会根据这个小本本找到一个地址空间分配出去,然后更新当前的小本本上未被使用过的内存空间。

我的疑问:这个非连续的内存分配,是否和操作系统的段页式管理内存一样呢?

  • 造成内存连续和非连续的原因
    采用serial,parNew等回收算法的时候会将堆内存整理成一个整块使用的内存和一块非使用的内存,中间用指针来标记位置;采用标记-清除算法的时候会使内存空间产生较多的非连续的内存空间。

并发内存分配

在并发的时候,修改一个指针也会出现修改错误的情况,所以也要考虑并发的时候JVM如何保证内存不会出现多分配或者少分配的情况。加入A,B同时向JVM申请内存的时候,A先到达,B后到达,这时候A开始修改指针位置分配内存,可能出现B也在修改指针的位置分配内存,A还没有提交,B读指针位置还是老地方的指针。JVM有两种解决方案,一种是采用CAS,一种使用TLAB(Thread Local Allocation Buffer),为每个线程都独立分配一个线程独有的TLAB,每次分配内存的时候都先在这个地方进行分配,当这里分配满了,利用同步锁定的方式申请一个新的缓冲区。可以使用-XX:/-UseTLAB来指定。

对象的初始化设置

在分配完内存空间之后还没有new出来程序想要的对象,因为还要对对象进行一些必要的设置,不然JVM是不知道这个对象是哪个类的实例对象。所以JVM还会对对象头进行一些设置,比如对象是哪个类的实例,对象的哈希值,对象的GC分代年龄等信息

  • 对象的头(Object Header)包含了当前对象的类信息,如何存放的呢?有两种方式,一种是句柄池,一种是直接指针的方式。
  • 句柄池就是在堆内存中分配出一块地址空间,专门管理对象所指向的类名和数据,句柄池包括对象的实例数据的指针和对象的实例类型的指针(我猜是方法区的符号引用)
  • 直接指针就是在不利用额外的空间来管理对象的类型,而是在存放数据的头指针处高几位存放的是对象的实例类型指针,而低几位存放的是对象的数据(而非指针)
  • 所以在对象的内存地址发生改变的时候句柄池只需要修改句柄所指向的数据地址空间就可以了,但是在寻址的时候会产生额外的时间开销

句柄池就像间接寻址一样,存放的是指针的指针,需要寻址两次才能访问到真正的数据,而直接指针就像直接寻址一样,只需通过一次访问就可以拿到数据

对象的头(ObjectHeader)

包含了两类信息

  • 存储对象自身的运行时数据,哈希码,GC分代年龄,锁状态标志,线程持有的锁等
  • 存储对象当前的数据类型的元数据的指针,通过该指针去方法区能找到当前对象对应的类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值