JVM中对象的回收过程
当我们的程序开启运行之后就,就会在我们的java堆中不断的产生新的对象,而这是需要占用我们的存储空间的,因为创建一个新的对象需要分配对应的内存空间,显然我的内存空间是固定有限的,所以我们需要对没有用的对象进行回收,本文就来记录下JVM中对象的销毁过程。
1.怎么判断对象是没用的了
引用计数算法
我们在很多场景中会听到java对象判断存活的方式是计算该对象的引用计数器是否为0,如果为0就说明没有其他变量引用该对象了,这个对象就可以被垃圾收集器回收了。但事实上JVM并不是采用该算法来判断对象是否可以回收的,比如objectA.a=objectB及objectB.b=objectA除此之外没有其他引用了。但是按照引用计数算法是不会回收这两个对象的。但是这两个对象也已经不能被其他对象访问了,所以这就是问题。
可达性分析算法
java中判断对象是否可以回收是通过可达性分析算法来实现的。如下图:
在上图中object5,object6及object7这三个对象虽然有相互之间的引用,但是通过GC Roots对象并不能引用到这三个对象,所以这三个对象是满足回收条件的,而对象1到4通过GC Roots可达,所以这几个对象任然存活。
GC Roots并不是一个对象,而是一组对象,在java中可以作为GC Roots对象的有如下几种:
序号 | 类型 |
---|---|
1 | 虚拟机栈(本地变量表)中引用的对象 |
2 | 方法区中类静态属性引用的对象 |
3 | 方法区中常量引用的对象 |
4 | 本地方法栈中JNI(一般说的Native方法)引用的对象 |
2.对象的引用分类
判断对象是否存活我们是通过GC Roots的引用可达性来判断的,但是引用关系并不止一种,而是有四种分别是:强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference)和虚引用(Phantom Reference).引用强度依次减弱。
强引用
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMmoryError错误,使程序异常终止,也不会靠随意回收具有强引用 对象来解决内存不足的问题。
软引用
软引用是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
/**
* 软引用:缓存场景的使用
* @author dengp
*
*/
public class SoftReferenceTest {
/**
* 运行参数 -Xmx200m -XX:+PrintGC
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
//存储100M的缓存数据
byte[] cacheData = new byte[100 * 1024 * 1024];
//将缓存数据用软引用持有
SoftReference<byte[]> cacheRef = new SoftReference<>(cacheData);
//将缓存数据的强引用去除
cacheData = null;
System.out.println("第一次GC前" + cacheData);
System.out.println("第一次GC前" + cacheRef.get());
//进行一次GC后查看对象的回收情况
System.gc();
//等待GC
Thread.sleep(500);
System.out.println("第一次GC后" + cacheData);
System.out.println("第一次GC后" + cacheRef.get());
//在分配一个120M的对象,看看缓存对象的回收情况
// 空间不够
byte[] newData = new byte[120 * 1024 * 1024];
System.out.println("分配后" + cacheData);
System.out.println("分配后" + cacheRef.get());
}
}
输出结果
第一次GC前null
第一次GC前[B@15db9742
[GC (System.gc()) 104396K->103072K(175104K), 0.0054505 secs]
[Full GC (System.gc()) 103072K->102931K(175104K), 0.0095426 secs]
第一次GC后null
第一次GC后[B@15db9742
[GC (Allocation Failure) 103597K->102995K(175104K), 0.0099572 secs]
[GC (Allocation Failure) 102995K->102963K(175104K), 0.0044781 secs]
[Full GC (Allocation Failure) 102963K->102931K(175104K), 0.0226699 secs]
[GC (Allocation Failure) 102931K->102931K(199680K), 0.0022288 secs]
[Full GC (Allocation Failure) 102931K->519K(131072K), 0.0226120 secs]
分配后null
分配后null
从上面的示例中就能看出,软引用关联的对象不会被GC回收。JVM在分配空间时,若果Heap空间不足,就会进行相应的GC,但是这次GC并不会收集软引用关联的对象,但是在JVM发现就算进行了一次回收后还是不足(Allocation Failure),JVM会尝试第二次GC,回收软引用关联的对象。
像这种如果内存充足,GC时就保留,内存不够,GC再来收集的功能很适合用在缓存的引用场景中。在使用缓存时有一个原则,如果缓存中有就从缓存获取,如果没有就从数据库中获取,缓存的存在是为了加快计算速度,如果因为缓存导致了内存不足进而整个程序崩溃,那就得不偿失了。
弱引用
弱引用也是用来描述非必须对象的,他的强度比软引用更弱一些,被弱引用关联的对象,在垃圾回收时,如果这个对象只被弱引用关联(没有任何强引用关联他),那么这个对象就会被回收
/**
* 弱引用
* @author dengp
*
*/
public class WeakReferenceTest {
/**
* 运行参数 -Xmx200m -XX:+PrintGC
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
//存储100M的缓存数据
byte[] cacheData = new byte[100 * 1024 * 1024];
//将缓存数据用软引用持有
WeakReference<byte[]> cacheRef = new WeakReference<>(cacheData);
//将缓存数据的强引用去除
cacheData = null;
System.out.println("第一次GC前" + cacheData);
System.out.println("第一次GC前" + cacheRef.get());
//进行一次GC后查看对象的回收情况
System.gc();
//等待GC
Thread.sleep(500);
System.out.println("第一次GC后" + cacheData);
System.out.println("第一次GC后" + cacheRef.get());
//在分配一个120M的对象,看看缓存对象的回收情况
byte[] newData = new byte[120 * 1024 * 1024];
System.out.println("分配后" + cacheData);
System.out.println("分配后" + cacheRef.get());
}
}
输出结果
第一次GC前null
第一次GC前[B@15db9742
[GC (System.gc()) 104396K->103072K(175104K), 0.0013337 secs]
[Full GC (System.gc()) 103072K->531K(175104K), 0.0070222 secs]
第一次GC后null
第一次GC后null
分配后null
分配后null
弱引用直接被回收掉了。那么弱引用的作用是什么?或者使用场景是什么呢?
static Map<Object,Object> container = new HashMap<>();
public static void putToContainer(Object key,Object value){
container.put(key,value);
}
public static void main(String[] args) {
//某个类中有这样一段代码
Object key = new Object();
Object value = new Object();
putToContainer(key,value);
//..........
/**
* 若干调用层次后程序员发现这个key指向的对象没有用了,
* 为了节省内存打算把这个对象抛弃,然而下面这个方式真的能把对象回收掉吗?
* 由于container对象中包含了这个对象的引用,所以这个对象不能按照程序员的意向进行回收.
* 并且由于在程序中的任何部分没有再出现这个键,所以,这个键 / 值 对无法从映射中删除。
* 很可能会造成内存泄漏。
*/
key = null;
}
在《Java核心技术卷1》这本书中对此做了说明
设计 WeakHashMap类是为了解决一个有趣的问题。如果有一个值,对应的键已经不再 使用了, 将会出现什么情况呢? 假定对某个键的最后一次引用已经消亡,不再有任何途径引 用这个值的对象了。但是,由于在程序中的任何部分没有再出现这个键,所以,这个键 / 值 对无法从映射中删除。为什么垃圾回收器不能够删除它呢? 难道删除无用的对象不是垃圾回 收器的工作吗?
遗憾的是,事情没有这样简单。垃圾回收器跟踪活动的对象。只要映射对象是活动的, 其中的所有桶也是活动的, 它们不能被回收。因此,需要由程序负责从长期存活的映射表中 删除那些无用的值。 或者使用 WeakHashMap完成这件事情。当对键的唯一引用来自散列条目时, 这一数据结构将与垃圾回收器协同工作一起删除键 / 值对。
下面是这种机制的内部运行情况。WeakHashMap 使用弱引用(weak references) 保存键。 WeakReference 对象将引用保存到另外一个对象中,在这里,就是散列键。对于这种类型的 对象,垃圾回收器用一种特有的方式进行处理。通常,如果垃圾回收器发现某个特定的对象 已经没有他人引用了,就将其回收。然而, 如果某个对象只能由 WeakReference 引用, 垃圾 回收器仍然回收它,但要将引用这个对象的弱引用放人队列中。WeakHashMap将周期性地检 查队列, 以便找出新添加的弱引用。一个弱引用进人队列意味着这个键不再被他人使用, 并 且已经被收集起来。于是, WeakHashMap将删除对应的条目。
除了WeakHashMap使用了弱引用,ThreadLocal类中也是用了弱引用,可以自行了解下。
虚引用
一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知
3.finalize方法
当一个对象在堆内存中运行时,根据它被引用变量所引用的状态,可以把它所处的状态
可达状态:当一个对象被创建后,若有一个以上的引用变量引用它,则这个对象在程序中处于可达状态。
可恢复状态:如果程序中某个对象不再有任何引用变量引用它,它就进入了可恢复状态。此时,系统的垃圾回收机制准备回收该对象所占用的内存,在回收该对象之前,系统会调用所有可恢复状态对象的finalize()方法进行资源清理。如果系统在调用finalize()方法时重新让一个引用变量引用该对象,则这个对象会再次变成可达状态;否则该对象将进入不可达状态。
不可达状态:当对象与所有引用变量的关联都被切断,且系统已经调用所有对象的finalize()方法后依然没有使该对象变成可达状态,那么这个对象将永久性地失去引用,最后变成不可达状态。只有当一个对象处于不可达状态时,系统才会真正回收该对象所占有的资源。
所以finalize方法只对象存活的最后一次机会,而且只会执行一次。
销毁一个对象过程归纳如下:
4.方法区的回收
很多人认为方法区(或者HotSpot虚拟机中的永久代)是没有垃圾收集的,Java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾收集,而且在方法区进行垃圾收集的“性价比”一般比较低:在堆中,尤其是在新生代中,常规应用进行一次垃圾收集一般可以回收70%~95%的空间,而永久代的垃圾收集效率远低于此。
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。回收废弃常量与回收Java堆中的对象非常类似。以常量池中字面量的回收为例,假如一个字符串“abc”已经进入了常量池中,但是当前系统没有任何一个String对象是叫做“abc”的,换句话说是没有任何String对象引用常量池中的“abc”常量,也没有其他地方引用了这个字面量,如果在这时候发生内存回收,而且必要的话,这个“abc”常量就会被系统“请”出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面3个条件才能算是“无用的类”:
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是“可以”,而不是和对象一样,不使用了就必然会回收。是否对类进行回收,HotSpot虚拟机提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class及-XX:+TraceClassLoading、 -XX:+TraceClassUnLoading查看类的加载和卸载信息。
在大量使用反射、动态代理、CGLib等bytecode框架的场景,以及动态生成JSP和OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。
参考《深入理解Java虚拟机》
JVM中对象的回收过程的更多相关文章
- JVM中对象的创建过程
JVM中对象的创建过程如以下流程图中所示: 对其主要步骤进行详细阐述: 为新生对象分配内存: 内存的分配方式: 指针碰撞:假设Java堆中内存是绝对规整的,所有用过的内存放在一边,空闲的内存在另一边, ...
- Java虚拟机笔记(五):JVM中对象的分代
为什么要分代 为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能.你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用, ...
- 浅谈jvm中的垃圾回收策略
下面小编就为大家带来一篇浅谈jvm中的垃圾回收策略.小编觉得挺不错的,现在就分享给大家,也给大家做个参考.一起跟随小编过来看看吧 java和C#中的内存的分配和释放都是由虚拟机自动管理的,此前我已 ...
- JVM中的垃圾回收
GC 垃圾回收回收什么 Java的内存分配原理与C/C++不同.C/C++每次申请内存时都要malloc进行系统调用.而系统调用发生在内核空间,每次都要中断进行切换,这须要一定的开销. 而Java虚拟 ...
- 学习一下 JVM (二) -- 学习一下 JVM 中对象、String 相关知识
一.JDK 8 版本下 JVM 对象的分配.布局.访问(简单了解下) 1.对象的创建过程 (1)前言 Java 是一门面向对象的编程语言,程序运行过程中在任意时刻都可能有对象被创建.开发中常用 new ...
- JVM 中的垃圾回收
说到JVM,总是绕不开垃圾回收,因为其是JVM的核心之一,因为对象的创建是非常频繁的,想要提高程序的执行效率,拥有一个高效的垃圾回收机制是必不可少的. 首先要明确,因为对象的创建只在堆中,所以垃圾回收 ...
- JVM中对象的销毁
1.可达性分析算法: 可达性分析算法用来寻找将要销毁的对象,它的基本思路是:通过一系列的称为“GC ROOTs”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链,当一个对象到GC ...
- @JVM中对象的引用类型
JVM中有四种引用类型:强引用.软引用.弱引用.虚引用 强引用(Stong Reference):是指在程序代码中普遍存在的,类似:Object obj = new Object()这类的引用,只 ...
- JVM(三) 对象的创建过程
1.对象的创建过程 1.1 . 给对象分配内存 对象的内存分配有两种方式,一种是指针碰撞另外一种是空闲列表的方式,堆是否规整由我们垃圾回收器来决定的 ,如果垃圾回收带有我们的压缩算法,那么他会规整的分 ...
随机推荐
- Stanford CS20学习笔记
Lecture Note 2 Tensorboard P3 Data Structures P4 Math Operations P6 Data Types P7 tf native &&am ...
- 微信小程序的自定义插件
第一步,创建一个页面和普通页面一样 第二不,在这个页面上进行对json配置, "component":true 第三不在需要插入的页面中进行设置 插入标签 <dialog i ...
- Photoshop功能组成色彩快捷键
功能 专业测评 Photoshop的专长在于图像处理,而不是图形创作.图像处理是对已有的位图图像进行编辑加工处理以及运用一些特殊效果,其重点在于对图像的处理加工:图形创作软件是按照自己的构思创意,使用 ...
- mybatis批量更新报错badsql
mybatis批量更新时语法写的都对,但是报错,需要在连接上面加上allowMultiQueries=true 示例:jdbc:MySQL://192.168.1.236:3306/test?useU ...
- win7里linux虚拟机安装vmware tools(ubuntu12.04)
安装Vmware Tools工具 1.安装linux虚拟机(略) 2.虚拟机去启动,选择虚拟机à设置,“硬件”中选择CD/DVD(IDE),右侧选择“使用ISO镜像文件(M)” -- 文件选择vmw ...
- 可遇不可求的Question之MySqlClient的Guid 类型的映射篇
关于 Guid 类型的映射 MySql 没有原生的 Guid 类型,一般使用 binary(16) 或者 char(36) 这两个类型.早期版本的 Connector/Net 将 binary(16) ...
- JS求数组差集的几种方法
第一种:如果不考虑IE8的兼容性完全可以使用Foreach ,此方法求出arr1 减去 arr2的差集, arr1 = [1,2,3,4];arr2 = [1,2,3]; var subSet = f ...
- Mimikatz 法国神器
0x00 标准模块 Module : standardFull name : Standard moduleDescription : Basic commands (does not require ...
- quartus 一种管脚分配方法
第一步: 在QII软件中,使用“Assignments -> Remove Assignments”标签,移除管脚分配内容,以确保此次操作,分配的管脚没有因为覆盖而出现错误的情况. 编写xxx. ...
- 自动的输入号码的辅助软件在ie浏览器下的尝试
在ie下面的自动输入号码的软件,我已经折腾了好久.由于ie环境非常复杂:网页延迟这个时间一直不能准确的得到,这个时间主要包括:网络的与服务器的交换速度,网页自身的加载速度,网页的js渲染的效果的影响. ...