对象分配过程
1. 常规
-
1.new的对象先放Eden区。此区有大小限制。
-
2.当Eden的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对Eden区进行垃圾回收(MinorGC/Yong GC),将Eden区中的不再被其他对象所引用的对象(不可达对象)进行销毁。再加载新的对象放到Eden区
-
3.然后将伊甸园中的剩余对象移动到survivor0区。
-
4.如果再次触发垃圾回收(此时会连带着存放对象的survivor0区一块),此时上次幸存下来的放到survivor1区的,
-
5.如果没有回收,就会放到survivor1区,如果再次经历垃圾回收,此时会把没回收的都放到survivor0区。 也就是每次回收都会把幸存下来的(Eden区和存放幸存者的survivor区)存放到空的幸存者区。
-
6.如此往复,当一个对象存活次数达到15次,也就是对象age达到15(默认是15)就会晋升到Old区 当Old区内存不足时,再次触发GC:Major GC/Full GC(取决于是否为CMS回收器,只有CMS回收器有单收集Old区行为),进行Old区的内存清理 若Old区执行了Major GC/Full GC之后,发现依然无法进行对象的保存,就会产生OOM异常。 可以设置参数:-Xx:MaxTenuringThreshold= N进行设置
当然,以上是正常的对象的分配过程,也会有一下特殊情况
2. 动态对象年龄判断
他的大致规则就是,假如说当前放对象的Survivor区域里,一批对象的总大小大于了这块Survivor区域的内存大小的50%,那么此时大于等于这批对象年龄的对象,就可以直接进入老年代了
假设Survivor0区(100M空间大小)有两个对象,这俩对象的年龄一样,都是2岁,然后俩对象加起来内存超过了50MB,这个时候,Survivor0区里大于等于2岁的对象,都要进入老年代里去。这就是动态年龄判断的规则,这条规则也会让一些年轻代的对象进入老年代。实际这个规则运行的时候是如下的逻辑:年龄从⼩到⼤进⾏累加,当加⼊某个年龄段后,累加和超过survivor区域TargetSurvivorRatio的时候,就从这个年龄段⽹上的年龄的对象进⾏晋升。
3. 大对象直接进入老年代
意思就是如果你要创建一个大于这个大小的对象,比如一个超大的数组,或者是别的啥东西,此时就直接把这个大对象放到老年代里去,压根不会经过年轻代。之所以这么做,是因为要避免年轻代里出现那种大对象,然后屡次躲过GC,还得把他在两个Survivor区域里来回复制多次之后才能进入老年代。
4. Survivor区满了
-
特别注意,在Eden区满了的时候,才会触发MinorGC,而Survivor区满了后,不会触发MinorGC操作
如果Survivor区满了后,将会触发一些特殊的规则,也就是可能直接晋升Old区
TLAB(Thread Local Allocation Buffer)
由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的,为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度。
从内存模型而不是垃圾收集的角度,对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内。多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略。
-
尽管不是所有的对象实例都能够在TLAB中成功分配内存,但JVM确实是将TLAB作为内存分配的首选。可以通过选项“-Xx:UseTLAB”设置是否开启TLAB空间。
-
默认情况下,TLAB空间的内存非常小,仅占有整个Eden空间的1%,可以通过选项“-Xx:TLABWasteTargetPercent”设置TLAB空间所占用Eden空间的百分比大小。
-
一旦对象在TLAB空间分配内存失败时,JVM就会尝试着通过使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存。
⚠️ 堆空间不都是共享的,因为还有TLAB这个概念,在堆中划分出一块区域,为每个线程所独占
Minor GC,Major GC,Full Gc,Mixed GC
Minor GC:
当年轻代空间不足时,就会触发MinorGC,这里的年轻代满指的是Eden区满,Survivor满不会引发GC。(每次Minor GC会清理年轻代的内存。)因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。这一定义既清晰又易于理解。Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行。
Minor GC后Eden区是空的,会把Eden中的所有活的对象都移到Survivor区域中,如果Survivor区中放不下,那么剩下的活的对象就被移到Old generation 中。
Major GC
指发生在老年代的GC,对象从老年代消失时,只有CMS回收器有单收集Old区行为
Full GC
整堆收集,收集整个Java堆和方法区的垃圾收集触发
Full GC执行的情况有如下五种:
-
调用System.gc()时,系统建议执行Full GC,但是不必然执行
-
老年代空间不足
-
方法区空间不足 (永久代或者元空间)
-
通过Minor GC后进入老年代的平均大小大于老年代的可用内存
-
由Eden区、survivor spacee(From Space)区向survivor spacel(To Space)区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
⚠️Full GC 是开发或调优中尽量要避免的。这样暂时时间会短一些
⚠️Major GC 和 Full GC出现STW的时间,是Minor GC的10倍以上
Mixed GC
收集整个young gen以及部分old gen的GC(只有G1有这个模式)