java是面向对象的语言,创建对象的方式有很多种通过关键字new、反射、克隆都可创建对象;比如最常使用的是使用关键字new创建对象,在jvm虚拟机执行到关键字new的时候会去堆内存中检查new出来的类是否已经被加载,如果没有被加载,首先会将类加载;通过检查这个动作之后会给这个new出来的对象分配内存,分配的内存主要用来存放对象的实例变量,
,在分配内存的过程中根据对象中实例变量中的具体情况确认需要分配内存空间的大小,然后从java堆内存中分出这样的一块区域用来存储对象的变量(没有被JIT优化的情况下),在 gc回收的过程中根据JVM使用的垃圾回收器的类型,因其回收算法不同,会导致堆中内存分配情况不同。如标记-清除算法回收后的内存中会有大量不连续的内存碎片,在给新的对象分配的时候,就需要通过"空闲列表"来确定一块空闲区域,无论使用哪种方法最终都要确定出一块内存区域,这一块内存区域用来存储创建的对象的引用,然后进行初始化的过程,一个情况下 这样能的分配内存是没有问题的,但是如果在并发的场景中是如何保证分配内存的线程是安全的,如何保不会有多个对象指向同一快内存区域
一般有两种方式解决:
1、确保分配内存的线程是安全的,给分配内存的这个操作加锁,对分配内存的动作做同步处理,采用CAS机制,配合失败重试的方式保证更新操作的线程安全性,这种方式可以解决问题,但是在jvm中穿件对象分配内存是一个很频繁的操作在每一次分配内存的动作上都要做安全同步,很显然采用这种方式的效率会很低;
2、在每个线程上先预先分配出一小块内存,然后该线程在创建对象分配内存的时候,使用预先非配出来的这块 “私有化” 内存,在是“私有化”内存上进行分配,当这个内存用完时在分配一块新的私有化内存;这一种方式称为TLAB(Thread Local Allocation Buffer)分配,这一块Buffer是从堆内存中分配出来的,但是是本地线程共独享的,需要重点关注的是,这一块内存我们说是TLAB独享的,但只是在分配内存的动作是独享的,在读取、使用,垃圾回收的动作上是共享的,并且在使用上和普通的堆存没有区别,另外,TLAB分配的内存的方式只作用在Elden 区,对于新分配的对象会首先分配在这块内存区域,新生代分配不了的大对象会直接分配到老年代,所以在java中多个小对象比一个大对象具有更高的内存分配效率
所以TLAB分配的内存存放在Eden区,是还是会被垃圾回收或者被移到Survivor 区、Old 区,
使用了TLAB之后,在TLAB上给对象分配内存时线程独享的了,这就没有冲突了,但是,TLAB这块内存自身从堆中划分出来的过程也可能存在内存安全问题,因此在对于TLAB 分配内存的过程还需要做同步安全的操作,相比于每次为单个对象划分内存时候对进行同步控制的要低的多**
-XX:+/-UseTLAB
参数来指定是否开启TLAB分配
总结:
1,堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的
2,JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer), 其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,
在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配
3、TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。
4 ,所有新创建的Object 都将会存储在新生代Yong Generation中。
如果Young Generation的数据在一次或多次GC后存活下来,那么将被转移到OldGeneration。 新的Object总是创建在Eden Space。
JVM优化之jvm分配内存如何保证线程安全
最新推荐文章于 2024-05-03 11:59:00 发布