[深入理解Java虚拟机]<垃圾收集器与内存分配策略>
Overview
- 垃圾收集考虑三件事:
- 哪些内存需要回收?
- 什么时候回收?
- 如何回收?
- 重点考虑Java堆中动态分配和回收的内存。
Is Object alive?
引用计数法
- 给对象添加一个引用计数器。
- 该方法实现简单,判定效率高。但是它很难解决对象之间相互循环引用的问题,因此几乎很少有JVM选用该方法。eg:
- public class ReferenceCountingGC {
- public Object instance = null;
- // 占点内存,以便在GC日志中看清楚是否被回收过
- private static final int _1MB = 1024 * 1024;
- private byte[] bigSize = new byte[2 * _1MB];
- public static void testGC() {
- ReferenceCountingGC objA = new ReferenceCountingGC();
- ReferenceCountingGC objB = new ReferenceCountingGC();
- objA.instance = objB;
- objB.instance = objA;
- objA = null;
- objB = null;
- System.gc();
- }
- }
- public class ReferenceCountingGC {
可达性分析算法
- 在主流商用程序语言(Java, C#, Lisp)的主流实现中,都是通过可达性分析(Reachability Analysis)来判断对象是否存活的。
- 该算法的基本思路是通过一系列的称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索。搜索的路径称为引用链(Reference Chain)。
- 当一个对象到GC Roots没有任何引用链相连(即不可达)时,则该对象是不可用的。不可达的对象是可回收的。如下:
- 在Java中,可作为GC Roots的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中的引用;
- 方法区中类静态属性引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中JNI引用的对象。
再谈引用
- 上面两种方法都是通过“引用”来判定对象是否alive。
- 但是在JDK1.2之前,Java中的引用定义很传统:若reference类型的数据中存储的数值是另一块内存的起始地址,则称这块内存代表着一个引用。
- 但是考虑这样的场景:我们希望描述这样的一类对象,当内存空间足够时,则能保留在内存中,否则可抛弃。 <-- 很多系统的缓存功能都符合这样的应用场景。
- 于是在JDK1.2之后,Java扩充了引用的概念:将引用分为强引用(Strong Reference), 软引用(Soft Reference), 弱引用(Weak Reference), 虚引用(Phantom Reference) 4种,强度依次减弱。
- 强引用:最常见的,类似Object obj = new Object()
- 软引用:用以描述一些还有用但是非必须的对象。对于该类对象,系统在将要发生内存溢出异常之前,会回收这些对象。JDK1.2之后提供了SoftReference类。
- 弱引用:也是用以描述非必须对象,但强度更弱于软引用。被弱引用关联的对象只能生存到下一次GC之前,而无论当前内存是否足够。
- 虚引用:PhantomReference类。
生存还是死亡
- 即使是在可达性分析算法中不可达的对象,也并非“非死不可”的。
- 要真正宣告一个对象死亡,至少要经历两次标记过程:TBD...
回收方法区
- 一般来说,在方法区进行垃圾收集的"性价比"比较低:在堆中,尤其是新生代中,常规应用一次垃圾收集一般可以回收70%~95%的空间,而永久代的垃圾收集效率远低于此。
- 永久代的垃圾收集主要回收两部分:废弃常量和无用的类。
- 回收废弃常量与回收Java堆中的对象非常相似。假设一个字符串"abc"已经进入常量池,但当前系统中没有任何一个String对象引用常量池中的"abc"常量,则"abc"常量就会被系统清理出常量池。
- 相比之下判定一个类是否是“无用的类”的条件则相对苛刻很多。需同时满足:
- 该类所有的实例已经被回收;
- 加载该类的ClassLoader已经被回收;
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
垃圾收集算法
标记-清除算法
- 算法分为“标记”和“清除”两个阶段。
- 该算法是后续收集算法的基础。
- 不足:
- 效率问题:标记和清除两个过程的效率都不高;
- 空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次GC。
复制算法
- 它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。
- 当使用的块用完时,就将还存活着的对象复制到另一块上面,然后再将已使用过的内存空间一次清理掉。
- 这样使得每次都是对整个半区进行内存回收,内存分配时也不用考虑内存碎片等情况,只用移动堆顶指针,顺序分配即可。
- 代价是内存缩小为原来的一半。
- 现在的商业JVM都采用这种收集算法。IBM的专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例划分内存空间。而是将内存分为一块较大的Eden空间和两块较小的Survivor空间。
标记-整理算法
- 复制收集算法在对象存活率较高时就需要较多的复制操作,效率将会变低。更关键的是,若不想浪费一般的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以老年代一般不能采用这种算法。
- 标记-整理算法:标记过程仍同于标记-清除算法。但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端外边的内存。
分代收集算法
- 当前商业JVM的GC都采用“分代收集”(Generational Collection)算法。
- 这种算法就是根据对象存货周期地不同将内存划分为几块。
- 一般是把Java堆分为新声代和老生代,从而根据各个年代的特点采用最适当的收集算法。
Summary
- 本篇主要覆盖了GC的两大问题:
- 哪些对象需要回收:最基础的是引用计数法,简单但无法解决循环引用问题。在此基础上,更常用的是可达性分析算法。
- 如何回收:最基础的是标记-清除法,即先标记出哪些对象需要回收,然后逐一清除,不足是会产生大量内存碎片,从而导致后续可能触发多次GC。此外更高效的是复制法,该方法确保了连续内存,但是将内存缩小至原内存一般,并且在对象存活率较高时会引入大量复制操作,效率降低。还有标记-整理算法。 另外,商业JVM都会采用分代收集算法,即将内存划分为几块,根据年代选取合适的收集算法。
[深入理解Java虚拟机]<垃圾收集器与内存分配策略>的更多相关文章
- 《深入理解Java虚拟机》虚拟机性能监控与故障处理工具
上节学习回顾 从课本章节划分,<垃圾收集器>和<内存分配策略>这两篇随笔同属一章节,主要是从理论+实验的手段来讲解JVM的内存处理机制.好让我们对JVM运行机制有一个良好的概念 ...
- 《深入理解 java虚拟机》学习笔记
java内存区域详解 以下内容参考自<深入理解 java虚拟机 JVM高级特性与最佳实践>,其中图片大多取自网络与本书,以供学习和参考.
- (1) 深入理解Java虚拟机到底是什么?
好文转载:http://blog.csdn.net/zhangjg_blog/article/details/20380971 什么是Java虚拟机 作为一个Java程序员,我们每天都在写Java ...
- 深入理解java虚拟机(7)---线程安全 & 锁优化
关于线程安全的话题,足可以使用一本书来讲解这些东西.<Java Concurrency in Practice> 就是讲解这些的,在这里 主要还是分析JVM中关于线程安全这块的内容. 1. ...
- 深入理解java虚拟机(6)---内存模型与线程 & Volatile
其实关于线程的使用,之前已经写过博客讲解过这部分的内容: http://www.cnblogs.com/deman/category/621531.html JVM里面关于多线程的部分,主要是多线程是 ...
- 深入理解java虚拟机(5)---字节码执行引擎
字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...
- 深入理解java虚拟机(4)---类加载机制
类加载的过程包括: 加载class到内存,数据校验,转换和解析,初始化,使用using和卸载unloading过程. 除了解析阶段,其他过程的顺序是固定的.解析可以放在初始化之后,目的就是为了支持动态 ...
- 深入理解java虚拟机(1)------内存区域与内存溢出
在C++领域,关于C++的内存存储,结构等等,有一本书:深度探索C++对象模型,讲解的非常透彻. 而Java确把这一工作交给了虚拟机来处理. 我们首先来看看关于内存的问题. 1.问题: 1)java ...
- 什么是HotSpot VM & 深入理解Java虚拟机
参考 http://book.2cto.com/201306/25434.html 另外,这篇文章也是从一个系列中得出的: <深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)> ...
- 【Todo】深入理解Java虚拟机 读书笔记
有一个在线系列地址 <深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)> http://book.2cto.com/201306/25426.html 已经下载了这本书(60多M ...
随机推荐
- hdu-5985 概率DP
http://acm.hdu.edu.cn/showproblem.php?pid=5985 作为队里负责动态规划的同学,做不出来好无奈啊.思考了一个下午,最好还是参考了别人的思想才写出来,数学啊!! ...
- hdu-3001 三进制状态压缩+dp
用dp来求最短路,虽然效率低,但是状态的概念方便解决最短路问题中的很多限制,也便于压缩以保存更多信息. 本题要求访问全图,且每个节点不能访问两次以上.所以用一个三进制数保存全图的访问状态(3^10,空 ...
- docker-compose 在线安装升级
参考:https://docs.docker.com/compose/install/ curl -L "https://github.com/docker/compose/releases ...
- sparklyr-R语言访问Spark的另外一种方法
Connect to Spark from R. The sparklyr package provides a complete dplyr backend. Filter and aggregat ...
- laravel请求到响应的生命周期
请求到响应的核个执行过程,主要可以归纳为四个阶段,即程序启动准备阶段.请求实例化阶段.请求处理阶段.响应发送和程序终止阶段. public\index.php中有这么一段代码 $app = requi ...
- spring cloud jwt用户鉴权及服务鉴权
用户鉴权 客户端请求服务时,根据提交的token获取用户信息,看是否有用户信息及用户信息是否正确 服务鉴权 微服务中,一般有多个服务,服务与服务之间相互调用时,有的服务接口比较敏感,比如资金服务,不允 ...
- cin.get()函数使用例子
#include <iostream>using namespace std; int k = 0; int main(){ char a[1000]; char c; do { cin. ...
- Win10系列:VC++绘制几何图形1
本小节主要介绍如何使用Direct2D来绘制几何图形,其中会使用到FillGeometry函数和FillEllipse函数,FillGeometry函数用于填充几何图形的内部区域,而FillEllip ...
- Promise,async/await解决回调地狱
先说一下async的用法,它作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行. 写一个async 函数 as ...
- js数组及常用数学方法
数组方法 清空数组 1: arr.length=0; 2: arr=[]; arr.push() //往数组最后一个添加元素,会待会一个返回值,就是新的数组长度arr.uns ...