icode icode
首页
  • Android学习

    • 📁基础内容
    • 📺AndroidCore
    • 🎨Android-UI
    • 🏖️Components
    • 📊Fragment
    • 🔗网络操作
    • 🔏异步机制
    • 📦数据存储
    • 🗃️Gradle
  • 学习笔记

    • 『框架』笔记
    • 『Kotlin』笔记
    • 《Vue》笔记
    • 《Git》学习笔记
    • 『Bug踩坑记录』
  • ListView
  • RecyclerView
  • ViewPager
  • Java笔记

    • 🟠JavaSE
    • 🟢JavaWeb
    • 🔴JavaEE
    • ⚪JavaTopic
    • 🍳设计模式
  • 计算机基础

    • 📌计算机网络
    • 🔍数据结构
    • 📦数据库
    • 💻OS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
  • 关于

    • 📫关于我
  • 收藏

    • 网站
    • 资源
    • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

iqqcode

保持对技术的探索实践与热爱
首页
  • Android学习

    • 📁基础内容
    • 📺AndroidCore
    • 🎨Android-UI
    • 🏖️Components
    • 📊Fragment
    • 🔗网络操作
    • 🔏异步机制
    • 📦数据存储
    • 🗃️Gradle
  • 学习笔记

    • 『框架』笔记
    • 『Kotlin』笔记
    • 《Vue》笔记
    • 《Git》学习笔记
    • 『Bug踩坑记录』
  • ListView
  • RecyclerView
  • ViewPager
  • Java笔记

    • 🟠JavaSE
    • 🟢JavaWeb
    • 🔴JavaEE
    • ⚪JavaTopic
    • 🍳设计模式
  • 计算机基础

    • 📌计算机网络
    • 🔍数据结构
    • 📦数据库
    • 💻OS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
  • 关于

    • 📫关于我
  • 收藏

    • 网站
    • 资源
    • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaSE

    • 面向对象

    • 常用API

    • 集合类

    • 多线程

    • JDK8新特性

    • IO流

    • JVM

      • 类加载系统
      • 运行时数据区及线程
      • 程序计数器
      • 虚拟机栈
      • 本地方法栈
      • 堆
      • 方法区
      • 对象实例化的内存布局与访问定位
      • 面试考点
      • 垃圾回收算法
      • 执行引擎
      • 垃圾回收相关概念
      • 垃圾回收算法
      • 垃圾回收器
      • GC日志分析
      • JVM内存区域与内存溢出异常
      • JVM垃圾回收器与内存分配策略
        • Java内存模型JMM详解
        • StringTable详解
        • 站在虚拟机栈的角度,用字节码来理解i++和++i
    • JavaWeb

    • JavaEE

    • JavaTopic

    • 设计模式

    • 计算机基础

    • Java后端
    • JavaSE
    • JVM
    iqqcode
    2021-06-17
    目录

    JVM垃圾回收器与内存分配策略

    [TOC]

    上一节内容回顾 :JVM(一)内存区域与内存溢出异常 (opens new window)

    # 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<Person> softReference = new SoftReference<>(per);
       per = null; //该对象指向为空,可以被回收
       System.gc;
    
    1
    2
    3
    4

    弱引用 :

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

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

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

    Person per = new Person(); //必须先创建强引用对象
    WeakReference<Person> weakReference = new WeakReference<>(per);
    per = null;
    System.gc;
    
    1
    2
    3
    4

    虚引用 :

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

    为对象设置虚引用的目的 :该对象在被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
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47

    在这里插入图片描述

    # 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区域,并将Eden和From区域清空
    2. 当后续Eden又发生Minor gc的时候,会对Eden和To区域进行垃圾回收. 存活的对象复制到From区域,并将Eden和To区清空. 之后依次循环
    3. 部分对象会在From和To区域中复制来复制去,如此交换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倍以上.
    编辑 (opens new window)
    上次更新: 2021/06/27, 10:49:09
    JVM内存区域与内存溢出异常
    Java内存模型JMM详解

    ← JVM内存区域与内存溢出异常 Java内存模型JMM详解→

    最近更新
    01
    匿名内部类
    10-08
    02
    函数式接口
    10-08
    03
    ARouter-Kotlin踩坑
    10-05
    更多文章>
    Theme by Vdoing | Copyright © 2021-2023 iqqcode | MIT License | 备案号-京ICP备2021028793号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式
    ×