【深入理解java虚拟机】第三章 垃圾收集器与内存分配策略

程序计数器,虚拟机栈和本地方法栈随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出有条不紊得执行着出栈和入栈的操作,每个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此这几个区域的内存分配和回收都具备确定性,不需要过多考虑回收问题,因为方法结束或者线程结束时,内存自然就跟随着回收了;

而java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法的多个分支需要的内存也不可能一样,我们只要在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器关注的就是这部分内存。

一 对象存活判定方法:

引用计数法(reference counting):

基本思想:给对象添加一个引用计数器,每当有一个地方引用它时,计数器就加一;当引用失效时,计数器就减1;任何时刻计数器为0的对象就是不可能再被使用的。

主要问题:很难解决对象之间相互循环引用的问题;

可达性分析方法(reachability analysis):

基本思想:通过一系列的被称为“GC Roots”的对象作为起始点,从这些结点开始向下搜索,搜索的所走过的路径称为引用链(reference chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

java中可作为GC Roots的对象包括:

虚拟机栈(栈帧中的本地变量表)中引用的对象;方法区中静态属性引用的对象;方法区中常量引用的对象;本地方法栈中JNI(即一般所说的Native方法)引用的对象。

引用的分类:

强引用(Strong Reference):程序代码中普遍存在的,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象;

软引用(Soft Reference):还有用但是并不必须的对象。对于软引用关联的队形,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存异常。

弱引用(Weak Reference):用来描述非必须的对象,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

虚引用(Phantom Reference):最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间产生影响,也无法通过虚引用来取得一个对象实例;为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

另外:

即使在可达性分析中不可达的对象,也并不是非死不可的。这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程。首先,如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那警徽被第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize方法。(当对象没有覆盖finalize()方法,或者finalize方法已经被系统调用过,则虚拟机将会视为“没有必要执行”) 如果有必要执行finalize方法,那么这个对象将会放置到一个F-Queue队列中,稍后GC将会对F-Queue中的对象进行第二次小规模的标记,如果对象在finalize方法中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可。finalize方法是对象逃脱死亡命运的最后一次机会,任何一个对象的finalize方法只会被系统调用一次。

二 回收方法区

方法区(HotSpot中的永久代)垃圾收集主要回收两部分内容:废弃常量和无用的类

回收废弃常量与java堆中的对象非常类似,以常量池中字面量的回收为例,假如一个字符串“abc”已经进入了常量池,但当前系统没有任何一个String对象是叫“abc”的,换句话说,没有任何一个String对象引用了常量池中的“abc”常量,也没有其他地方引用了这个字面量,如果这个时候发生内存回收,而且有必要的话,这个“abc”常量就会被系统清理出常量池。

判定一个类是否是一个“无用的类”,需要同时满足以下三个条件:

该类的所有实例都已经被回收,也就是说java堆中不存在该类的任何实例;

加载该类的ClassLoader已经被回收

该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

三 垃圾收集算法

1 标记-清除算法

主要不足:标记和清除的两个过程的效率都不高;另外会产生大量不连续的内存碎片;

2 复制算法

实现简单,运行高效,但是代价是将内存缩小为了原来的一半。

现在的商业虚拟机都采用这种算法回收新生代

复制算法在对象存活率较高的时候就要进行较多的复制操作,效率变低,更关键的是如果不想浪费50%的空间,就需要额外的空间进行分配担保,以应对所有对象都100%存活的极端情况,所以老年代一般不直接选用复制算法

3 标记-整理算法

老年代

4 分代收集算法

根据对象存活的周期不同将内存划分为几块,将java堆分为新生代和老年代;在新生代中每次垃圾收集都发现有大批对象死去,只有少量存活,选用复制算法,只需要复制少量存活对象的复制成本就能完成收集;老年代因为对象存活率高,没有额外的空间对其进行分配担保,就必须使用“标记清除”或者“标记整理”算法进行回收

四 垃圾收集器

Serial收集器:

新生代收集器 单线程收集器,是虚拟机运行在client模式下的默认新生代收集器

ParNew收集器:

新生代收集器,是Serial收集器的多线程版本,是许多运行在Server模式下的虚拟机中首选的新生代收集器(一个与性能无关的原因是,除了Serial外,只有它是能与CMS收集器配合工作的)

Parallel Scavenge收集器:

新生代收集器,使用复制算法,并行多线程;其目标是达到一个可控制的吞吐量(CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间);停顿时间短适合需要与用户交互的程序,高吞吐量可以高效利用CPU时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务;另外自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别;

Serial Old收集器:

是Serial收集器的老年代版本,单线程收集器,使用标记-整理算法;主要意义在于给Client模式下的虚拟机使用;

Parallel Old收集器:

是Parallel Scavenge的老年代版本,使用多线程和标记-整理算法;

CMS(concurrent mark sweep)收集器

是一种以获取最短回收停顿时间为目标的收集器。整个过程包含4个步骤:

初始标记,并发标记,重新标记,并发清除

初始标记和重新标记仍然需要“stop the world”,初始标记仅仅是标记一下GC Roots能直接关联到的对象,速度很快;并发标记阶段是进行GC Roots Tracing的过程;重新标记是为了修正并发标记期间用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。

缺点:对CPU 资源敏感;无法处理浮动垃圾;大量空间碎片产生;

G1收集器

一款面向服务端应用的垃圾收集器;与其他GC收集器相比,具有以下特点:

  • 并行与并发
  • 分代收集
  • 空间整合:不会产生内存空间碎片;G1从整体上看是基于标记-整理算法实现的,从局部看是基于复制算法实现的;
  • 可预测的停顿

在G1之前的其他收集器进行收集的范围是整个新生代或者整个老年代,而G1不再是这样,Java内存布局与其他收集器有很大差别,它将整个java堆划分为多个大小相等的独立区域,虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,都是一部分的Region(不需要连续)的集合

四 内存分配策略

大多数情况下,对象在新生代Eden去中分配。当Eden区没有足够空间进行分配时,虚拟机将发生一次Minor GC;

大对象(需要大量连续内存空间和java对象,最典型的大对象就是那种很长的字符串和数组)直接进入老年代

长期存活的对象进入老年代

 

 

 

 

 

 

    原文作者:java虚拟机
    原文地址: https://blog.csdn.net/u013401204/article/details/81285938
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