WatchDogs

Knowledge of Backend Development

0%

Java 垃圾回收

JVM运行时内存也叫做JVM堆,从GC的角度:
-新生代1/3

  • Eden区8/30
  • ServivorFrom区1/30
  • ServivorTo区1/30

-老年代2/3

-方法区(HotSpot)
(1.7时使用永久代实现,1.8中,数据被分到了元数据区和堆中,元空间存储类的元信息,静态变量和常量池等并入堆中。其中元空间不属于JVM内存区域,属于本地内存)

新生代

新生代的MinorGC采用复制算法实现

新生代主要存放新生成的对象,特点是对象数量多但生命周期短。在每次进行垃圾回收时都有大量对象被回收。

1把Eden区和ServivorFrom区中存活的对象复制到ServivorTo区,把这些对象的年龄+1。
如果某对象的年龄到达老年代的标准(默认15),则将其复制到老年代。
如果ServivorTo内存不足,也复制到老年代。
如果对象属于大对象(2k-128k),则也复制到老年代。

2清空Eden区和ServivorFrom区

3把ServivorTo区移动到ServivorFrom区。

老年代

老年代主要存放有长生命周期的对象和大对象。老年代的GC叫做MajorGC

老年代中的对象比较稳定,MajorGC不会被频繁触发。MajorGC被触发前,肯定是做过一次MinorGC,之后发现老年代空间不足或无法找到足够大的连续空间,才会触发MajorGC。

MajorGC采用标记清除(整理)法

MajorGC需要扫描整个老年代空间,所以耗时较长。同时标记清除法容易产生内存碎片。如果没有空间可以分配,则会抛出OOM error。

如何确定垃圾?

Java采用引用记数法可达性分析来确定对象是否应被回收。

引用记数法:为对象添加一个引用,则计数+1,删除一个引用,则计数-1。容易产生循环引用问题,两个对象相互引用,则可能无法被回收。

可达性分析:定义一些GCRoot对象,从root起点向下搜索,搜索不到就是不可达。若对象和GCRoot不可达两次,则对象被回收。

一个对象可以属于多个root,GC root有几下种:

  • Stack Local - 在虚拟机栈(栈帧中的本地变量表)中引用的对象

  • Static - 静态属性引用的对象,Java 类引用类型静态变量

  • Const - 常量引用的对象,如字符串常量池里的引用

  • JNI Local - 本地方法栈中引用的对象

  • Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots。

  • Monitor Used - 用于同步的监控对象,如被 synchronized锁住的对象

  • Thread - 活着的线程

  • Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM并没有为这些对象提供其它的信息,因此需要去确定哪些是属于”JVM持有”的了。

垃圾回收算法

1 标记清除法:先对要回收的对象进行标记,然后释放内存空间。容易产生内存碎片化。

2 复制算法: 将内存划分为两块大小相等的区域,新生成的对象被放在区域1。区域1满时,对里面的对象标记,对于标记之后仍存活的对象,复制到区域2中。然后清理整个区域1.复制算法高效,易于实现,不会出现碎片。但同一个时刻只有一个区域可以用,可用空间是原来的一半。内存存在浪费。系统若有大量存活对象,剩余空间又不多时,会频繁触发复制算法,使存活对象在两个区来复制,影响效率。该算法只在对象为“朝生夕死”状态时效率最高。

3 标记整理法: 结合了前两种的优点,标记阶段和标记清除法的标记阶段相同,标记完成后,将存活的对象集中复制到内存的一端连续的区域,然后清除其它区域。

4 分代收集法: 根据对象不同类型将内存划分为不同区域,在不同区域根据其特点选择不同的算法。

四种引用类型

在Java中,一切皆对象。对象的操作是通过该对象的引用(Reference)实现的。
Java的引用类型一共有四种。

  • 强引用
    最常见的引用,在把一个对象赋给一个引用变量时,这个引用变量就是一个强引用。有强引用的对象一定为可达性状态,不会被垃圾回收机制回收。强引用是造成内存泄露的主要原因。
  • 软引用
    通过SoftReference实现。软引用是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统内存不足时该对象才会被回收。像这种如果内存充足,GC时就保留,内存不够,GC再来收集的功能很适合用在缓存的引用场景中。在使用缓存时有一个原则,如果缓存中有就从缓存获取,如果没有就从数据库中获取,缓存的存在是为了加快计算速度,如果因为缓存导致了内存不足进而整个程序崩溃,那就得不偿失了。
  • 弱引用
    通过WeakReference。当一个对象被作为Key存在Map中被Map引用时,如果这个对象没有其他强引用,这时Map中的引用也就没有了意义。而且也无法被删除。设计 WeakHashMap,使用弱引用作为Key,解决了上述问题。如果一个对象只有弱引用,则在垃圾回收过程中一定会被回收。ThreadLocal中的 key使用了弱引用。
  • 虚引用
    可以用来跟踪对象被垃圾回收的活动。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。虚引用与软引用和弱引用的一个区别在于:虚引用的get方法始终返回null,虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

