• Java堆是虚拟机所管理内存最大的一块区域,并且一个JVM实例只有一个堆,堆是Java内存管理的核心区域。
  • Java堆是被所有线程共享的,但是Java堆中还可以划分线程私有的缓冲区(Thread Local Allocation Buffer, TLAB),用来提高对象分配时的效率。
  • Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
  • Java的程序中,“几乎”所有的对象实例都在这里分配内存。

这里有特殊情况 逃逸分析技术的日渐强大 栈上分配、标量替换(后续说明)导致了”几乎”这一情况的发生

  • 方法结束后,堆中的对象不会马上移除,仅仅在垃圾收集的时候才会被移除。
  • 堆是垃圾收集的重点对象。

堆空间内部结构

我认为之所以这种分代设置都是为了方便垃圾收集,基于分代思想,一个应用程序中对象的声明周期不尽相同,每次垃圾回收肯定不能堆空间全部扫描,这样效率太低了,所以才有了对象分代这个思想,不是为了分代而分代,而是为了程序更好的性能,就像不是为了多线程而去盲目使用多线程,而是根据实际情况来使用,一切都要考虑实际情况。

这里不讨论永久代(元空间)!

年轻代、老年代

存储在JVM中的Java对象有两类:

  1. 一类是生命周期特别短,就像是你写的普通方法中声明的局部变量,大部分都是这样的情况,这类对象创建和消亡都十分迅速。
  2. 另一类对象的声明周期十分的长,这样的对象一般存放在老年代。
  • 几乎所有的Java对象都是在Eden区被new出来的。
  • 绝大部分的Java对象的销毁都是在新生代进行的。
  • “-Xmn参数可以设置新生代最大内存大小”

设置堆内存大小

  • 堆的大小在JVM启动时就已经设定好了,可以通过选项“-Xmx”和“-Xms”来进行设置。

“-Xms”用于表示堆区的起始内存,等价于-XX:InittalHeapSize

“-Xmx”用于表示堆区的最大内存,等价于-XX:MaxHeapSize

  • 堆中的内存大小超过“-Xmx“指定的最大内存的时候,就会抛出OutOfMemoryError异常。

  • 一般会将-Xms和-Xmx两个参数配置相同的值,目的是为了能够在java垃圾回收机制清理完堆区后不需要重新计算堆区的大小,从而提高性能。

  • 默认情况下,初始内存大小:物理电脑内存大小/64;最大内存大小:物理电脑内存大小/4;

测试

1
2
3
4
5
6
7
8
9
10
public class Test {
public byte[] buffer = new byte[1024 * 1024];

public static void main(String[] args) {
List<Test> list = new ArrayList<>();
for (int i = 0; i < 8; i++) {
list.add(new Test());
}
}
}

这个结果很明显了正常情况下程序肯定不会OOM -Xms -Xmx 改变了堆的大小,导致这一情况发生。

对象分配的过程

  1. new的对象先放到Eden区,如果超大对象直接分配到老年代。
  2. 当Eden区空间填满时,创建新的对象,就会触发JVM的垃圾回收机制,对新生区进行垃圾回收(Minor GC),然后在加载新的对象放到Eden区。
  3. 将Eden区中剩余的对象移动到幸存者0区。
  4. 如果再次触发垃圾回收机制,幸存的对象(包括幸存者0区的对象)会全部放入幸存者1区。如此反复
  5. 当一个对象经历15次这样的操作后,历经磨难,就会进入养老区。

15次只是JVM一个默认值,-XX:MaxTenuringThreshold= 这个参数可以设置 15次在Hotspot虚拟机中是最大值。

上面的描述实际上很不全面,你有可能会想:

进行垃圾回收之后Eden还是空间不足怎么办?

垃圾回收后将存活的对象移动到其中的一个幸存区的时候内存不够怎么办?

新对象难道必须经过15次洗礼才能进入到老年区养老吗?

利用具体的流程图来解释

具体的流程图:

这个图算是比较详细的,解释了上面的几个问题。

Minor GC、Major GC、Full GC

JVM在进行GC的时候,肯定不是这三个区域一块回收,要是这样分代有什么意义。大部分回收的是新生代。


Minor GC/Young GC:只是新生代的垃圾收集。

Major GC/Old GC:只是老年代的垃圾收集

只有CMS GC会有单独收集老年代的行为。其它的情况就是Full GC。

还有一种混合收集(Mined GC)只有G1会有这种行为,G1这个垃圾收集器组成很特殊。

Full GC:收集整个java堆和方法区的垃圾。

GC触发机制

Minor GC

  1. 当年轻代空间不足时,就会触发Minor GC,这里的年轻代满指的是Eden代满,Survivor区满不会触发GC。Survivor区只能等待Eden区被动清理。这个清理会清理整个新生代。
  2. Minor GC十分频繁(这里的频繁是相对来说的),回收速度非常快。
  3. Minor GC会导致STW,暂停用户线程,等待垃圾回收结束,用户线程才恢复运行。

Major GC、Full GC

  1. 出现Major GC,一般会伴随至少一次的Minor GC(大部分垃圾收集器都是这样Parallel Scavenge这个比较特殊)。
  2. 如果Major GC后内存还不足,那就完蛋,直接OOM。
  3. Major GC 很慢,STW时间很长(相对Minor GC)。

TLAB

作用

对象的创建在堆中十分频繁,因为堆是线程共享的,在并发的环境下在堆区中划分内存空间是线程不安全的,为了避免多个线程操作同一地址,需要使用加锁的机制,这导致了分配速度有所影响,但是TLAB是堆中开辟的每个线程私有的,就缓解了这个问题。

上面之所以说的缓解是因为并不是全部的对象都是这种方式分配内存的,也不可能这样,这个TLAB的空间非常小,仅仅为Eden空间的1%,-XX:TLABWasteTargetPercent可以设置,虽然只是缓解,但是这也是JVM分配内存的一个首选方式。

概念

  • TLAB是JVM为每个线程分配的一个私有缓存区域,这个区域在Eden空间内。

  • TLAB快速分配内存,这种内存分配方式称为快速分配策略。

  • -XX:UseTLAB 设置是否开启TLAB。

  • 一旦对象在TLAB空间分配内存失败时,JVM就会尝试通过使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存。

分配内存的方式在垃圾那章节说明!

堆空间各种参数总结

参数 解释
-XX: +PrintFlageInitial 查看所有的参数的默认初始值
-XX:MaxTenuringThreshold 设置新生代对象最大年龄
-Xms 初始堆空间内存(默认本机物理内存1/64)
-Xmx 最大堆空间内存(默认本机物理内存1/4)
-Xmn 设置新生代大小
-XX:NewRatio 配置新生代与老年代在堆结构的占比
-XX:SurvivorRatio 设置新生代中Eden和s0/s1空间比例
-XX:+PringGCDetails 输出详细的GC处理日志
-XX:HandlePromotionFailure 是否设置空间分配担保(具体百度这个参数我仅仅了解)

jinfo查看默认参数

逃逸分析

这个博客写的不错:我确实对这个理解不够,不做总结。

https://www.jianshu.com/p/580f17760f6e

结论

平常写代码的时候能使用局部变量的,就不要使用在方法外定义。