目录
垃圾回收
当需要排查各种内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
堆空间的基本结构
Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 堆 内存中对象的分配与回收。
Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)。
从垃圾回收的角度来说,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆被划分为了几个不同的区域,这样我们就可以根据各个区域的特点选择合适的垃圾收集算法。
在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常分为下面三部分:
-
新生代内存(Young Generation):新生代主要存储新创建的对象。当对象被创建时,它们首先会被分配到新生代。
-
老生代(Old Generation):老年代主要存储长时间存活的对象,老年代的垃圾回收频率相对较低,因此其大小通常比新生代大得多。
-
永久代(Permanent Generation):然方法区和永久代都用于存储类的元数据信息,但它们之间有一些区别。方法区主要存储加载到JVM中的类的信息,而永久代则存储类的元数据信息,这些信息是类加载时所需要的。永久代的存在使得Java具有了动态加载类和实现类的反射机制等功能。
他们三个在堆中的占比为8:1:1,JDK 8 版本之后 PermGen(永久) 已被 Metaspace(元空间) 取代,元空间使用的是直接内存 。
内存分配和回收原则
分代收集机制
不同的分代内存回收机制也存在一些不同之处,在HotSpot虚拟机中,新生代被划分为三块,一块较大的Eden空间和两块较小的Survivor空间,默认比例为8:1:1,老年代的GC频率相对较低,永久代一般存放类信息等(其实就是方法区的实现)如图所示:
那么它是如何运作的呢?
首先,所有新创建的对象,在一开始都会进入到新生代的Eden区(如果是大对象会被直接丢进老年代),在进行新生代区域的垃圾回收时,首先会对所有新生代区域的对象进行扫描,并回收那些不再使用对象:
接着,在一次垃圾回收之后,Eden区域没有被回收的对象,会进入到Survivor区。在一开始From和To都是空的,而GC之后,所有Eden区域存活的对象都会直接被放入到From区,最后From和To会发生一次交换,也就是说目前存放我们对象的From区,变为To区,而To区变为From区:
接着就是下一次垃圾回收了,操作与上面是一样的,不过这时由于我们To区域中已经存在对象了,所以,在Eden区的存活对象复制到From区之后,所有To区域中的对象会进行年龄判定(每经历一轮GC年龄+1
,如果对象的年龄大于默认值为15
,那么会直接进入到老年代,否则移动到From区)
最后像上面一样交换To区和From区,之后不断重复以上步骤。
垃圾回收的分类可以分为Minor GC,Major GC, Full GC
-
Minor GC - 次要垃圾回收,主要进行新生代区域的垃圾收集。
-
触发条件:新生代的Eden区容量已满时。
-
-
Major GC - 主要垃圾回收,主要进行老年代的垃圾收集。
-
Full GC - 完全垃圾回收,对整个Java堆内存和方法区进行垃圾回收。
-
触发条件1:每次晋升到老年代的对象平均大小大于老年代剩余空间
-
触发条件2:Minor GC后存活的对象超过了老年代剩余空间
-
触发条件3:永久代内存不足(JDK8之前)
-
触发条件4:手动调用
System.gc()
方法
-
Minor GC 流程
空间分配担保
Survivor区无法容纳的对象直接送到老年代,让老年代进行分配担保(当然老年代也得装得下才行)在现实生活中,贷款会指定担保人,就是当借款人还不起钱的时候由担保人来还钱。
老年代
大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
大对象直接进入老年代的行为是由虚拟机动态决定的,它与具体使用的垃圾回收器和相关参数有关。大对象直接进入老年代是一种优化策略,旨在避免将大对象放入新生代,从而减少新生代的垃圾回收频率和成本。
-
G1 垃圾回收器会根据
-XX:G1HeapRegionSize
参数设置的堆区域大小和-XX:G1MixedGCLiveThresholdPercent
参数设置的阈值,来决定哪些对象会直接进入老年代。 -
Parallel Scavenge 垃圾回收器中,默认情况下,并没有一个固定的阈值(
XX:ThresholdTolerance
是动态调整的)来决定何时直接在老年代分配大对象。而是由虚拟机根