博客
关于我
JVM(二)垃圾回收器与内存分配策略
阅读量:661 次
发布时间:2019-03-15

本文共 4061 字,大约阅读时间需要 13 分钟。

文章目录

上一节内容回顾 :

1. 垃圾回收策略

回收 针对的是线程共享内存(堆,方法区)

判断对象是否存活?

a. 引用计数法(Python,C++)

  • 给每个对象附加一个引用计数器,每当有引用指向当前对象时,计数器 +1;每当有引用不再指向当前对象时,计数器 -1
  • 任意时刻,引用计数器值为0的对象,被标记为 “不在存活”
  • 缺点 :无法解决循环引用问题(我中有你,你中有我)

b. 可达性分析算法(Java,C#)

核心思想:

  • 通过一系列称为GC Roots对象开始向下搜索,若到指定对象有路可走(即"可达"),认为此对象存活;
  • 若从任意一个GC Roots对象到目标对象均不可达,认为目标对象已经不再存活.

在这里插入图片描述

在Java语言中,可作为GC Roots的对象包含下面几种 :

  1. 虚拟机栈中的临时变量引用的对象

  2. 本地方法栈中JNI(Native方法)引用的对象

  3. 方法区中类静态变量引用的对象

  4. 方法区中常量引用的对象

2. 引用的扩充

JDK 1.2之后关于引用的扩充:强软弱虚,引用强度依次扩充

强引用 :

程序中普遍存在的,GC Roots对象所指向的引用都属于强引用

Person per = new Person();

只要当前对象被任意一个强引用指向,即便内存不够用也不能回收此对象

软引用 :

有用但不必须的对象(缓存对象)

  • 当前内存够用时,不回收对象;

  • 当前内存不够用时,回收对象.

JDK 1.2之后用SoftReference来表示软引用

Person per = new Person(); //必须先创建强引用对象   SoftReference
softReference = new SoftReference<>(per); per = null; System.gc;

弱引用 :

描述非必须对象,强度若于软引用

  • 被弱引用指向的对象,只能存活到下次GC之前;
  • 当GC开始时,不管内存是否够用,都会回收被弱引用指向的对象

JDK 1.2之后用WeakReference来表示弱引用

Person per = new Person(); //必须先创建强引用对象WeakReference
weakReference = new WeakReference<>(per);per = null;//该对象指向为空,可以被回收System.gc;

虚引用 :

最弱的引用关系,完全对对象的生存周期不造成影响,并且无法通过虚引用取得对象

为对象设置虚引用的目的 :该对象在被GC回收之前由系统发回 回收通知

JDK 1.2之后用PhantomReference来表示虚引用

3. 对象的自我拯救

当一个对象到GC Root不可达时,并不是"当场去世",而是进行自我拯救

finalize() 对象的一次自我拯救机会

JVM在进行GC之前,需要判断回收对象所在的类 是否覆写了finalize()

  • 若没覆写,此对象直接回收
  • 若覆写了finalize()
    • finalize() 未被JVM调用,JVM则会调用finalize() ;对象在此调用过程中与GC Roots有路可达,此对象不再被回收(自救成功)
    • finalize() 已被JVM调用,此对象被回收
      在这里插入图片描述
package iqqcode.Study.GCRoots;import java.util.concurrent.TimeUnit;/** * @Author: Mr.Q * @Date: 2019-09-05 19:37 * @Description:finalize()对象的自我拯救 * */public class FinalizeTest {       private static FinalizeTest test;    @Override    protected void finalize() throws Throwable {           System.out.println("finalize method execute!");        test = this; //test等于当前调用finalize()的对象    }    public static void main(String[] args) throws InterruptedException {           test = new FinalizeTest();        test = null; //此时test对象没有被强引用指向,无引用指向        //JVM检查 是否覆写且调用过finalize()        //对象在此调用过程中与`GC Roots`有路可走,此对象不再被回收        System.gc();        TimeUnit.SECONDS.sleep(1);        if(test == null) {               System.out.println("Now I'm dead...");        }else {               System.out.println("I'm Alive!");        }        test = null;//此时无引用指向,可以被垃圾回收        //JVM检查 覆写且调用过finalize()        //不再调用finalize(),判断对象死亡        //finalize() 对象的一次自我拯救机会        System.gc();        TimeUnit.SECONDS.sleep(1);        if(test == null) {               System.out.println("Now I'm dead...");        }else {               System.out.println("I'm Alive!");        }        //TODO    }}

在这里插入图片描述

4. 方法区的回收

方法区回收(永久代回收)------废弃常量和无用类

方法区的GC频率非常低

5. 垃圾回收算法(堆上)

I. 标记清除算法

算法分为"标记"和"清除"两个阶段 :

  • 首先标记(根据可达性分析算法来标记)出所有需要回收的对象
  • 在标记完成后统一回收所有被标记的对象

在这里插入图片描述

标记-清除算法的不足主要有两个 :

  1. 效率问题 : 标记和清除这两个过程的效率都不高
  2. 空间问题 : 标记清除后会产生大量不连续的内存碎片,导致gc频繁发生

II. 复制算法(新生代回收算法)

(所有对象和数组对象)

  • 新生代: 对象默认在此区域产生,大部分对象都在此区域,该区域的特点是==“朝生夕死”== (存活率低)
  • 老年代: 存活率高
  • "复制"算法是为了解决"标记-清理"的效率问题.
  • 它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象 复制 到另一块上面,然后再把已经使用过的内存区域一次清理掉

在这里插入图片描述

这样做虽然提高了回收效率,但是降低了内存的利用率(100%的内区域变成了50%)

新生代中98%的对象都是"朝生夕死"的,所以并不需要按照1 : 1的比例来划分内存空间

而是将内存(新生代内存)分为一块较大的 Eden(伊甸区) 空间和两块较小的 Survivor(幸存区) 空间,每次使用 Eden 和其中一块Survivor(Survivor区域一个称为From区,另一个称为To区域)

画图说明

HotSpot 实现的复制算法流程如下:

  1. Eden区满的时候,会触发第一次Minor gc,把还活着的对象拷贝到Survivor From区,然后清理掉Eden区的所有空间. 当Eden区即将满时,再次触发Minor gc,会扫描Eden区和From区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将EdenFrom区域清空
  2. 当后续Eden又发生Minor gc的时候,会对EdenTo区域进行垃圾回收. 存活的对象复制到From区域,并将EdenTo区清空. 之后依次循环
  3. 部分对象会在FromTo区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代

III. 标记-整理算法(老年代回收算法)

核心思想相较于标记清除算法------整理阶段先让存活对象向一端移动,而后清理掉端边界以外的内存

Question : 为何老年代不采用复制算法?

Answer :新生代中的绝大多数对象都是朝生夕死,所以每次复制存活的对象较少;而老年代中大部分对象都存活,反复复制反而效率低下

IV. 分代收集策略(JavaGC)

将堆空间分为新生代(-Xmn)与老年代(堆的大小 - 新生代)空间,其中新生代采用复制算法,老年代采用标记整理算法

面试题 : 请问了解Minor GC和Full GC么,这两种GC有什么不一样吗?

  1. Minor GC 又称为新生代 GC : 指的是发生在新生代的垃圾收集. 因为Java对象大多都具备朝生夕灭的特性,因此 Minor GC (采用复制算法)非常频繁,一般回收速度也比较快.
  2. Full GC 又称为 老年代 GC 或者 Major GC : 指发生在老年代的垃圾收集. 出现了 Major GC,经常会伴随至少一次的 Minor GC(并非绝对,在 Parallel Scavenge 收集器中就有直接进行 Full GC 的策略选择过程). Major GC 的速度一般会比 Minor GC 慢10倍以上.

转载地址:http://itlmz.baihongyu.com/

你可能感兴趣的文章
MySQL、HBase 和 Elasticsearch:特点与区别详解
查看>>
MySQL、Redis高频面试题汇总
查看>>
MYSQL、SQL Server、Oracle数据库排序空值null问题及其解决办法
查看>>
mysql一个字段为空时使用另一个字段排序
查看>>
MySQL一个表A中多个字段关联了表B的ID,如何关联查询?
查看>>
MYSQL一直显示正在启动
查看>>
MySQL一站到底!华为首发MySQL进阶宝典,基础+优化+源码+架构+实战五飞
查看>>
MySQL万字总结!超详细!
查看>>
Mysql下载以及安装(新手入门,超详细)
查看>>
MySQL不会性能调优?看看这份清华架构师编写的MySQL性能优化手册吧
查看>>
MySQL不同字符集及排序规则详解:业务场景下的最佳选
查看>>
Mysql不同官方版本对比
查看>>
MySQL与Informix数据库中的同义表创建:深入解析与比较
查看>>
mysql与mem_细说 MySQL 之 MEM_ROOT
查看>>
MySQL与Oracle的数据迁移注意事项,另附转换工具链接
查看>>
mysql丢失更新问题
查看>>
MySQL两千万数据优化&迁移
查看>>
MySql中 delimiter 详解
查看>>
MYSQL中 find_in_set() 函数用法详解
查看>>
MySQL中auto_increment有什么作用?(IT枫斗者)
查看>>