Java堆——Tlab分析,介绍

首先谈java对象的创建

我们在语言层面上,创建一个对象仅仅是一个new关键字可以解决的。

但是在JVM层面上是怎么创建一个对象的呢??

当 jvm 遇到 new 指令
1,检查指令的参数能否在常量池中定位到一个类的符号引用。并检查这个符号引用所代表的类是否已经被加载,解析,初始化过。
2,没有,就进行相应的类加载。
3,类加载完成后就知道对象所需内存大小,为对象进行内存空间分配,在将指针移动内存大小的位置(指针碰撞的分配方式)。

为什么类加载完成之后就知道对象所需要内存大小?
类加载完成之后,加载类的继承体系关系得以明确,父级继承链中所有非静态域成员的 FieldsLayout 和 size 就已知了,这个size就是当前加载类的 base 值;对于当前加载类,根据域成员的定义顺序,依次迭代成员域数组_fields,根据数组中的每一个域成员的类型,在 base 值的基础上累加该类型的偏移量……这样就能计算出来所有非静态成员的 size 总和;考虑到 VM 所属平台是否是64bit,或是32bit,指针大小是8字节还是4字节,在上述迭代过程中会对被迭代的每一个non_static_field做内存对齐处理(加上内存对齐导致的padded_offset)——以上过程称之为layout_fields;最后考虑到对象头的存在,再加上对象头的header_size(),就可以决定使用 new 指令创建对象时所需要 allocate 的堆内存大小了

什么是 “指针碰撞” 的分配方式?
假设 java 堆中内存都是绝对规整的,被使用的内存放在一边,空闲的内存放在一边,有一个指针指向交界处。根据类加载知道的对象 size 信息,JVM给这个要被 new 的对象分配 size 的内存,然后指向交界区的指针向前移动 size 大小的距离,这个过程就叫指针碰撞。

问题来了
指针碰撞的方式给创建的对象分配内存,看似只修改了一个指针指向的位置,就好像很安全的样子!其实在并发情况下并不是线程安全的。比如:

JVM一个线程正在给A对象分配内存,指针还没有来的及修改,其它为B对象分配内存的线程,还引用这之前的指针指向。这样就出问题了。

解决方法
1)对分配空间的动作进行同步处理——就是保证分配内存和修改指针两个动作原子性。
2)就是我们TLAB空间了(终于让本篇主角出场了)

TLAB自述

  Tlab(ThreadLocalAllocBuffer)是一个本地线程分配缓冲区,是线程的一块私有内存,如果设置了虚拟机参数 -XX:UseTLAB,在线程初始化时,同时也会申请一块指定大小的内存,只给当前线程使用,这样每个线程都单独拥有一个Buffer,如果需要分配内存,就在自己的Buffer上分配,这样就不存在竞争的情况,可以大大提升分配效率,当Buffer容量不够的时候,再重新从Eden区域申请一块继续使用,这个申请动作还是需要原子操作的。
  
  TLAB的目的是在为新对象分配内存空间时,让每个Java应用线程能在使用自己专属的分配指针来分配空间,均摊对GC堆(eden区)里共享的分配指针做更新而带来的同步开销。

  TLAB的本质其实是三个指针管理的区域:start,top 和 end,每个线程都会从Eden分配一块空间,例如说100KB,作为自己的TLAB,其中 start 和 end 是占位用的,标识出 eden 里被这个 TLAB 所管理的区域,卡住eden里的一块空间不让其它线程来这里分配。
  而 top 就是里面的分配指针,一开始指向跟 start 同样的位置,然后逐渐分配,直到再要分配下一个对象就会撞上 end 的时候就会触发一次 TLAB refill(重新申请一个TLAB)

想了解更加底层可以参考:http://www.kejixun.com/article/170523/330012.shtml

TLAB的难题

