垃圾收集器和内存分配

垃圾收集器和内存分配

程序计数器、虚拟机栈、本地方法栈这三个区域和线程的生命周期一致,所以方法结束或者线程结束时,内存自然就跟着回收了。Java堆和方法区,只有在程序处于运行期间才能知道会创建哪些对象,即这部分的内存分配和回收都是动态的,垃圾回收主要关注的是堆内存

对象存活判断

在进行垃圾回收之前,首先要判断哪些对象还存活,哪些已经死去去。判断对象存活的方法,有如下几种:

引用计数法

每个对象有一个引用计数器,每当有一个地方引用了它计数+1;引用失效计数器-1;当引用计数为0时,说明这个对象在任何地方都不被使用了,可以进行回收了。

引用计数法有缺点:对象之间的循环引用。当两个对象互相引用对方,除此之外它们都再无其他任何引用时,两个对象的引用计数都不为0,造成了GC收集器无法回收它们。

可达性分析法

Java中正是使用了这种算法来判断对象是否存活。这种算法使用了类似树形结构来搜索对象,作为根结点的称为GC Roots,是搜索的起点,搜索走过的路径叫做搜索链,可以作为GC Roots的对象有

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性有引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(通常所说的Native方法)引用的对象

当某个对象到GC Roots的路径上没有引用,或者说从GC Roots开始搜索不到这个对象(GC Roots到这个对象是不可达的),那么该对象就可以被回收。

图中GC Roots到Object5、6、7都不可达。

可达性分析中不可达的对象并不是一定会被回收,对象真正被回收需要经过两次标记。可达性分析后发现GC Roots到某个对象不可达时,该对象会被第一次标记。接着判断,如果:

  • 对象没有覆盖finalize()方法
  • finalize()方法已被调用(只能被系统调用唯一一次)

满足以上条件的任一个,虚拟机则认为“没有必要执行finalize方法”,接着经过第二次标记后,对象被回收。否则,有必要执行finalize,对象进入F-Queue队列之中,finalize方法是对象存活的最后机会——只需和引用链上的任一个对象关联即可,那么在第二次标记时将被移除出“即将回收”的集合;如果还不能finalize中逃脱,该对象才真正被回收。

引用

引用有4种:

  • 强引用。比如Object obj = new Object(),只要强引用还在,GC收集器不会将被引用的对象回收。
  • 软引用。用于描述一些还有用但非必需的对象,和软引用关联的对象,系统将要发生内存溢出时,会将这些对象列入回收范围中进行第二次回收;若这次回收后还是没有足够的内存,才抛出内存溢出异常。
  • 弱引用。也用于描述非必需对象,比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前
  • 虚引用。最弱的引用关系,对象的虚引用存在与否,不会对其生存时间造成影响,也不能通过引用取得对象。设置虚引用的作用是当对象被回收时能收到一个系统通知。

垃圾收集算法

标记-清除算法

  • 标记要回收的对象
  • 统一回收被标记的对象

缺点如下:

  • 标记和清除两个阶段效率不高
  • 清除后产生大量不连续的内存碎片,对之后范培大对象带来不便(不得不提前出发一次垃圾收集)

复制算法

将内存按比例分成两块,每次只使用其中一块。当这一块内存用完了,将存活对象全部复制到另一块中,接着将已使用的内存空间一次性清除。

优点:不会产生内存碎片;缺点:内存利用率低。

标记-整理算法

和标记-清除算法类似,不同的是标记后并不是直接清理,而是让所有存活对象向一端移动,然后直接清除掉端边界外的内存。

分代收集算法

根据对象的存活周期的不同将内存划分为几块,通常将Java堆分为新生代和老年代。根据各个年代的特点采用最适当的收集算法:

  • 新生代。每次垃圾收集都发现只有少量对象存活,采用复制算法,因为所需复制操作次数少;
  • 老年代。对象存活率高、没有额外的空间对它进行分配担保,必须使用标记-清除或标记-整理算法。

GC进行时必须停顿所有Java执行线程。程序执行时只有在到达安全点才能暂停。而安全区域则是安全点的扩展:指在一段代码中,引用关系不会发生变化,在这个区域的任何地方开始GC都是安全的。

垃圾收集器

Serial收集器:单线程的收集器,在它进行垃圾回收时必须暂停其他所有的工作线程,直到它收集完成为止。优点:简单高效,没有线程交互的开销;缺点:GC时候其他线程不能工作。

ParNew收集器:Serial的多线程版本,使用多条线程进行垃圾收集,其余和Serial收集器几乎一致。

Parallel Scavenge收集器:收集器使用复制算法新生代收集器,且是并行的多线程收集器。该收集器的目的是达到一个可控制的吞吐量。

