jvm-垃圾回收(基础知识)

垃圾回收

      • 垃圾回收相关概述
        • 什么是垃圾回收
        • 什么是GC
        • STW
        • 并⾏与并发
          • 并发(Concurrent)
          • 并⾏(Parallel)
          • ⼆者对⽐
        • System.gc()
        • 安全点与安全区域
          • 安全点
          • 安全区域
        • GC分类
        • GC触发条件
          • 年轻代(Minor GC)触发条件
          • ⽼年代(Major GC)触发条件:
          • FULL GC触发条件

垃圾回收相关概述

什么是垃圾回收

垃圾指的是在运⾏程序中没有任何指针(或引⽤)指向的对象,这个对象就是需要回收的垃圾。 如果不及时对内存中的垃圾进⾏清理,那么这些垃圾对象所占⽤的内存空间⼀直保留到应⽤程序结束,被保留的空间⽆法被其他对象使⽤。可能会导致内存溢出。

对于⾼级语⾔来说,如果不进⾏垃圾回收,因为不断分配内存⽽不进⾏回收,内存早晚会被消耗完。除了释放没有⽤的对象,垃圾回收也可以清除内存⾥的碎⽚,碎⽚整理将所占⽤的堆内存移动到堆的⼀端,便于JVM将整理出内存分配给新的对象。特别是⼤的对象,可能需要⼀块连续的⼤的内存空间。

什么是GC

垃圾回收(Garbage Collection)作为⼀⻔实⽤⽽⼜᯿要的技术,可以说拯救了⽆数苦于内存管理的程序员。尽管很多⼈认为,GC技术⾛进⼤众的视ᰀ,多是源于Java语⾔的崛起,但是GC技术本身却相当的古⽼。早在1960年,Lisp之⽗John McCarthy已经在其论⽂中发布了GC算法,Lisp语⾔也是第⼀个实现GC的语⾔。

在 GC 最开始设计时,⼈们在思考 GC 时就需要完成三件事情:

  • 哪些内存需要进⾏回收?
  • 什么时候对这些内存进⾏回收?
  • 如何进⾏回收?

垃圾回收与“java⾯向对象编程”⼀样是java语⾔的特性之⼀;它与“ c/c++语⾔”最⼤区别是不⽤⼿动调⽤ free() 和 delete() 释放内存。GC 主要是处理 Java堆Heap ,也就是作⽤在 Java虚拟机 ⽤于存放对象实例的内存区域,(Java堆⼜称为GC堆)。JVM能够完成内存分配和内存回收,虽然降低了开发难度,避免了像C/C++直接操作内存的危险。但也正因为太过于依赖JVM去完成内存管理,导致很多Java开发者不再关⼼内存分配,导致很多程序低效、耗内存问题。因此开发者需要主动了解GC机制,充分利⽤有限的内存的程序,才能写出更⾼效的程序。

STW

Stop-the-World,简称STW,指的是GC事件发⽣过程中,会产⽣应⽤程序的停顿。停顿是产⽣时整个应⽤程序线程会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW。Stop-the-world意味着 JVM由于要执⾏GC⽽停⽌了应⽤程序(⽤户线程、⼯作线程)的执⾏,并且这种情形会在任何⼀种GC算法中发⽣。当Stop-the-world发⽣时,除了GC所需的线程以外,所有线程都处于等待状态直到GC任务完成。
jvm-垃圾回收(基础知识)_第1张图片
STW事件和采⽤哪款GC⽆关,所有的GC都有这个事件。哪怕是G1也不能完全避免Stop-the-world情况发⽣,只能说垃圾回收器越来越优秀,回收效率越来越⾼,尽可能缩短了暂停时间。

STW是JVM在后台⾃动发起和⾃动完成的,在⽤户不可⻅的情况下,把⽤户正常的⼯作线程全部停掉。

随着应⽤程序越来越复杂,每次GC不能保证应⽤程序的正常运⾏。⽽经常造成STW的GC跟不上实际的需求,所以才需要不断对GC进⾏优化。事实上,GC优化很多时候就是指减少Stop-the-world发⽣的时间,从⽽使系统具有⾼吞吐 、低停顿的特点。

并⾏与并发
并发(Concurrent)

在操作系统中,是指⼀个时间段中有个⼏个程序都处于已启动运⾏到运⾏完毕之间,且这⼏个程序都是在同⼀个处理器上运⾏。

并发并不是真正意义上的"同时进⾏",只是CPU把⼀个时间段划分成⼏个时间⽚段(时间区间),然后在这⼏个时间区间之间来回切换,由于CPU处理的速度⾮常快,只要时间间隔处理得当,即可让⽤户感觉是多个应⽤程序是同时进⾏的。
jvm-垃圾回收(基础知识)_第2张图片

并⾏(Parallel)