1,TLAB空间大小是固定的,但是这时候一个大对象,我TLAB剩余的空间已经容不下它了。
2,TLAB空间还剩一点点没有用到,有点舍不得。
  以上这种问题还是很低级的啊,JVM开发人员对这种很容易发生的问题,还是做了处理的。TLAB有一个参数 _refill_waste_limit——最大浪费空间。
  当剩余的空间小于最大浪费空间,那该TLAB属于的线程在重新向Eden区申请一个TLAB空间。进行对象创建,还是空间不够,那你这个对象太大了,去Eden区直接创建吧!
  当剩余的空间大于最大浪费空间,那这个大对象请你直接去Eden区创建,我TLAB放不下没有使用完的空间。

上面的问题这样解决了,但是新问题也来了啊!

3,Eden空间够的时候,你再次申请TLAB没问题,我不够了,Heap的Eden区要开始GC,然后吧啦吧啦GC涉及经典问题又带来了一路。
4,TLAB允许浪费空间,导致Eden区空间不连续,积少成多。以后还要人帮忙打理。

TLAB内心苦啊,本来为了线程安全而生,却还是有这么多问题缠身

这个TLAB事情也告诉了我们,学习一个知识,是为了提高自己。但是学习之路并不是平的,会带来很多的问题,所以要丰富自己,像JVM不停的版本迭代,吸纳新知识,就能好的解决知识的漏洞和欠缺点。

下一步该回顾我了解的GC了。

### JVM中指针碰撞机制 在JVM的内存管理中,指针碰撞是一种高效的内存分配策略。这种技术依赖于Java内存的高度规整性,即所有已使用的内存位于一侧,而空闲内存位于另一侧[^1]。在这种情况下,一个指针充当分界点,用于区分已使用和未使用的内存区域。当需要为新对象分配内存时,仅需将此指针向空闲内存的方向移动一段距离,这段距离等于所需分配的对象大小。 #### 指针碰撞的工作原理 具体而言,指针碰撞的过程如下: - **内存分区**:Java被划分为两部分——已分配的对象区和尚未分配的空闲区。 - **内存分配**:对于一个新的对象请求,垃圾收集器检查当前空闲区内是否有足够的空间来满足该请求。如果有,则调整指针的位置,并返回原指针位置作为新对象的起始地址[^4]。 - **垃圾回收与整理**:如果发现剩余的空间不足,那么会触发一次垃圾回收过程以清理不再引用的对象并释放其占用的空间。某些垃圾收集器还可能执行额外的操作,比如重新排列存活下来的对象以便恢复连续性的存储布局,进而继续支持指针碰撞的技术应用。 然而需要注意的是,只有当整个或者特定代内的内存保持高度紧凑状态时才能有效利用这一方法;否则就需要采取其他更复杂的分配方案如“自由列表”。 ### 多线程环境下的内存分配 尽管单线程环境下实现起来相对简单明了,但在现代应用程序通常运行于多核处理器之上且涉及多个并发工作的线程的情况下,如何高效安全地处理这些线程各自的内存需求成为了一个重要课题。 为了应对这种情况,JVM引入了一种称为Thread Local Allocation Buffer (TLAB) 的概念。每个线程都有自己独立的小型缓冲区(TLAB),允许它们在其内部自行进行快速简便的新建实例操作而不必每次都去竞争全局共享资源锁或同步访问公共的大块空间。这种方式不仅减少了因频繁加解锁带来的性能开销,同时也降低了不同线程间互相干扰的可能性,提高了整体系统的吞吐量以及响应速度。 一旦某个线程耗尽了自己的 TLAB 配额,则可以从主获取更多可用内存填充到自己的本地缓存里头直到再次达到上限为止。当然,在极端条件下也可能发生跨过界限的情况,此时便不得不回退至传统的集中管理模式下完成后续动作。 ```java // 示例代码展示如何通过设置参数启用/禁用 TLAB 功能 public class Main { public static void main(String[] args){ // 可通过 JVM 参数调节 TLAB 尺寸等相关配置 System.setProperty("UseTLAB", "true"); Object obj = new Object(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值