程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,这几个区域的内存分配和回收都具备确定性,不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟着回收了,而java堆和方法区则不一样,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存。

一、判断对象是否存活的方法

1.引用计数法(主流java虚拟机并没有采用这种方式)

所谓引用计数法,就是当引用对象时就+1,当引用失效时,计数器值就-1,任何时候当计数值为0的对象就是不可能在被使用了,为什么java虚拟机不用这个呢,因为这个无法解决对象互相循环引用问题。

例:对象A和对象B

A.instance=B;

B.instance=A;

除此之外就无对对象的操作,而此时无法用引用计数法来进行回收他们。

2.可达性分析算法(在主流的商用程序语言都是通过此来判断对象是否存活的)

算法思想:通过一系列的称为GC Roots对象作为起始点,从这些节点向下搜索,搜索所连接的路径称为引用链,当发现有对象与GC Roots对象无法通过引用链相连,则证明这些对象是不可用的,所以它们将被判定为是可回收的对象。

注意:在Java中,可作为GC Roots对象包括下面几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  2. 方法区中类静态属性引用的对象。
  3. 方法区中常量与引用的对象。
  4. 本地方法栈中JNI(一般说的Native方法)引用的对象。

二、再谈引用

在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用4种,这四种引用强度依次逐渐减弱。

  1. 强引用:类似于Object obj =new Object()这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
  2. 软引用:是描述一类还可用但非必需的对象,在内存还充足时保存,当将要发生内存溢出异常之前,将会对这些对象进行第二次回收,如果回收后还没有足够的内存,才会抛出内存溢出异常。
  3. 弱引用:非必需对象,在进行垃圾回收时,都会回收掉这些对象。
  4. 虚引用:最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象的实例,虚引用唯一目的就是当对象被收集器回收时收到一个通知。

三、生存还是死亡

要真正宣告对象的死亡,要经过两次标记过程;如果对象在进行可达性分析后没有与GC Roots相连接的引用链,那么它将被第一次标记并且进行一次筛选,筛选原则时finalize()方法是否有必要被执行,如果有必要则进入F-Queue队列,再队列中finalize()方法是对象逃脱死亡的最后一次机会,稍后GC会对队列进行第二次标记,如果对象要在finalize()方法中拯救自己——只要重新与引用链上的任意对象关联即可,如果没有关联,则死亡,注意finalize()方法只会被执行一次触发一次。

四、回收方法区

永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。

判断一个常类是否是废弃常量比较简单,而判断一个类是否是废弃类则需要同时满足三个条件才能算是“无用的类”:

  1. 该类的所有实例都已经回收。
  2. 加载该类的ClassLoader已经被回收。
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

五、垃圾收集算法

标记-清除算法

算法分为标记-清除两个阶段,首先标记出所有需要回收的对象,而在标记完成后统一回收所有被标记的对象。

不足:首先效率问题,效率都不高,其次容易造成内存碎片,当需要分配较大对象时,无法找到足够的连续内存而不得不触发另一次垃圾收集动作。

复制算法

为了解决效率问题,复制算法是将内存空间分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,就将还存活的对象赋值到另一块上,在把这个内存空间清理一次。有点不需要考虑内存碎片问题,高效,缺点内存缩小到原来的一半,代价有点大。

标记-整理算法

复制收集算法在对象存活率较高时就要进行较多的复制操作,效率会贬低,有人提出标记-整理算法:标记过程与标记清除算法相同,但后续不是清除,而是将对象向某一边移动,然后清理出端边界以外的内存。

分代收集算法

当前商业虚拟机都采用该方法,该方法一般根据对象存活周期的不同分成几块,java堆一般分成新生代和老年代,新生代朝生夕死,只存在少量的存活对象则采用复制算法,移动少量存活对象,而老年代则存活大部分对象,没有额外空间对它进行分配担保,则采用标记-清理算法或者标记-整理算法进行回收。

六、安全点

如果直接用GC Roots直接找引用链的话需要进行枚举,必然会消耗很多时间,所以采用OopMap的数据结构再特定的位置记录下那些地方存放着对象引用,而这些特定的位置称之为安全点,程序执行时并非再所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。

安全点停顿

如何在GC发生时,所有的线程都跑到最近的安全点上再停顿下来。两种方案:抢先式中断和主动式中断。

其中抢先式中断不需要线程的执行代码主动去配合,而是在GC发生时,中断所有线程,判断如果中断点不在安全点则恢复线程,让它跑到安全点中断,现在几乎没有虚拟机采用此方法暂停线程。

而主动式中断思想是建立标志位,当标志位为真时,就将自己挂起,标志位的设置与安全点是重合的。

安全区域

安全点看似已经解决进入GC的问题,但是当线程不执行的时候呢(处于Sleep状态),就无法响应JVM的中断请求,这种时候就需要安全区域(Safe Region)来解决。

安全区域是指在一段代码之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的,我们也可以把Safe Region看做是被扩展了的Safepoint。

七、垃圾收集器

如果说收集方法是内存回收的方法论,而垃圾收集器就是内存回收的具体实现。

如图所示展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明他们可以搭配使用。

Serial收集器

是最基本、发展历史最悠久的收集器,属于单线程收集器,当进行收集时必须暂停其他线程,这对很多应用来说是很难接受的。

ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本。

Parallel Scavenge收集器

此收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点时尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。

停顿时间越短就越适合用户交互程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效率的利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

Serial Old收集器

是Serial收集器的老年代版本。

Parallel Old收集器

是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法

重点:CMS收集器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。特别适用于服务的交互服务上。