当系统有⼀个以上CPU时,当⼀个CPU执⾏⼀个进程时,另外⼀个CPU可以执⾏另⼀个进程,两个进程互不抢占CPU资源,可以同时进⾏,我们称之为并⾏(Parallel);

其实决定并⾏的因素不是CPU的数ᰁ,⽽是CPU的核⼼数ᰁ,⽐如⼀个CPU多个核可以并⾏。

适合科学计算,后台处理等弱交互场景。
jvm-垃圾回收(基础知识)_第3张图片

⼆者对⽐

并发,指的是多个事情,在同⼀时间段内同时发⽣了。
并⾏,指的是多个事情,在同⼀时间点上同时发⽣了。

并发的多个任务之间是互相抢占资源的。
并⾏的多个任务之间是不互相抢占资源的。

只有在多个CPU或者⼀个CPU多核的情况中,才会发⽣并⾏。
否则,看似同时发⽣的事情,其实都是并发执⾏的。

System.gc()

在默认情况下,通过System.gc()或者Runtime getRuntime().gc()的调⽤,会显示触发Full GC,同时对⽼年代和新⽣代进⾏回收,尝试释放被丢弃对象的占⽤的内存。

然⽽System.gc()调⽤附带⼀个免责声明,⽆法保证对垃圾收集器的调⽤。

JVM实现者可以通过System.gc()调⽤在决定JVM的GC⾏为。⽽⼀般情况下,垃圾回收应该是⾃动进⾏的,⽆须⼿动触发,否则就太过于麻烦了。在⼀些特殊情况下,如我们正在编写⼀个性能基准,我们可以在运⾏之间调⽤System.gc()。

public class SystemGCTest {
	protected void finalize() throws Throwable{
		super.finalize();
		System.out.println("SystemGCTest 重写finalize方法");
    }
	public static void main(String[] args) {
		new SystemGCTest();
		//提醒jvm的垃圾回收器执行gc,但是不确定是否马上执行gc,与Runtime.getRuntime().gc();作用一致
		System.gc();  
		//强制调用使用引用的对象Finaliza方法
		System.runFinalization(); 
	}
}
安全点与安全区域
安全点

jvm-垃圾回收(基础知识)_第4张图片
程序执⾏时并⾮在所有的地⽅都能停顿下来开始执⾏GC,只有在特定的位置才能停顿下来开始GC,这些位置称为"安全点(safepoint)"。

Safe Point的选择很᯿要,如果太少可能导致GC等待的时间太⻓,如果太频繁可能导致运⾏时的性能问题。⼤部分指令的执⾏时间都⾮常短暂,通常会根据"是否具有让程序⻓时间执⾏的特征"为标准。

  • ⽐如:选择⼀些执⾏时间较⻓的指令作为Safe Point,如⽅法调⽤、循环跳转和异常跳转等。

如何在GC发⽣时,检查所有线程都跑到最近的安全点停顿下来呢?

  • 抢先式中断
    • ⾸先中断所有线程。如果还有线程不在安全点,就恢复线程,让线程跑到安全点。
  • 主动式中断
    • 设置⼀个中断标志,各个线程运⾏到Safe Point的时候主动轮询这标志,如果中断标志为真,则
      将⾃⼰进⾏中断挂起。
安全区域

SafePoint机制保证了程序执⾏时,在不太⻓的时间内就会遇到可进⼊GC的Safepoint。但是程序"不执⾏"的时候呢?例如线程处于Sleep状态或Blocked状态,这时候线程⽆法响应JVM的中断请求,"⾛"到安全点去中断挂起,JVM也不太可能等待线程被唤醒。对于这种情况,就需要安全区域(SafeRegion)来解决。

安全区域是在在⼀段代码⽚段中,对象的引⽤关系不会发⽣变化,在这个区域中的任何位置开始GC都是安全的。我们也可以把Safe Region看做是被扩展了的Safepoint。
实际执⾏时:

  • 当线程运⾏到Safe Region的代码是,⾸先标识已经进⼊了Safe Region,如果这段时间内发⽣GC,
    JVM会忽略标识为Safe Region状态的线程。
  • 当线程即将离开Safe Region时,会检测JVM是否已经完成GC,如果完成了,则继续运⾏,否则线程必须等到知道收到可以安全离开Safe Region 的信号为⽌。
GC分类

JVM在进⾏回收时,是针对不同的内存区域进⾏回收的,⼤多数的回收指的是对新⽣代的回收。
jvm-垃圾回收(基础知识)_第5张图片

针对HotSpot VM的实现,它⾥⾯的GC其实准确分类只有两⼤种:

  • Partial GC:并不收集整个GC堆的模式。其中⼜分为
    • 新⽣代的回收:(Minor GC/Young GC),只收集新⽣代的GC
    • ⽼年代的回收:(Major GC/Old GC),只收集⽼年代的GC(⽬前只有CMS的concurrent collection是这个模式,只收集⽼年代。)。
    • Mixed GC:收集整个young gen以及部分old gen的GC(只有G1有这个模式)。
  • Full GC:收集整个堆,包括young gen、old gen、perm gen(如果存在的话)等所有部分的模
    式。

