学习笔记系列开头惯例发布一些寻亲消息
链接:https://baobeihuijia.com/bbhj/contents/3/190801.html
java垃圾回收(stop the world)
- 专注于堆和方法区的垃圾回收,年轻代,老年代,永久代
- 判断对象是否还存活?(区分尸体)
- 引用计数算法
- 给每个对象保存一个整型的引用计数器类型,用于记录对象被引用的情况
- 缺点就是无法处理循环引用的情况,导致内存泄漏
- 可达性分析算法
- 找到GC ROOT对象,从根节点开始扩展访问
- 引用计数算法
- finalization机制(死前挣扎机制)
- 用于在对象被回收时进行资源释放,比如关闭文件,套接字和数据库连接等。
- 由于finalize的存在,对象有三种状态
- 可触及的:活着
- 可复活的:finalize重写过并且还没有执行,因此有机会进行死亡逃脱
- 不可触及的:直接打死
-
垃圾回收(三种算法清理尸体)
- 复制算法(常用在新生代这种回收率较高的块上):有一块是专门用来备用的,清除的时候将存活对象复制到备用上,然后清除当前使用的内存块,交换两个的块的角色
- 标记-清除算法:挨个清理,容易产生内存碎片,维护一个空闲列表,是一种非移动式的回收算法
- 标记-压缩清除:在标记清除的基础上加上了内存碎片的整理,而不是维护一个列表,因此是一种移动式的回收算法
-
增量收集算法:(解决stop the world)
每次垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。使用这种方式,由于在垃圾回收过程中,间断性地还执行了应用程序代码,所以能减少系统的停顿时间。但是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。
-
分代算法和分区算法:针对不同情况用不同的垃圾回收算法
-
内存溢出OOM和内存泄漏ML
-
stop the world,所有的GC都会有这种情况
-
GC的并发和并行
-
concurrent
- 并发:多个事情在同一段时间内同时发生:用户线程和垃圾回收线程同时执行,不会停顿用户进程
-
STW
- 并行:多个事情在用一个时间点上同时发生:多条垃圾回收并行,用户处于等待状态
- 串行:相对于并行,单线程运行
-
-
安全点: 数量需要适中,安全点实质上是一条指令(一般选择执行时间较长的指令作为安全点)
- 抢先式中断:控制权在安全点,把所有线程挂起,没有运行到安全点的就恢复让运行到安全点
- 主动式中断:线程运行到这里主动询问,主动挂起
-
安全区:是扩展的安全点,在区域的任何位置GC都是安全的,安全区的对象引用关系不会变化
- JVM会忽略标记进入到安全区的线程,线程还可以自主运行没有被停(因为其中的每句都是安全的)
- 但等到线程出来的时候,就检查GC是否完成,完成了就可以走,没完成就得等着(要是进入了不安全区域,而且这个线程还没有被停住,就容易让JVM和改变后的引用关系发生冲突)
-
引用(是有引用的可触及的对象,所以不会被判定为尸体,但是不一定有引用就会存活,也分情况)
-
强引用(有用)
- 只要引用关系存在,JVM宁愿OOM也不会回收对象
- 是造成内存泄漏的原因之一
- 强引用可以直接访问目标对象
-
软引用(有用但是非必须)
- 即将内存溢出时,回收这些软可达对象,回收后内存还是不够才抛出异常
-
weak弱引用(非必须)
- 不管堆空间使用情况,下一次垃圾回收器工作一定会回收掉
- GC的优先级很低,一般很久才能拿到线程,所以也能保有很久
- 当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用。软引用、弱引用都非常适合来保存那些可有可无的缓存数据。
-
虚引用:虚引用不会对对象的生存造成影响(如果只有虚引用,那么和没有引用是一样的)
- 唯一作用就是对象回收时会收到一个系统通知,可以用来跟踪对象回收时间
-
终结器引用(并不是一种对象还被别的对象需要的信号,而是将所有需要执行finalize的对象全都加进来
- Finalizer线程通过终结器引用找到对象,并执行finalize方法
-
-
垃圾回收器
-
评价指标:吞吐量、暂停时间、占用内存
- 个人理解:吞吐量是指用户的实际有效运行时间占比,暂停时间是用户需要等待GC的时间,这两者是相互冲突的。因为想要有效占比时间长,那么就需要GC不要那么频繁的发起回收,而是一次性高效回收完,回收时间较长,而暂停时间短,就需要每次GC的时间很短,但是发起次数比较频繁,导致程序吞吐量下降。
-
不同垃圾回收器(年轻代和老年代的垃圾回收器进行组合)
- 新生代:
- serial:复制算法、串行回收、stop the world
- ParNew:复制算法、并行回收、stop the world
- parallel scavenge:复制算法、并行回收、stop the world、可控吞吐量(适用于不需要太多交互的场景)
- 新生代:
-
老年代
- serial old:标记-压缩算法、串行回收、stop the world
- parallel old:标记-压缩算法,并行,stop the world
- CMS: 标记-清除算法、并发、stop the world,必须采用空闲列表而不是指针碰撞(与parallel scavenge不符,只能换别的新生代回收器)由于CMS回收过程中,应该确保应用程序也有部分的内存,所以CMS不能等到老年代全部满了再进行收集,到一定阈值就应该收集。当预留内存不满足应用程序要求的时候,就会出现concurrent failure,虚拟机会临时启动serial old回收,停顿时间变长。
- 初始标记STW:GC Root
- 并发标记Concurrent:遍历对象图
- 重新标记STW:修正并发标记期间的错误
- 并发清除Concurrent:清除标记阶段死亡的对象,释放内存空间
-
G1回收器:全能垃圾回收器
- 将堆内存分为不同的region,尽量避免对整个堆全区域进行垃圾回收。
- 跟踪每个区域的垃圾回收价值,维护一个优先列表,根据每次允许的收集时间优先回收价值最大的region
- 并行与并发兼容:部分GC并行进行,部分工作可以与应用程序并发交替执行
-
流程
-
年轻代回收过程:G1独占式并行运行,首先收集年轻代eden和survivor的内存回收片段,从年轻代移动存活对象到survivor或老年区间(最频繁使用的)
- Rset避免全堆扫描root
- 更新Rset(有的被老年代引用了)
- 复制:将存活片段复制到survivor,空间不够就直接到old
-
老年代并发标记过程:堆内存使用达到阈值(隔几天进行一次)
- 初始标记阶段STW:标记根节点直接可达对象,触发Young GC
- 根区域扫描(检查survivor直接可达老年代对象并标记)
- 并发标记:计算区域的对象活性(存活比例)
- 再次标记:修正并发标记的结果
- 独占清理STW:计算存活对象和GC回收比例进行排序
- 并发清理阶段
-
老年代的混合回收:每次只扫描一小部分老年代的region,然后老年代和年轻代一起回收,为了避免堆内存被耗尽
-
Full GC:会产生大的停顿,尽量避免
-
-
Remember set
-