<<深入Java虚拟机>>-第三章-垃圾收集器与内存分配策略-学习笔记
垃圾收集
垃圾收集(Garbage Collection,GC),垃圾收集需要完成的三件事情。
- 哪些对象需要回收
- 什么时候回收
- 如何回收
如何确定对象已死(即不可能在被任何途径引用的对象)
引用计数算法
给每一个对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;计数器值为0的对象就是表示没有在被使用的。
引用计数算法的实现简单,判定效率也比较高,大部分情况下都是一个不错的算法。
Java语言中没有采用该算法来管理内存,最主要的原因是该算法没法解决对象之间的相互依赖的问题。
根搜索算法
算法的基本思路是通过一系列的名为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
Java中,可以作为GC Roots的对象有以下几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
再谈引用
JDK1.2之前,Java中的引用定义很传统:如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。
JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种。
- 强引用就是在程序代码中普遍存在的,类似于“Object obj = new Object()”,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象
- 软引用用来描述一些还有用,但不是必须的对象。如果内存空间足够,垃圾回收器就不会回收它,内存空间不够了就会回收该对象。
- 弱引用也是用来描述非必须对象的,它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集之前。
- 虚引用是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用来取得一个对象的实例。设置虚引用的唯一目的就是希望这个对象被垃圾收集器回收时收到一个系统通知。(虚引用作用就是我们可以声明一个虚引用来引用我们感兴趣的对象,在垃圾收集器回收之前,垃圾收集器会把这个对象添加到referenceQueue,这样我们检测到referenceQueue中有我们感兴趣的对象时,就说明gc要回收该对象了,我们可以在gc回收之前做一些事情,比如记录日志等)
两次标记
要真正判断一个对象死亡,至少要经过两次标记过程:如果对象在进行根搜索后发现没有与GC Roots相关联的引用链,那么它将被第一次标记并进行一次筛选,筛选的条件是判断对象是否有必要执行finalize()方法。
当对象没有覆盖finalize()方法或者对象的finalize()方法已经被虚拟机执行过了,虚拟机将这两种情况都视为没有“必要执行”。
如果一个对象被判定为有必要执行finalize()方法,那么这个对象会被放置到一个名为F-Queue的队列中,虚拟机会自动创建一个低优先级的Finalizer线程去触发对象的finalize()方法(不承诺会等待它运行结束),如果在finalize方法中对象有了引用链建立连接(比如将this赋值给某个类的成员变量),则对象就会复活。
一个对象的finalize()方法只会被虚拟机执行一次,也就是说一个对象不可能自救两次。
finalize()方法很少使用,finalize()能做的关闭资源工作,使用try-catch-finally或其他方式都可以做得更好、更及时。
回收方法区
在方法区进行垃圾收集的“性价比”一般比较低,在堆中,尤其是在新生代中,常规应用进行一次垃圾回收可以回收70%-95%空间,而永久代的垃圾收集效率远低于此。
方法区的垃圾收集主要回收两部分内容:废弃常量和无用的类。
- 废弃常量的判定和堆中判定对象不可用类似。以常量池中字面量为例,比如一个字符串“abc”已经在常量池中,但是当前程序没用任何String对象引用常量池中的“abc”变量,这个时候发生垃圾回收,如果有必要的话,“abc”则会被回收掉。
- 无用的类:判定类为无用的类需要同时满足下面三个条件才能算是“无用的类”
- 该类的所有实例已经被回收,也就是Java堆中不存在该类的任何实例
- 加载该类的ClassLoader已经被回收
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
垃圾收集算法
标记-清除算法
最基础的收集算法是“标记-清除算法”,算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所被标记的对象。
这种算法的主要缺点有两个:一是效率问题,标记和清除过程的效率都不高;另外一个是空间问题,标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致程序运行过程中需要分配一块较大对象时无法找到足够的连续内存而不得不提前出发另一次垃圾收集工作。
复制算法
为了解决效率问题,一种称为“复制”的收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将这一块上还存活的对象复制到另一块上面,然后在把已使用过的内存空间一次清理掉。
这样每次都是对其中的一块进行内存回收,内存分配的时候也不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
只是这种算法的代价就是将内存缩小为原来的一半。
现在的商业虚拟机都采用这种方法来回收新生代,IBM的专门研究表明,新生代中的对象98%都是朝生夕死的,所以并不需要按照1:1的比例来划分内存空间,而是将内存划分为一块较大的Eden内存空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor空间。回收时,将Eden和Survivor中还存活的对象一次性地拷贝到另一块Survivor空间上,最后清理掉Eden和刚才使用过的Survivor空间。
Sun HotSpot虚拟机默认Eden和Survivor空间的大小比例为8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存是被“浪费”掉的。
对于98%的对象时朝生夕死,我们也没办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,就需要依赖其他的内存(这里指老年代)进行分配担保(Handle Promotion)。
如果另外一块Survivor空间没有足够的空间存放上一次新生代收集存活下来的对象,这些对象将直接通过分配担保机制进入老年代。
标记-整理算法
根据老年代的特点,提出了一种“标记-整理”算法,标记过程和“标记-清除”算法一样,但后续的步骤不是对标记的对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。老年代一般采用这种算法。
分代收集算法
当前的商业虚拟机垃圾收集都采用“分代收集”算法,并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样可以根据各个年代的特点采用不同的垃圾回收算法。
在新生代中,每次垃圾回收时都有大批量的对象死去,只有少量存活,就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
在老年代中,由于对象的存活率高,没有多余的空间对它进行分配担保,就必须使用“标记-清除”或“标记-整理”算法来进行回收。
内存分配与回收策略
对象优先在Eden分配
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。
- 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多具有朝生夕死的特性,所以Minor GC非常频繁,回收速度也比较快。
- 老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的),Major GC的速度一般比Minor GC慢十倍以上。
大对象直接进入老年代
大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串及数组。经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的内存空间来“安置”它们。
虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代中分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存拷贝。
长期存活的对象将进入老年代
虚拟机采用分代收集的思想来管理内存,就必须识别哪些对象应当放到新生代,哪些对象应放到老年代中。为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过了一次Minor GC之后任然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1。对象在Survivor区中每熬过一次Minor GC,年龄就增加一,当它的年龄增加到一定的程度(默认为15岁)时,就会被晋升到老年代中。
对象晋升到老年代的年龄阀值,可以通过参数-XX:MaxTenuringThreshold来设置。
动态对象年龄判定
为了更好的适应不同程序的内存状况,虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入到老年代,无需等到MaxTenuringThreshold中要求的年龄。
空间分配担保
在发生Minor GC之前,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则改为直接进行一次Full GC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败;如果允许,那只会进行Minor GC;如果不允许,则也要改为进行一次Full GC。
新生代使用复制收集算法,为了内存利用率,只使用其中的一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC之后仍然存活的情况时,就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入到老年代中。
理解GC日志
停顿类型
[GC:Minor GC, [Full GC:Full GC
GC的位置
[DefNew:Default New Generation Serial收集器新生代
[ParNew:Parallel新生代
[PSYoungGen:Parallel Scanvenge收集器的新生代
[Tenured:老年代
[Perm:永久代
<<深入Java虚拟机>>-第三章-垃圾收集器与内存分配策略-学习笔记的更多相关文章
- [Note][深入理解Java虚拟机] 第三章 垃圾收集器与内存分配策略笔记
书上关于GCTimeRatio的讲解有点难以理解,查看Oracle的文档后重新理解了下 -XX:GCTimeRatio 运行时间 / GC时间 当GCTimeRatio为19时,运行时间是GC时间的1 ...
- 《深入理解JAVA虚拟机》----------第三章 垃圾收集器与内存分配策略,笔记(下)
1.垃圾收集器 1.1 Serial收集器 这个收集器是一个单线程的收集器,它在进行垃圾收集时,必须暂停其他所有的工作线程. 它是虚拟机运行在Client模式下的默认新生代收集器,它简单而高效. 1. ...
- 《深入理解java虚拟机》第三章 垃圾收集器与内存分配策略
第三章 垃圾收集器与内存分配策略 3.1 概述 哪些内存需要回收 何时回收 如何回收 程序计数器.虚拟机栈.本地方法栈3个区域随线程而生灭. java堆和方法区的内存需要回收. 3.2 对象已死吗 ...
- JVM学习笔记-第三章-垃圾收集器与内存分配策略
JVM学习笔记-第三章-垃圾收集器与内存分配策略 tips:对于3.4之前的章节可见博客:https://blog.csdn.net/sanhewuyang/article/details/95380 ...
- 深入了解Java虚拟机(2)垃圾收集器与内存分配策略
垃圾收集器与内存分配策略 由于JVM中对象的频繁操作是在堆中,所以主要回收的是堆内存,方法区中的回收也有,但是比较谨慎 一.对象死亡判断方法 1.引用计数法 就是如果对象被引用一次,就给计数器+1,否 ...
- 读书笔记,《深入理解java虚拟机》,第三章 垃圾收集器与内存分配策略
要实现虚拟机,其实人们主要考虑完成三件事情: 第一,哪些内存需要回收: 第二,什么时候回收: 第三,如何回收. 第二节,对象已死吗 垃圾收集其实主要是针对java堆里面的数据来说的,传统的垃圾收 ...
- [深入理解JVM虚拟机]第3章-垃圾收集器、内存分配策略
垃圾收集器 判断对象是否需存活 回收堆 判断对象是否存活: 方法一:引用计数法.对象被引用一次就+1,当为0时回收对象.缺点:无法解决循环引用问题. 方法二:可达性分析算法.记录当前对象是否有和GC ...
- 深入理解java虚拟机(2)------垃圾收集器和内存分配策略
GC可谓是java相较于C++语言,最大的不同点之一. 1.GC回收什么? 上一篇讲了内存的分布. 其中程序计数器栈,虚拟机栈,本地方法栈 3个区域随着线程而生,随着线程而死.这些栈的内存,可以理解为 ...
- java虚拟机(二)--垃圾收集器与内存分配策略
1.判断对象是否存活的算法: 1.1.引用计数算法:给对象添加一个引用计数器,每当有一个地方引用他时,计数器+1,当引用失效时,计数器-1,任何时刻计数器为0的对象就是不可能再被引用的,但是他很难解决 ...
随机推荐
- JNI函数复杂对象传递
主要操作内容,包括如下几个部分: 1.在Native层返回一个字符串 2.从Native层返回一个int型二维数组(int a[ ][ ]) 3.从Native层操作Java层的类: 读取/设置类属性 ...
- cas 3.5.2 登录成功后,如何返回用户更多信息?
国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html内部邀请码:C8E245J (不写邀请码,没有现金送)国内私 ...
- python内存管理
python对象三要素: identity(值):对应于内存的地址,不可修改 type(类型):不可修改 value(值): mutable :可以修改 immutable:不可以修改 引用计数 当引 ...
- IE jquery mouseenter,mouseover超奇葩问题
做了个项目,结构很简单 <div class="index-main" data-url="./img/index_default.jpg"> &l ...
- 详解 Objective-C 中的 Runtime
公司项目用到一个三方开源库,里面有个bug,不能改动源码,我想来想去,只能通过runtime这个万能的手段来解决.但是runtime 并不怎么会用,怎么办,马上学习呗.说到runtime,它是Obje ...
- ios 界面间跳转方法总结
接触ios也有一段时间了,偶然间,我们公司的技术总监兼我的导师,问我,你可知道,界面间的跳转有几种方式?我说出了两种,但是也有点含糊其辞,于是我就想一定要找个时间总结一下.有句话说的好,“前人种树,后 ...
- 为Photoshop添加右键快捷
打开注册表,开始--->运行--->regedit 找到 HKEY_CLASSES_ROOT <----> *<---->shell 新建项,使用Photosh ...
- javascript/jquery给动态加载的元素添加click事件
/** 这种写法:在重新加载数据后事件依然有效*/$(document).on('click', '#district_layer ul li', function () { });
- 虚拟机Linux系统中安装SYNOPSYS工具图解教程
V TRON KO 2.8.2 启动 dv 在终端运行命令: lmli2 然后再运行命令: dv V TRON KO V TRO ...
- PHP之数组函数归类
数组之所以强大,除了本身声明.存储方式灵活,它还有坚强后盾:一系列功能各异的数组处理函数.就像一只军队,除了领队将军本身能征善战,指挥英明之外,还有一群不怕死.忠实于他的士兵,这样才能显得整体的强大. ...