分区收集法

分区收集法将整个堆空间划分为连续的大小不同的区域,对每个小区域都单独进行内存使用和垃圾回收。这样可以根据每个小区域内存大小灵活使用和释放内存。

分区收集法根据系统可接受的停顿时间,每次都快速回收若干个小区域的内存,以缩短垃圾回收时系统停顿的时间。最后以多次并行累加的方式逐步完成整个内存区域的垃圾回收。如果垃圾回收机制一次回收整个堆内存,则需要更长的系统停顿时间。长时间的系统停顿将影响系统运行的稳定性。

垃圾收集器

新生代:

  • Serial 单线程复制算法
    在他正在进行垃圾收集时,必须暂停其他所有工作线程,直到垃圾收集结束。对于单CPU来说,没有线程交互开销,可以获得最高的单线程垃圾收集效率。是Java虚拟机在Client模式下新生代默认垃圾收集器。
  • ParNew 多线程复制算法
    采用多线程模式工作,除此之外跟Serial几乎一样。在垃圾收集过程中会暂停所有其他工作线程,是虚拟机在Server模式下的新生代默认垃圾收集器它默认开启与CPU同等数量的线程进行垃圾回收,在Java应用启动时可通过-XX:ParallelGCThreads参数调节ParNew垃圾收集器工作线程数。
  • Parallel Scavenge 多线程复制算法
    它是为提高新生代垃圾收集效率而设计的垃圾收集器基于多线程复制算法实现,在系统吞吐量上有很大优化,可以更高效的利用CPU尽快完成垃圾回收任务。它通过自适应调节策略提高系统吞吐量,提供了三个参数用于调节,控制垃圾回收的停顿时间及吞吐量:控制最大垃圾收集停顿时间-XX:MaxGCPauseMillis参数,控制吞吐量大小的-XX:GCTimeRatio参数,控制自适应调节策略开启与否的UseAdaptiveSizePolicy参数。

老年代:

  • Serial Old 单线程标记整理法
    是Serial垃圾收集器老年代实现。不同的是,SerialOld针对老年代长生命周期基于标记整理算法实现。JVM在Client下老年代默认垃圾收集器
  • Parallel Old 多线程标记整理法
    它在上优先考虑系统吞吐量,其次考虑停顿时间等。如果系统吞吐量要求较高,可以优先考虑与新生代的Parallel Scavenge 垃圾收集器配合使用。
  • CMS多线程标记清除法
    CMS垃圾收集器是为老年代设计的垃圾收集器,其主要目的是最短的垃圾回收停顿,基于线程的标记清除算法实现,以便在多线程并发环境下以最短的垃圾收集停顿时间提高系统的稳定性。
    CMS工作机制相对复杂,垃圾回收过程包含如下四个步骤
    ①初始标记:只标记和GCRoot直接关联的对象,速度很快,需要暂停所有工作线程。
    ②并发标记:和用户线程一起工作,执行GCRoots跟踪标记过程。
    ③重新标记:在并发标记过程中,用户线程继续运行,导致在垃圾回收过程中部分对象的状态发生变化,为确保这部分对象的状态正确性,需要对其重新标记并暂停工作线程。
    ④并发清除:和用户线程一起工作,执行清除GCRoots不可达对象的任务。
    CMS垃圾收集器在和用户线程一起工作时,不需要暂停用户线程,有效缩短了垃圾回收时系统的停顿时间,同时由于CMS垃圾收集器和用户线程一起工作,因此其并行度和效率也有很大提升。

G1 多线程标记整理法
G1(Garbage First)垃圾收集器为了避免全区域垃圾收集引起的系统停顿,将堆内存划分为大小固定的几个区域,独立式用这些区域的内存资源并且跟踪这些区域的垃圾收集进度,同事在后台维护一个优先级列表,在垃圾回收过程中根据系统允许的最长垃圾收集时间,优先回收垃圾最多的区域。G1垃圾收集器通过内存区域独立划分使用和根据不同优先级回收各区域垃圾的机制,确保了G1垃圾收集器在有限时间内获得最高的垃圾收集效率。相对于CMS收集器,G1垃圾收集器有两个突出的改进:1,基于标记整理算法,不产生内存碎片。2可以精确地控制停顿时间,在不牺牲吞吐量的前提下实现短停顿垃圾回收。