要实现虚拟机,其实人们主要考虑完成三件事情:
第一,哪些内存需要回收;
第二,什么时候回收;
第三,如何回收。
第二节,对象已死吗
垃圾收集其实主要是针对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. ...
随机推荐
- 工程化框架之feather
feather是一个工程化框架,他的主要任务是框架规范.性能优化.代码部署.自动化.本地调试.多人协同.静态资源管理. 一.安装 因为feather 为npm包,要安装node.js: 如果需要本地调 ...
- linux安装project lemon测评机
(写下备用) 机子:xubuntu 16.04 LTS 1.下载lemon github地址:https://github.com/Sojiv/Project_lemon 这里download zip ...
- 20172319 实验四 《Android程序设计》实验报告
20172319 2018.05.17-30 实验四<Android程序设计> 实验报告 课程名称:<程序设计与数据结构> 学生班级:1723班 学生姓名:唐才铭 学生学号:2 ...
- nginx+uwsgi+flask 服务器配置
注:每个机器,软件版本可能不一样,虽然网上有很多类似的帖子,但是我在搭建的时候遇到了不少的坑,此文仅供参考. 请求流程: 1.安装uwsgi uwsgi是一个应用服务器,非静态文件的网络请求就必须通过 ...
- jtagger Versatile multiprogrammer for FPGAs, MCUs, etc.
jtagger Versatile multiprogrammer for FPGAs, MCUs, etc. Well, it's not really just a jtagger, but I' ...
- 解决oracle语句中 含数字的字符串按数字排序问题
普通排序利用:order by 字段名 ASC 但是遇到有中文而且类型是varchar类型的结果就是这样 政采代(甲)字第0298号 政采代(甲)字第0421号 政采代(甲)字第1098号 政采代(甲 ...
- [Go] Http / Net 相关资料
[astaxie] [基础]GO搭建一个简单的Web服务器 [astaxie] Go如何使得Web工作 [astaxie] Go 的 Http 包详解 [叶剑峰] Go语言_HTTP包 [叶剑峰] 使 ...
- 查看sqlserver2008数据库服务器实例名称
select @@SERVICENAME 安装SQLServer时,如果不另外设置数据库实例名称,那么默认的数据库实例名就是MSSQLSERVER
- redis哈希缓存数据表
redis哈希缓存数据表 REDIS HASH可以用来缓存数据表的数据,以后可以从REDIS内存数据库中读取数据. 从内存中取数,无疑是很快的. var FRedis: IRedisClient; F ...
- C#编程(二十八)----------泛型类的功能
泛型类的功能 在创建泛型类时,还需要一些其他的C#关键字.例如,不能把null赋予泛型类型.此时,可以使用default关键字.如果泛型类型不需要object类的功能,但需要调用泛型类上的某些特定方法 ...