是基于“标记-清除”算法实现的,整个过程分为4个步骤

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发清除

CMS收集器缺点

CMS收集器对CPU资源非常敏感。其实,面向并发设计的程序都对CPU资源比较敏感。

CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。

CMS是一款基于“标记-清除”算法实现的收集器,则会产生大量的空间碎片,将会给大对象分配带来很大麻烦。

G1收集器

是当今收集器技术发展的最前沿成果之一,是一款面向服务端应用的垃圾收集器。

具备以下特点:

  1. 并行与并发
  2. 分代收集
  3. 空间整合
  4. 可预测的停顿

G1收集器的运作大致可划分为以下几个步骤

初始标记

并发标记

最终标记

筛选回收

八、内存分配与回收策略

对象的内存分配,往大方向讲,就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的。

对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。

大对象直接进入老年代

所谓的大对象是指,需要大量连续内存空间的java对象,最典型的大对象就是那种很长的字符串以及数组。大对象对虚拟机的内存分配来说是一个坏消息。

长期存活的对象将进入老年代

既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生,哪些对象应放在老年代中。通过Minor GC来进行确定,建立一个对象年龄计数器,每进行一次Minor GC如果对象依然存在,则该对象的年龄增加一岁。当年龄达到某一个阈值时,就进入老年代中。

动态对象年龄判断

为了更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

《深入理解Java虚拟机》之(二、垃圾收集器与内存分配策略)的更多相关文章

  1. 深入理解Java虚拟机03--垃圾收集器与内存分配策略

    一.概述  哪些内存需要回收? 什么时候回收? 如何回收? 二.对象已死吗  1.引用计数算法  定义:给对象添加一个引用计数器,当增加一个引用时,加1,当一个引用时,减1; 缺陷:当对象之间互相循环 ...

  2. jvm系列 (二) ---垃圾收集器与内存分配策略

    垃圾收集器与内存分配策略 前言:本文基于<深入java虚拟机>再加上个人的理解以及其他相关资料,对内容进行整理浓缩总结.本文中的图来自网络,感谢图的作者.如果有不正确的地方,欢迎指出. 目 ...

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

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

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

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

  5. 《深入理解java虚拟机》第三章 垃圾收集器与内存分配策略

    第三章 垃圾收集器与内存分配策略 3.1 概述 哪些内存需要回收 何时回收 如何回收 程序计数器.虚拟机栈.本地方法栈3个区域随线程而生灭. java堆和方法区的内存需要回收.   3.2 对象已死吗 ...

  6. 《深入理解 Java 虚拟机》学习 -- 垃圾收集器

    <深入理解 Java 虚拟机>学习 -- 垃圾收集器 1. Serial 收集器(新生代) 含义: 单线程收集器. 缺点: 进行垃圾收集时,必须暂停其他所有的工作线程. 优点: 简单而高效 ...

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

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

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

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

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

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

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

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

随机推荐

  1. mui mui-control-item获得选中的标签

    function getActiveControl() { var segmentedControl = document.getElementById("top-scroll") ...

  2. [Cometoj#3 B]棋盘_状压dp

    棋盘 题目链接:https://cometoj.com/contest/38/problem/B?problem_id=1535 数据范围:略. 题解: 因为行数特别小,所以$dp$的时候可以状压起来 ...

  3. DP的初级问题——01包、最长公共子序列、完全背包、01包value、多重部分和、最长上升子序列、划分数问题、多重集组合数

    当初学者最开始学习 dp 的时候往往接触的是一大堆的 背包 dp 问题, 那么我们在这里就不妨讨论一下常见的几种背包的 dp 问题: 初级的时候背包 dp 就完全相当于BFS DFS 进行搜索之后的记 ...

  4. php7和PHP5对比的新特性和性能优化

    1  抽象语法树( AST) 1)在 PHP5中,从 php 脚本到 opcodes 的执行的过程是: Lexing:词法扫描分析,将源文件转换成 token 流:    Parsing:语法分析,在 ...

  5. Zookeeper快速开始

    具体部署流程: #下载 wget https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/current/apache-zookeeper-3.5. ...

  6. C++反汇编第三讲,反汇编中识别继承关系,父类,子类,成员对象

    讲解目录: 1.各类在内存中的表现形式   备注: 主要复习开发知识,和反汇编没有关系,但是是理解反汇编的前提.     2.子类继承父类 2.1 子类中有虚函数,父类中有虚函数 : 都有的情况下   ...

  7. OkHttp3 + retrofit2 封装

    0.下载文件 1.gradle 添加 compile 'com.squareup.retrofit2:retrofit:2.1.0'compile 'com.squareup.retrofit2:co ...

  8. 路由器WAN口IP显示为10、100、172开头,网络被电信联通等运营商做了NAT转发

    摘要:路由器WAN口IP显示为10.100.172开头,网络被电信联通等运营商做了NAT转发 ... 路由器WAN口IP显示为10.100.172开头的解决方法方法一:找电信(10000号)或者联通( ...

  9. 关于ManualResetEvent的实例分析

    最近用WPF开发时使用多个定时器处理时需要实例化N多个DispatcherTimer,而且全部暴露在程序外部,显得很冗杂,例如下面的例子:用到的两个定时器就要实例化两个DispatcherTimer, ...

  10. xcode 把项目代码提交到远程SVN服务器

    环境 xcode 7  Mac air xcode默认支持GIT源码管理工具,但现在想把代码提交到已有到SVN服务器上,步骤如下: 1,在safari中打开svn链接地址,信任证书,输入用户名密码 , ...