要实现虚拟机,其实人们主要考虑完成三件事情:
第一,哪些内存需要回收;
第二,什么时候回收;
第三,如何回收。
第二节,对象已死吗
垃圾收集其实主要是针对java堆里面的数据来说的,传统的垃圾收集方法主要是基于引用计数算法,比如windows里面的com或者是actionscript3里面的flash player,以及python语言,但是引用计数算法无法解决对象之间互相影响的问题。所以Java语言没有选用这种方式。
现在主流的商用语言,比如java,c#,都是采用可达性分析的算法来判断对象是否存活。这里就引入了GC Roots的概念。
在java语言中,GC Roots的主要包括以下几种,
第一种是虚拟机栈所引用的对象;
第二种是方法区中类静态属性引用的对象
第三是方法区常量引用的对象,
还有一种就是本地方法中,JNI引用的对象。
既然说到引用的话,那么这里面就提到了
强引用,软引用,弱应用,虚引用,四种应用概念。其中在我的博客上,有一篇文章介绍了关于
使用弱应用来构建敏感数据缓存的例子。可以参考下。
接下来书中就介绍了,程序如何判断一个对象是生存还是死亡,以及在Java中可以使用Finalize方法来最后一次挽救对象的例子。但是由于finalize是java早期为了吸引C++程序员来使用Java,然后就引入Finalize()来适配C++中的析构方法,现在已经不建议使用这个方法了。
第三节,垃圾收集算法
这一节主要介绍了三种常见的垃圾收集算法。
第一种,标记-清除算法
清除之后原来那个空间就留在那,这是一种最基础的数据收集算法,不过它有两点不足,一个是效率问题,标记和清除两个过程的效率都不高,第二个是空间问题,标记清楚之后,会产生大量不连续的内存碎片,而空间碎片太多,可能会导致以后在程序运行过程中需要分配较大对象的时候,无法找到足够的连续内存,而不得不提前触发另外一次垃圾收集动作。
第二种 复制算法
它的基本思路就是把可用的内存大容量划分为大小相等的两块,然后每次要做垃圾收集的时候,就把其中一块里面仍然存活的对象复制到另外一块,从头开始连续排放,然后原来的这一块就整体清除。这种算法的优点就是实现简单,运行高效,而缺点就是,内存缩小为原来的一半,代价太高了。虚拟机在新生代中采用了这种算法,但是在实现的时候是把内存分为三块(Eden区、Survivor1区,Survivor2区),而且也不是大小相等的,而是一个大两个小。之所以在新生代中采用复制算法,是因为新生代的对象绝大部分是朝生夕死,对象存活率低,所以采用复制算法比较划算。
第三种,标记-整理算法
由于复制收集上法在对象存活率较高的时候需要进行较多的复制操作,效率就降低了。人们就提出了一种,标记-整理算法。先对存活的对象作标记,然后就让所有存活的对象都移动到该片内存的一端,然后再直接清除掉段边界以外的内存。这种算法比较适合老年代的内存回收,因为在老年代中的对象存活率很高。
第四节,Hotspot的的算法实现
这里面主要提出了几个概念,枚举根节点,安全点,安全区域。
关于这一节可以参考我在博客中的
介绍oopmap的一篇文章。里面对于为什么要有oopmap,以及oopmap对于枚举跟节点有什么帮助都说的比较清楚。
第五节,垃圾收集器
这里面主要介绍了新生代的三种垃圾收集器,老年代的三种垃圾收集器,还有最新的G1垃圾收集器。
除了g1收集器,其他的收集器都要把新生代和老年代的垃圾收集器一对一的配合使用。
而G1收集器一个人就可以搞定新生代和老年代,实际上在采用g1收集器的时候,他内部并不是按照新生带到年代的这种方法来分的,他是把内存分为多个大小相等的独立区域,虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理上隔离的了,他们都是一部分区域的集合。
G1收集器的运作大致可划分为以下几个步骤,初始标记,并发标记,最终标记,和筛选回收。
在本节的最后作者,给了一个小结来说明,我们该如何解读虚拟机的垃圾回收日志。其实不同的虚拟机的日志的格式是不一样的,所以要完全看懂这个日志的话,我估计还是要到甲骨文的官网上去找他们的说明文档。
第六节,内存分配与回收策略
JAVA技术体系中所提倡的自动内存管理,最终都可以归结为自动化的解决两个问题:给对象分配内存,以及回收分配给对象的内存。
本节主要是通过各种代码以及虚拟机的启动参数,然后再结合虚拟机的日志来说明Java的内存分配与释放策略。
对象的内存分配在大方向上来讲就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按照线程优先在TLAB上分配,当然,这也不是绝对的,因为有的时候也会在老年带上分配。所以具体的细节还取决于使用了哪一种垃圾收集器,及其相关选项。
第一小节,对象优先在Eden区上分配
本节给了一个例子来说明,对象在新生代Eden区中分配,当Eden区中没有足够空间进行分配的时候,虚拟机将发起一次minor GC。这个例子中通过虚拟机选项限制Java堆的大小为20M,然后又限制新生代和老年带各10M。连续分配几个大对象,当分配到最后一个大对象的时候,新生代的内存就不够用了,此时通过看虚拟机的日志就可以看到,已经出发了一次Minor GC,有的对象已经转移到老年代上了。
这里面有提到一点,就是MinorGC的速度一般会比MajorGC慢十倍以上。
第二小节,大对象直接进入老年代
所谓的大对象,就是指需要大量连续内存空间的java对象,最典型的他对象就是那种很长的字符串以及数组,大对象对于虚拟机的内存分配来说是一个坏消息,那么比遇到一个大对象更坏的消息就是遇到一群朝生夕死的短命大对象,写程序的时候应当避免。因为经常出现大对象很容易导致内存还有不少空间的时候,就提前触发了一次垃圾收集以获取足够的连续空间来安置他们。
所以本节结合虚拟机提供的一个参数 -XX:PretenureSizeThreshold,当某个对象大于这个参数值之后,就直接在老年代上分配。在例子中,作者给把这个参数设置为3M,然后写这一段代码来直接分配一个4M的大对象,通过看虚拟机日可以发现他确实是在老年代中分配的。
第三小节,长期存活的对象将进入老年代
虚拟机采用分代的思想来管理内存,那么回收时就必须能够识别哪些对象应放在新生代哪些对象应放在老年代,虚拟机给每个对象定义了一个对象年龄计算器。对象首先放在eden区,经过第一次Minor GC后,如果仍然能够存活,就会被转移到survivor空间。在survivor空间,年龄增长到一定程度的时候,就会晋升到老年带中。这个晋升的年龄是可以设置的,默认是15岁。在本书中,作者故意把这个默认年龄改成一岁,来做测试。
第四小节,动态对象年龄判断
为了更好的适应不同程序的内存状况,虚拟机并不是永远要等待对象的年龄达到阈值之后才晋升到老年代。如果在Suvivor空间中相同年龄对象的大小的总和大于那个空间的一半,那么年龄大于或等于该年龄的对象,就可以直接进入老年。
第五节,空间分配担保。
所谓的担保空间其实就是老年代的空间。在发生MinorGC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立那么Minor GC就可以保证是安全的。如果不成立,则需立即就会检查一下handlepromotionFailure 设置的值是否允许担保失败,如果允许那么会继续检查老年代最大可用的连续空间是否大于历次,、晋升到老年代对象的平均大小,如果大于,将尝试进行一次MinorGC。如果小于,或者HandlePromotionFailure被设置为不允许冒险,那这时就要改为进行一次FullGC。
这里作者详细说明了什么是老年代的担保,以及是担保失败的风险。作者有提到,如果老年代担保失败,虚拟机就会再进行一次FullGC以回收更多的内存,但是我很怀疑其实再执行FullGC也不一定能够清理出更多的内存。此时要么就是再向系统要更多的内存,要么就是系统也内存不够了,然后虚拟机直接报OutOfMemory。
- [Note][深入理解Java虚拟机] 第三章 垃圾收集器与内存分配策略笔记
书上关于GCTimeRatio的讲解有点难以理解,查看Oracle的文档后重新理解了下 -XX:GCTimeRatio 运行时间 / GC时间 当GCTimeRatio为19时,运行时间是GC时间的1 ...
- <<深入Java虚拟机>>-第三章-垃圾收集器与内存分配策略-学习笔记
垃圾收集 垃圾收集(Garbage Collection,GC),垃圾收集需要完成的三件事情. 哪些对象需要回收 什么时候回收 如何回收 如何确定对象已死(即不可能在被任何途径引用的对象) 引用计数算 ...
- 深入理解java虚拟机(2)------垃圾收集器和内存分配策略
GC可谓是java相较于C++语言,最大的不同点之一. 1.GC回收什么? 上一篇讲了内存的分布. 其中程序计数器栈,虚拟机栈,本地方法栈 3个区域随着线程而生,随着线程而死.这些栈的内存,可以理解为 ...
- [深入理解JVM虚拟机]第3章-垃圾收集器、内存分配策略
垃圾收集器 判断对象是否需存活 回收堆 判断对象是否存活: 方法一:引用计数法.对象被引用一次就+1,当为0时回收对象.缺点:无法解决循环引用问题. 方法二:可达性分析算法.记录当前对象是否有和GC ...
- 《深入理解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虚拟机 第三章 垃圾收集器 笔记
1.1 垃圾收集器 垃圾收集器是内存回收的具体实现.以下讨论的收集器是基于JDK1.7Update14之后的HotSpot虚拟机.这个虚拟机包含的所有收集器有: 上图展示了7种作用于不同分代的收集 ...
- 《深入理解JAVA虚拟机》----------第三章 垃圾收集器与内存分配策略,笔记(下)
1.垃圾收集器 1.1 Serial收集器 这个收集器是一个单线程的收集器,它在进行垃圾收集时,必须暂停其他所有的工作线程. 它是虚拟机运行在Client模式下的默认新生代收集器,它简单而高效. 1. ...
随机推荐
- 解决apache上访问 cgi脚本时总是在网页中显示出脚本的源代码而不是执行结果的问题
apache是支持cgi脚本的,但是需要保证四个条件: 1.放置cgi脚本的文件夹本身需要对apache服务器这个用户(一般默认用户名是www,linux下的用户机制请自行百度)开放x(即可执行)权限 ...
- NLP文本相似度(TF-IDF)
本篇博文是数据挖掘部分的首篇,思路主要是先聊聊相似度的理论部分,下一篇是代码实战. 我们在比较事物时,往往会用到“不同”,“一样”,“相似”等词语,这些词语背后都涉及到一个动作——双方的比 ...
- FTP传输一定要注意使用二进制模式
一个问题困扰了我一下午,刚刚才解决.我要上传一个PHP程序,其中用了sqlite数据库,没想到上传完以后无论如何也不能用,总是数据库查询失败.我登录上SSH,把几乎每个php都调试了一遍,还是没法解决 ...
- TC SRM601
250 水题,上来想一下就可以搞了. 500,比赛时候没有想出来,结束后补了下,感觉很简单. 只要枚举两个集合异或结果哪一位二进制位不同,因为大小由最高位不同决定. 然后进行DP就可以了,比较好的题目 ...
- 微信emoji表情编码 、MySQL 存储 emoji 表情符号字符集
相关资料 微信emoji表情编码 微信用户名显示「emoji表情」 PHP处理微信中带Emoji表情的消息发送和接收(Unicode字符转码编码) MySQL 存储emoji表情 MySQL 存储 e ...
- 避免string.Format方法的装箱
我们知道,使用string.Format方法可能会存在装箱的情况.比如如下: static void Main(string[] args) { string s = string.Format(&q ...
- uitextfield 设置为密码框显示
uitextfield 设置为密码框显示: 在xib中,将文本secure的复选框选中即可.
- Android Studio 出现 Gradle's dependency cache may be corrupt 错误分析
http://blog.csdn.net/u014231734/article/details/41913775 情况说明: 之前下载了 Android Studio 1.0rc2候选版,那时候把 S ...
- Codeforces Round #131 Div1 B
Problem 给出Ai(i∈[0,9]).表示每一个数字至少须要出现多少次.问有多少个数满足下面三个条件:1. 数至多有N位:2. 数不含有前导0:3. 每一个数 i 出现的次数不少于Ai(mod ...
- JVM调优系列:(五)JVM常用调试参数和工具
转自:http://blog.csdn.net/opensure/article/details/46715769 JVM常用调试参数: –verbose:gc在虚拟机发生内存回收时在输出设备显示信息 ...