TLAB、指针碰撞和空闲列表

new对象与指针碰撞

new对象怎么就出问题了呢?
java中我们要创建一个对象,用关键字new就可以了。但是,在我们日常中,有很多生命周期很短的对象。比如:

public void dome(){
    User user=new user();
    user.sayhi();
}

这种对象的作用域都不会逃逸出方法外,也就是说该对象的生命周期会随着方法的调用开始而开始,方法的调用结束而结束。
假设JVM所有的对象都放在堆内存中(为什么用假设,因为JVM并不是这样)一旦方法结束,没有了指向该对象的引用,该对象就需要被GC回收,如果存在很多这样的情况,对GC来说压力山大呀。

那么什么又是指针碰撞呢?
假设JVM虚拟机上,堆内存都是规整的。堆内存被一个指针一分为二。指针的左边都被塞满了对象,指针的右变是未使用的区域。每一次有新的对象创建,指针就会向右移动一个对象size的距离。这就被称为指针碰撞。

 

图1.png

好,问题来了。如果我们用多线程执行刚才的dome方法,一个线程正在给A对象分配内存,指针还没有来的及修改,同时为B对象分配内存的线程,仍引用这之前的指针指向。这样就出现毛病了。
(要注意的是,上面两种情况解决方案不止一个,我今天主要是讲TLAB,其他方案自行查询)

TLAB的出现

我们现在已经搞清楚,我们出现了哪些问题。我在为大家介绍一下今天的主角。

TLAB的全称是Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个线程专用的内存分配区域。

如果设置了虚拟机参数 -XX:UseTLAB,在线程初始化时,同时也会申请一块指定大小的内存,只给当前线程使用,这样每个线程都单独拥有一个空间,如果需要分配内存,就在自己的空间上分配,这样就不存在竞争的情况,可以大大提升分配效率。

TLAB空间的内存非常小,缺省情况下仅占有整个Eden空间的1%,也可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。

TLAB的本质其实是三个指针管理的区域:start,top 和 end,每个线程都会从Eden分配一块空间,例如说100KB,作为自己的TLAB,其中 start 和 end 是占位用的,标识出 eden 里被这个 TLAB 所管理的区域,卡住eden里的一块空间不让其它线程来这里分配。

TLAB只是让每个线程有私有的分配指针,但底下存对象的内存空间还是给所有线程访问的,只是其它线程无法在这个区域分配而已。从这一点看,它被翻译为 线程私有分配区 更为合理一点
当一个TLAB用满(分配指针top撞上分配极限end了),就新申请一个TLAB,而在老TLAB里的对象还留在原地什么都不用管——它们无法感知自己是否是曾经从TLAB分配出来的,而只关心自己是在eden里分配的。

TLAB的缺点

事务总不是完美的,TLAB也又自己的缺点。因为TLAB通常很小,所以放不下大对象。
1,TLAB空间大小是固定的,但是这时候一个大对象,我TLAB剩余的空间已经容不下它了。(比如100kb的TLAB,来了个110KB的对象)
2,TLAB空间还剩一点点没有用到,有点舍不得。(比如100kb的TLAB,装了80KB,又来了个30KB的对象)
所以JVM开发人员做了以下处理,设置了最大浪费空间。
当剩余的空间小于最大浪费空间,那该TLAB属于的线程在重新向Eden区申请一个TLAB空间。进行对象创建,还是空间不够,那你这个对象太大了,去Eden区直接创建吧!
当剩余的空间大于最大浪费空间,那这个大对象请你直接去Eden区创建,我TLAB放不下没有使用完的空间。

当然,又回造成新的病垢。
3,Eden空间够的时候,你再次申请TLAB没问题,我不够了,Heap的Eden区要开始GC,
4,TLAB允许浪费空间,导致Eden区空间不连续,积少成多。以后还要人帮忙打理。

对于Java开发来说,在虚拟机内存管理的帮助下,不需要为每个新的对象在代码层面分配内存,回收内存,比如像C语言那样操作。所以在正常情况下,内存泄露和内存溢出等问题也不太容易出现。所以要是运行中的程序出现了内存泄露问题,排查还是有一定困难。

Java堆是被所有线程共享的一块内存区域,主要用于存放对象实例,为对象分配内存就是把一块大小确定的内存从堆内存中划分出来,通常有指针碰撞和空闲列表两种实现方式。

1.指针碰撞法
假设Java堆中内存时完整的,已分配的内存和空闲内存分别在不同的一侧,通过一个指针作为分界点,需要分配内存时,仅仅需要把指针往空闲的一端移动与对象大小相等的距离。使用的GC收集器:Serial、ParNew,适用堆内存规整(即没有内存碎片)的情况下。

2.空闲列表法
事实上,Java堆的内存并不是完整的,已分配的内存和空闲内存相互交错,JVM通过维护一个列表,记录可用的内存块信息,当分配操作发生时,从列表中找到一个足够大的内存块分配给对象实例,并更新列表上的记录。使用的GC收集器:CMS,适用堆内存不规整的情况下。

内存分配并发问题

在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:

  • CAS: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
  • TLAB: 为每一个线程预先分配一块内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配。

如果想多了,创建一个对象还是挺麻烦的,需要这么多步骤,那么我们在开发过程中尽量非必须的对象创建呢?

创建对象有以下几个要点

  1. 类加载机制检查:JVM首先检查一个new指令的参数是否能在常量池中定位到一个符号引用,并且检查该符号引用代表的类是否已被加载、解析和初始化过
  2. 分配内存:把一块儿确定大小的内存从Java堆中划分出来
  3. 初始化零值:对象的实例字段不需要赋初始值也可以直接使用其默认零值,就是这里起得作用
  4. 设置对象头:存储对象自身的运行时数据,类型指针
  5. 执行<init>:为对象的字段赋值

学习前人笔记,记录一下!

参考:

https://www.jianshu.com/p/eaef248b5a2c

https://juejin.im/post/5b7d69e4e51d4538ca5730cb(java jvm内存区域)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值