java 内存不足-java栈内存与堆内存
我们在写java代码创建每一种对象后,jvm是怎么给它们分配内存的呢?
上图:
分配原则
一般来讲,new一个对象后,内存一般分配在堆空间中,但也有一些例外。有些对象会分配在栈上或者TLAB中。如果可以在栈上分配,就直接在栈上分配,不行就会进行TLAB分配,再不行就判断是否是大对象,大对象直接进入老年代,再不行就分配到eden区,eden若是空间不够,就会进行一次MinorGC。
大对象
顾名思义就是很大的对象,需要大量的连续内存空间,jvm会让这种对象直接进入老年代,减少eden和两个survivor区发生大量的内存复制,提高效率。
栈上分配
大家都知道,在jvm中堆是线程共享的,也就是说堆里存的东西对于所有的线程都是可见的,可访问的,虚拟机中的垃圾回收才可以回收堆中的没有被引用的对象。但是,若是有对象的作用域不会逃离方法之外,那么,这个对象就可以分配在栈中。随着方法的结束而销毁,无需回收。这就是栈上分配
TLAB分配
本地线程分配缓冲(Thread Local Allocation Buffer即TLAB,为每⼀个线程预先分配⼀块内存,JVM在给线程中的对象分配内存时,⾸先在TLAB分配。
由于对象一般会分配在堆上,而堆是全局共享的。因此在同一时间,可能会有多个线程在堆上申请空间。为了保证同一块内存的线程安全JVM有两种方式
一:CAS,⽐较和交换(Compare And Swap): CAS 是乐观锁的⼀种实现⽅式。所谓乐观锁就是,每次不加锁⽽是假设没有冲突⽽去完成某项操作java 内存不足,如果因为冲突失败就重试,直到成功为⽌。虚拟机采⽤ CAS 配上失败重试的⽅式保证更新操作的原⼦性。
但是很多线程同时申请内存时。CAS效率就会变得低下,所以,JVM在给线程中的对象分配内存时,⾸先在TLAB分配,当对象⼤于TLAB中的剩余内存或TLAB的内存已⽤尽时,再采⽤上述的CAS进⾏内存分配。
在给对象分配内存时,每个线程使用自己的TLABjava 内存不足,这样可以避免线程同步,提高了对象分配的效率。 TLAB本身占用eEden区空间,在开启TLAB的情况下,虚拟机会为每个Java线程分配一块TLAB空间。TLAB空间的内存非常小, 缺省情况下仅占有整个Eden空间的1%, 由于TLAB空间一般不会很大,因此大对象无法在TLAB上进行分配,总是会直接分配在堆上。TLAB空间由于比较小,因此很容易装满。
比如,一个100K的空间,已经使用了80KB, 当需要再分配一个30KB的对象时,肯定就无能为力了。这时虚拟机会有两种选择,第一,废弃当前TLAB,这样就会浪费20KB空间;第二,将这30KB的对象直接分配在堆上,保留当前的TLAB。
个人学习整理,有错欢迎纠正。