吞吐量 = 运行用户代码的时间 / (运行用户代码的时间  + GC收集时间)

Serial Old收集器:Serial收集器的老年代版本,使用标识-整理算法。

Parallel Old收集器:Paralell Scavenge的老年版本,使用多线程和标记-整理算法。

CMS收集器:老年代的收集器目的是尽可能缩短垃圾收集时用户线程的停顿时间。适合需要用户交互的场景,能获得较短的响应时间。

基于标记-清除算法实现,整个过程分为以下4步:

  • 初始标记:标记GC Roots能直接关联到的对象
  • 并发标记:进行GC Roots Tracing的过程,即在堆中堆对象进行可达性分析,从GC Roots开始找出存活的对象
  • 重新标记:修正并发标记期间因用户程序继续运作导致标志产生变动的那部分对象的标志记录
  • 并发清除:并发清除要回收的对象

缺点:

  • CMS无法处理浮动垃圾(浮动垃圾指CMS在并发清理的过程中用户线程还在继续运行,因此还会产生垃圾,这些新产生的垃圾在标记之后,故CMS无法在本次收集中清理掉它们)
  • CMS基于标记-清除,故会产生大量不连续的内存碎片
  • 对CPU资源很敏感

G1收集器:有如下特点:

  • 并行与并发
  • 分代收集
  • 从整体上看是基于标记-整理算法实现,从局部(两个Region之间)来看是基于复制算法的收集器,以确保G1运行期间不会产生内存空间碎片
  • 可预测的停顿,G1除了追求低停顿外,还能建立可预测的时间模型,主要原因是它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。

在使用G1收集器时,Java堆的内存划分为多个大小相等的独立区域,新生代和老年代不再是物理隔离。G1跟踪各个区域的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的区域。

G1收集器的运作大概有以下几个步骤:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

前三个步骤和CMS的前三个步骤类似。最后一步筛选回收会对各个区域的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。

内存分配和回收策略

对象主要分配在新生代的Eden区上,少数情况下也可能直接分配在老年代。当Eden区没有足够的空间时,虚拟机将发起一次Minor GC。

  • Minor GC:发生在新生代的垃圾收集,Java对象大多“朝生夕灭”,因此Minor GC比较频繁,回收速度快;
  • Full GC / Major GC:发生在老年代的GC,出现Full GC一般会伴随至少一次的Minor GC,且速度相比Minor GC会慢很多。

大对象会直接进入老年代。大对象指的是需要大量连续内存空间的Java对象,比如长字符串和大数组。

长期存活的对象将进入老年代。虚拟机给每个对象设置了一个对象年龄计数器,如果对象在Eden区出生并经历过一次Minor GC后仍然存活,且能被Survivor区容纳的话,该对象将被移动到Survivor区,且对象年龄设为1。此后,该对象每在Survivor区“熬过”一次Minor GC,对象年龄就+1,当对象年龄增长到一定程度(默认15岁)就晋升到老年代。但这个准则并不是一定的,如果在Survivor区中相同年龄所有对象的大小总和大于Survivor空间的一半,年龄大于等于该年龄的对象就可以直接晋升老年代。

Minor GC之前,虚拟机会先检查老年代最大可用连续空间是否能容纳新生代所有对象空间,若满足,则此次Minor GC是安全的;若不满足,则虚拟机会查看是否设置了允许担保失败,若允许,判断老年代的最大可用连续空间是否大于历次晋升到老年代对象的平均大小,若大于,就尝试着进行一次Minor GC,如果小于或者设置了不允许担保失败或者,那么将进行一次Full GC。



by @sunhaiyu

2018.6.9

原文地址:https://www.cnblogs.com/sun-haiyu/p/9159470.html

时间: 2024-10-28 15:32:02

垃圾收集器和内存分配的相关文章

垃圾收集器以及内存分配策略

垃圾回收 垃圾回收的三个问题: 哪些内存需要回收? 什么时候回收? 如何回收? 1.哪些对象需要回收? 判断对象是否存活的办法: 引用计数算法:给对象中添加一个引用计数器,有一个地方引用就+1,引用失效就-1.只要计数器为0则对象已死. 优点:简单易实现: 缺点:无法解决对象之间相互引用的问题.(JVM也因为此种原因没有使用它) 根搜索算法: 通过选取出一个GC Roots对象,已它作为起始点,如果对象不可达,则对象已死. GC Roots对象: 虚拟机栈中引用的对象 方法区中类静态属性引用的对

Java虚拟机垃圾收集器与内存分配策略