Major GC通常是跟full GC是等价的,收集整个GC堆。但因为HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,当有⼈说“major GC”的时候⼀定要问清楚他想要指的是上⾯的full GC还是old GC。

约定: 新⽣代/新⽣区/年轻代 养⽼区/⽼年区/⽼年代/年⽼代 永久区/永久代
从次数上讲:

  • 频繁收集年轻代
  • 较少收集⽼年代
  • 基本不动⽅法区
GC触发条件

最简单的分代式GC策略,按HotSpot VM的serial GC的实现来看,触发条件是:

年轻代(Minor GC)触发条件

⼀般情况下,所有新⽣成的对象⾸先都是放在新⽣代的。新⽣代内存按照 8:1:1 的⽐例分为⼀个eden区和两个survivor(from survivor,to survivor)区,⼤部分对象在Eden区中⽣成。

在进⾏垃圾回收时,先将eden区存活对象复制到from survivor区,然后清空eden区,当这个from survivor区也满了时,则将eden区和from survivor区存活对象复制到to survivor区,然后清空eden和这个from survivor区,此时from survivor区是空的,然后交换from survivor区和to survivor区的⻆⾊(即下次垃圾回收时会扫描Eden区和to survivor区),即保持from survivor区为空,如此往复。

特别地,当to survivor区也不⾜以存放eden区和from survivor区的存活对象时,就将存活对象直接存放到⽼年代。如果⽼年代也满了,就会触发⼀次FullGC,也就是新⽣代、⽼年代都进⾏回收。注意,新⽣代发⽣的GC叫做MinorGC,MinorGC发⽣频率⽐较⾼,不⼀定等 Eden区满了才触发。

Minor GC触发⽐较频繁,⼀般回收速度也是⽐较快的。Minor GC会引发STW,暂停⽤户线
程,等待垃圾回收完毕后,⽤户线程才会恢复。
jvm-垃圾回收(基础知识)_第6张图片

⽼年代(Major GC)触发条件:

由Eden区、from survivor区向to survivor区复制时,对象⼤⼩⼤于to survivor可⽤内存,则把该对象转存到⽼年代,会先尝试触发Minor GC,如果之后空间还是不⾜,则会触发Major GC。

如果Major GC后还是不⾜ 就会OOM。

发⽣Major GC,通常伴随Minor GC,但这并不是绝对的,Parallel Scavage这种收集器就有直接进⾏Major GC的策略过程。

说明:Major GC的速度⼀般会⽐Minor GC的速度慢10倍以上。

FULL GC触发条件
  • System.gc()⽅法的调⽤
    • 此⽅法的调⽤是建议JVM进⾏Full GC,虽然只是建议⽽⾮⼀定,但很多情况下它会触发 Full GC,从⽽增加Full GC的频率,也即增加了间歇性停顿的次数。建议能不使⽤此⽅法就别使⽤,让虚拟机⾃⼰去管理它的内存,可通过通过-XX:+DisableExplicitGC来禁⽌RMI(Java远程⽅法调⽤)调⽤System.gc。
  • ⽼年代空间不⾜
    • ⽼年代空间只有在新⽣代对象转⼊及创建为⼤对象、⼤数组时才会出现不⾜的现象,当执⾏Full GC后空间仍然不⾜,则抛出如下错误: java.lang.OutOfMemoryError: Java heap space 为避免以上两种状况引起的FullGC,调优时应尽ᰁ做到让对象在Minor GC阶段被回收、让对象在新⽣代多存活⼀段时间及不要创建过⼤的对象及数组。
  • ⽅法区空间不⾜
  • JVM规范中运⾏时数据区域中的⽅法区,在HotSpot虚拟机中⼜被习惯称为永⽣代或者永⽣区,Permanet Generation中存放的为⼀些class的信息、常ᰁ、静态变ᰁ等数据,当系统中要加载的类、反射的类和调⽤的⽅法较多时,Permanet Generation可能会被占满,在未配置为采⽤CMS GC的情况下也会执⾏Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:java.lang.OutOfMemoryError: PermGen space 为避免Perm Gen占满造成Full GC现象,可采⽤的⽅法为增⼤Perm Gen空间或转为使⽤CMSGC
  • 通过Minor GC后进⼊⽼年代的平均⼤⼩⼤于⽼年代的可⽤内存
    • 如果发现统计数据说之前Minor GC的平均晋升⼤⼩⽐⽬前old gen剩余的空间⼤,则不会触发Minor GC⽽是转为触发full GC
  • 由Eden区、from survivor区向to survivor区复制时,对象⼤⼩⼤于to survivor可⽤内存,则把该对象转存到⽼年代,且⽼年代的可⽤内存⼩于该对象⼤⼩

你可能感兴趣的:(jvm,jvm,java,垃圾回收)