? Heap堆区,用于存放对象实例和数组的内存区域
? Heap堆区,是JVM所管理的内存中最大的一块区域,被所有线程共享的一块内存区域。堆区中存放内存实例,“几乎”所有的对象实例以及数组都在这里分配内存。
? 每一个JVM进程值存在一个堆区,它在JVM启动时被创建,JVM规范中规定堆区可以是物理上不连续的内存,但必须是逻辑上连续的内存。
对象逃逸分析
? Java世界中“几乎”所有的对象都在堆中分配,但是,随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。
? 从JDK 1.7开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。
? 从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以JVM中的堆区旺旺进行分代划分,例如:新生代和老年代。目的是更好地回收内存,或者更快地分配内存。
? 堆区地组成分为新生代(Young Generation)、老年代(Old Generation)。新生代被分为伊甸区(Eden)和幸存区(from+to),幸存区又被分为Survivor 0(from)和Survior 1(to)。
? 新生代和老年代的比例为1:2,伊甸区和s0、s1比例为8:1:1,不同区域存放对象的用途和方式不同:
堆空间的内存大小是可以修改的,默认情况下,初始堆内存为物理内存的1/64,最大为物理内存的1/4。
Heap堆区中的新生代、老年代的空间分配比例,通过java -xx:+PrintFlagsFinal -version命令查看:
上述输出结果分析:
InitialSurvivorRatio = 8
新生代Young(Eden/Survivor)空间的初始比例 = 8:代表Eden占新生代空间的80%
uintx NewRatio = 2
老年代Old/新生代Young的空间比例 = 2:代表老年代Old是新生代Young的2倍
? 因为新生代是由Eden + s0 + s1组成的,所以按照上述默认比例,如果Eden区内存大小是40M,那么两个Survivor区就是5M,整个新生代区就是50M,然后可以算出老年代Old区内存大小是100M,堆区总大小就是150M。
? 创建一个新对象,在堆中的内存分配。
? 大部分情况下,对象会在Eden区生成,当Eden区装填满的时候,会触发Young Garbage Collection,即YGC垃圾回收的时候,在Eden区实现清楚策略,没有被引用的对象则直接收回。
? 依然存活的对象会被送到Survivor区。Survivor区分为s0和s1两块内存区域。每次YGC的时候,他们将存活的对象复制到未使用的Survivor空间(s0或s1),然后将当前正在使用的空间完全清除,交换两块空间的使用状态。每次交换时,对象的年龄会+1。
? 如果YGC要移送的对象大于Survivor区容量的上限,则直接移交给老年代。一个对象不可能永远呆在新生代,在JVM中一个对象从新生代晋升到老年代的阈值默认值为15,可以在Survivor区交换14次之后,晋升到老年代。
? 处于效率的缘故,JVM的垃圾收集器不会对三个区域(伊甸区、幸存区、老年代)进行收集,大部分时候都是回收新生代,HotSpot虚拟机将垃圾收集分为部分收集(Partial GC)和整堆收集(Full GC)。
部分收集:
整堆收集FGC(Full GC):回收整个Java堆区,默认堆空间使用到达80%(可调整)的时候会触发FGC。
? GC组合垃圾回收只有YGC和Full GC,Old GC不可以单执行。原因是OldGC是STW机制+标记整理算法,相对耗时只能在关键时刻使用,因此只有Full GC 才能触发Old GC。
GC垃圾回收的影响:
GC耗时太长、GC次数太多会影响进程的性能,导致进程响应变慢,或者无法响应。
YGC耗时:耗时在几十或着几百毫秒属于征程情况,用户几乎无感知,对程序影响比较少。耗时太长或者频繁,会导致服务器超时问题。
YGC次数:太频繁,会降低服务的整体性能。高并发服务时,影响会比较大。
FGC次数:越少越好。比较正常情况几个小时一次、或者几天一次。
FGC耗时:耗时很长会导致线程频繁被停止,使应用响应变慢,比较卡顿。
产生FGC的原因:
- 大对象:系统一次加载了过多数据到内存中,导致大对象进入老年代。
- 内存泄露:频繁创建了大量对象,但是无法被回收(比如IO流对象使用后未调用close方法释放资源),先引发FGC,最后导致OOM。
- 程序频繁生成一些长生命周期的对象,当这些对象呢的存活年龄超过分代奈年龄时便会进入老年代,最后引发FGC。
- 程序BUG导致动态生成了很多新类,使得Metaspace不断被占用,先引发FGC,最后导致OOM。
- JVM参数设置不合理:包括内存大小、新生代和老年代的大小、Eden区和Survivor区的大小、元空间大小、垃圾回收算法等等。
堆区最容易处出现的就是OutOfMemoeyError错误,这种错误的表现形式会有以下两种:
- OutOfMemoeyError:GC Overhead Limit Exceeded:当JVM花太多时间执行回收,并且只能回收很少的堆空间时,就会发生此错误。
- OutOfMemoryError:Java heap space:假如在创建新的对象时,堆空间中的空间不足以存放新创建的对象,就会引发此错误。
这种情况与配置的最大内存有关,且受制于物理内存的大小。