Java虚拟机垃圾收集器与内存分配策略 概述 那些内存需要回收,什么时候回收,如何回收是GC需要完成的3件事情. 程序计数器,虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性,内存随着方法结束或者线程结束就回收了. java堆与方法区在运行期才知道创建那些对象,这部分内存分配是动态的,本章笔记中分配与回收的内存指的就是:java堆与方法区. 判断对象已经死了 引用计数算法:给对象添加一个引用计数器,每当有一个地方引用它,计数器+1;引用失败,计数器-1.计数器为0则改判

第三章 垃圾收集器和内存分配策略

第三章 垃圾收集器和内存分配策略 对象已死吗 引用计算方法 可达性分析算法 通过一些列的GC roots 对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链,当一个对象到GC roots 没有任何引用链的则证明对象不可用的 虚拟机栈中的引用的对象 方法区中类静态属性引用的对象 方法去区中常量引用的对象 本地方法栈中JNI引用的对象 生存还是死亡 一次筛选,筛选是否有必要执行 finalize()方法 没有覆盖或者finalize()已经被调用过  视为没必要执行 放入一个F-Qu

垃圾收集器与内存分配策略(二)

垃圾收集算法简介 1.标记-清除算法       标记-清除算法主要分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一进行回收.对象的标记过程在垃圾收集器与内存分配策略(一)中已经介绍过. 存在的问题:一是效率问题,标记和清除的效率都不高:二是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时无法找到足够的内存而不得不提前触发另一次垃圾收集动作. 2.复制算法       复制算法:它将内存按照容量划分为大小

垃圾收集器与内存分配策略(四)之垃圾收集器

垃圾收集器与内存分配策略(四)--垃圾收集器 收集算法是内存回收的方法论,垃圾收集器则是内存回收的具体实现. 垃圾收集器介绍 在垃圾收集器的层面上对并行与并发的解释: 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户现场仍处于等待状态. 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但并不一定是并行的,可能会交替执行),用户程序仍在继续执行,而垃圾收集程序运行于另一个CPU上. 对于不同的厂商,不同的版本的虚拟机都可能有很大的差别.此处讨论的是jdk1.7之后的

垃圾收集器与内存分配策略(三)之HotSpot的算法实现

垃圾收集器与内存分配策略(三)--HotSpot的算法实现 Java JVM 垃圾回收 在HotSpot虚拟机上实现这些算法时,必须对算法的执行效率有着严格的考量,才能保证虚拟机高效地运行. 1. 枚举根节点 采用可达性分析从GC Roots节点中找引用链为例 存在的缺点: 1.在前面找出还存活对象时,采用可达性分析从GC Roots节点中找引用链时,可作为GC Roots的节点主要在全局性的引用(方法区的常量或类静态属性引用)与执行上下文(虚拟机栈栈帧中的本地变量表或本地方法栈中的Native

垃圾收集器与内存分配策略(六)之内存分配与回收策略

垃圾收集器与内存分配策略(六)--内存分配与回收策略 对象的内存分配,一般来说就是在堆上的分配(但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配),对象分配的细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数设置. 区分Minor GC与 Full GC: 新生代GC(Minor GC):指发生在新生代的的垃圾收集动作,因为Java对象大多具有朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快. 老年代GC(Full GC / Major GC):老

垃圾收集器与内存分配策略(五)之垃圾日志与常见参数

垃圾收集器与内存分配策略(五)--垃圾日志与常见参数 理解GC日志 每个收集器的日志格式都可以不一样,但各个每个收集器的日志都维持一定的共性.如下面二段日志: 33.125: [GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs] 100.667: [Full GC [Tenured: 0K->210K(10240K), 0.0149142 secs] 4603K->

垃圾收集器与内存分配策略(二)之垃圾收集算法

垃圾收集器与内存分配策略(二)--垃圾收集算法 Java JVM 垃圾回收 简单了解算法的思想 1. 标记-清除算法 标记-清除算法分为标记和清除二个阶段:首先标记出需要回收的对象(详见上一节的可达性分析找出存活对象),在标记完成后统一回收所有被标记的对象. 缺点: 1.标记和清除二个过程的效率都不高 2.空间问题,标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后再程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作. 2. 复制算法 复制算

深入理解java虚拟机----->垃圾收集器与内存分配策略(下)

1.  前言 内存分配与回收策略 JVM堆的结构分析(新生代.老年代.永久代) 对象优先在Eden分配 大对象直接进入老年代 长期存活的对象将进入老年代 动态对象年龄判定 空间分配担保  2.  垃圾收集器与内存分配策略 Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决两个问题: 给对象分配内存; 回收分配给对象的内存. 对象的内存分配,往大方向上讲就是在堆上的分配,对象主要分配在新生代的Eden区上.少数也可能分配在老年代,取决于哪一种垃圾收集器组合,还有虚拟机中的相关内存的参