垃圾收集器和内存分配

程序计数器、虚拟机栈、本地方法栈这三个区域和线程的生命周期一致,所以方法结束或者线程结束时,内存自然就跟着回收了。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

Java虚拟机--垃圾收集器和内存分配的更多相关文章

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

    Java虚拟机垃圾收集器与内存分配策略 概述 那些内存须要回收,什么时候回收.怎样回收是GC须要完毕的3件事情. 程序计数器.虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性 ...

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

    1.  前言 内存分配与回收策略 JVM堆的结构分析(新生代.老年代.永久代) 对象优先在Eden分配 大对象直接进入老年代 长期存活的对象将进入老年代 动态对象年龄判定 空间分配担保  2.  垃圾 ...

  3. 深入理解JAVA虚拟机 垃圾收集器和内存分配策略

    引用计数算法 很多教科书判断对象是否存活的算法是这样的:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器都为0的对象就是不可能再被使用的 ...

  4. [深入理解Java虚拟机]<垃圾收集器与内存分配策略>

    Overview 垃圾收集考虑三件事: 哪些内存需要回收? 什么时候回收? 如何回收? 重点考虑Java堆中动态分配和回收的内存. Is Object alive? 引用计数法 给对象添加一个引用计数 ...

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

    说起GC,我们要思考的主要有三件事 哪些内存需要回收 那些已经“死去”的对象,那么哪些对象“死”,哪些对象“活”呢,有个简单的办法 引用计数法,但是没法解决循环依赖问题 所以Java虚拟机采用的是可达 ...

  6. java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...

  7. 深入理解java虚拟机_第三章(上)----->垃圾收集器与内存分配策略

    1.  前言 这一版块内容比较多,分为两篇文章来做笔记.本文讲述上半部分垃圾收集部分;下一篇文章写内存分配部分. 概述 对象已死吗? 引用技术算法 可达性分析算法 再谈引用 两次标记 回收方法区 2. ...

  8. 《深入java虚拟机》读书笔记之垃圾收集器与内存分配策略

    前言 该读书笔记用于记录在学习<深入理解Java虚拟机--JVM高级特性与最佳实践>一书中的一些重要知识点,对其中的部分内容进行归纳,或者是对其中不明白的地方做一些注释.主要是方便之后进行 ...

  9. 《深入理解Java虚拟机》(三)垃圾收集器与内存分配策略

    垃圾收集器与内存分配策略 详解 3.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第三章 ,为了整理思路,简单记录一下,方便后期查阅. 3.2 对象已死吗 在垃圾收集器进行回收 ...

随机推荐

  1. SQL SERVER的锁机制(四)——概述(各种事务隔离级别发生的影响)

    六.各种事务隔离级别发生的影响 修改数据的用户会影响同时读取或修改相同数据的其他用户.即这些用户可以并发访问数据.如果数据存储系统没有并发控制,则用户可能会看到以下负面影响: · 未提交的依赖关系(脏 ...

  2. 【BZOJ4755】 [Jsoi2016]扭动的回文串

    BZOJ4755 [Jsoi2016]扭动的回文串 Solution 考虑对于他给出的 A中的一个回文串: B中的一个回文串: 或者某一个回文的扭动字符串S(i,j,k) 这样子几个限制,我们1,2就 ...

  3. Flask系列09--Flask中WTForms插件,及自定义验证器

    一.概述 django中的forms组件非常的方便,在flask中有WTForms的组件实现的也是类似的功能, 安装这个插件 二.简单使用 文档地址https://wtforms.readthedoc ...

  4. kali linux 安装sublime text3完全教程

    点击进入官网 下载页面 将鼠标放在64 bit(64位系统)上右击复制链接 打开终端: #wget 路径(粘贴刚复制的) #tar -xvvf 刚刚下载的文件文件名(解压) #mv 解压出来的文件名  ...

  5. C++ 设置Java 环境变量完整的例子

    #include <stdio.h> #include <stdlib.h> #include <windows.h> #include <iostream& ...

  6. spring 后处理器

    Bean后处理器 新建maven项目并添加spring依赖,目录结构如下 Axe public interface Axe { public String chop(); } Person publi ...

  7. Android四大组件之一 -- Service详解

    相信大多数朋友对Service这个名词都不会陌生,没错,一个老练的Android程序员如果连Service都没听说过的话,那确实也太逊了.Service作为Android四大组件之一,在每一个应用程序 ...

  8. Word在转PDF的过程中如何创建标签快速方便阅读(图文详解)

    不多说,直接上干货! 选择如下 成功! 欢迎大家,加入我的微信公众号:大数据躺过的坑        人工智能躺过的坑       同时,大家可以关注我的个人博客:    http://www.cnbl ...

  9. Javac中对import关键字进行的处理

    参考文章: (1)关于类的符号输入过程第二篇 ImportScope中存储的为ImportEntry,继承了Scope.Entry类并且多定义了个origin属性,也就是符号的最终来源.除此之外还对g ...

  10. Python学习--08函数式编程

    函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数. 高阶函数 Python支持高阶函数(Higher-order function). 什么是高阶函数呢?把函数作为参 ...