JVM GC之对象生死
1.简述
在Java内存运行时区域的各个部分中,程序计数器、虚拟机栈、本地方法栈3个区域随着线程而生,随着线程而亡。栈中的栈帧随着方法的进入和退出而有条不紊的进行着入栈和出栈操作。
每个栈帧需要分配多少内存基本上在类结构确定时就已知了,因此这几个区域的内存分配和回收具有可确定性,在这几个区域就不需要过多的考虑内存的分配和回收问题,因为方法结束或线程结束时,内存自然就释放了。而java堆和方法区不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,只有在程序运行期间才能知道会创建哪些对象,这部分的内存分配和回收都是动态的,垃圾收集器所关注就是这部分内存,因此本文的后续讨论中的“内存”分配与回收也仅指这部分内存
2.如何判断对象死亡
在堆里存放着java几乎所有的对象实例,垃圾收集器在对堆中的内存进行回收时,第一件事情就是要确定哪些对象是“存活”的,哪些对象是“死亡”的。那么如何判断对象是否可回收呢,引用计数器算法和可达性分析法
2.1.引用计数算法
引用技术算法就是,给对象添加一个引用计数器,当对象被引用时计数器就加1;当引用失效时,计数器就减1;当计数器为0时,表示对象就不能再被使用了。
使用引用计数算法判断对象的存活,方法很简单,但是该方法很难解决对象之间循环引用的问题。
举个简单的例子:
对象objA和对象objB都有字段instance,对其赋值objA.instance=objB、objB.instance=objA,此时对象objA和objB互相引用着对方,导致他们的计数器都不为0,于是引用计数算法无法通知垃圾收集器回收他们
2.1.2.可达性分析法
通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象时不可用的。如图:对象object5,object6,object7虽然相互有关联,但是他们到GC Roots是不可达的,所以它们将会被判定为可回收对象

在Java语言中,可作为GC Roots的对象包括下面几种:
- [x] 虚拟机栈(栈帧中的本地变量表)中的引用对象
- [x] 方法区中类静态属性引用的对象
- [x] 方法区中常量引用的对象
- [x] 本地方法栈中JNI(Native方法)引用的对象
2.1.3.Java引用
无论是通过引用计数器算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判断对象存活都与“引用”有关。
Java将引用分为强引用(Strong Reference)、软引用(Weak Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这四种引用强度一次逐渐减弱。
强引用(Strong Reference)
强引用是使用最普遍的引用。如果一个对象具有强引用,垃圾收集器永远不会回收掉被引用的对象
Object obj=new Object() //强引用
当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会随意回收具有强引用的对象来解决内存不足的问题。
如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。
obj=null //告诉垃圾收集器此对象可以被回收
软引用(Weak Reference)
软引用是用来描述一些有用但非必须的对象。
String str=new String("123"); // 强引用
SoftReference<String> softRef=new SoftReference<String>(str); // 软引用
如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;
如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。
SoftReference的特点是它的一个实例保存对一个Java对象的软引用, 该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。
也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对 这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。
另外,一旦垃圾线程回收该Java对象之 后,get()方法将返回null。
弱引用(Weak Reference)
弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示
public class WeakReferenceDemo {
public static void main(String[] args) {
WeakReference<User> reference=new WeakReference<User>(new User("make",19));
System.out.println(reference.get());
System.gc();//通知垃圾收集器回收资源
System.out.println(reference.get());
}
@Data
@AllArgsConstructor
public static class User{
private String name;
private int age;
}
}
输出
WeakReferenceDemo.User(name=make, age=19)
null
第二个输出结果是null,这说明只要JVM进行垃圾回收,被弱引用关联的对象必定会被回收掉。
不过要注意的是,这里所说的被弱引用关联的对象是指只有弱引用与之关联,如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象(软引用也是如此),稍微修改一下代码,再看一下输出结果:
public class WeakReferenceDemo {
public static void main(String[] args) {
User user=new User("make",19);//强引用
WeakReference<User> reference=new WeakReference<User>(user);
System.out.println(reference.get());
System.gc();//通知垃圾收集器回收资源
System.out.println(reference.get());
}
@Data
@AllArgsConstructor
public static class User{
private String name;
private int age;
}
}
输出
WeakReferenceDemo.User(name=make, age=19)
WeakReferenceDemo.User(name=make, age=19)
虚引用(Phantom Reference)
虚引用也称为幽灵引用或者幻影引用,他是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对它的生命周期造成影响,也无法通过虚引用来获取一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知,在Java中通过PhantomReference类来实现虚引用。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
引用总结
引用级别由高到低:强引用 > 软引用 > 弱引用 > 虚引用
| 引用类型 | 回收时机 | 用途 | 生存时间 |
|---|---|---|---|
| 强引用 | 从不 | 一般对象 | jvm运行终止 |
| 软引用 | 内存不足时 | 对象缓存 | 内存不足时终止 |
| 弱引用 | 垃圾回收时 | 对象缓存 | 垃圾回收后终止 |
| 虚引用 | 未知 | 垃圾回收通知 | 垃圾回收后终止 |
3.对象的自我救赎
即使在可达性分析算法中不可达的对象,也并非是“非死不可”,这时候它处于待回收阶段,要真正的宣告一个对象的死亡,至少要经历两次标记过程:
第一次,可达性分析后发现对象不可达,将会被第一次标记并进行一次筛选,筛选条件是判断该对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或已经被虚拟机调用过finalize()方法,虚拟机将这两种情况称为“没有必要执行”
第二次,如果这个对象被判定为有必要执行finalize()方法,那么这个对象会被放在一个叫做F-Queue的队列里,并在稍后由一个虚拟机自动创建的、低优先级的Finalizer线程去执行它。finalize()方法是对象逃脱死亡的最后一次机会,稍后GC将对F-Queue中的对象进行第二次标记。如果对象在finalize()中成功拯救自己,重新与引用链上的任何一个对象建立了关联,那么第二次标记将把它移除“即将回收”的集合。否则,就会真正被回收
对象自我救赎实例:
package com.lkf.jvm;
/**
* 此代码演示了两点:
* 一是:对象可以被GC自我救赎
* 二是:这种自我救赎的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
**/
public class FinalizeEscapeGCDemo {
public static FinalizeEscapeGCDemo SAVE_HOOK = null;
public void isAlive() {
System.out.println("Yes,I'm still alive :)");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!");
FinalizeEscapeGCDemo.SAVE_HOOK = this;
}
public static void main(String[] args) throws Throwable {
SAVE_HOOK = new FinalizeEscapeGCDemo();
//对象第一次成功拯救自己
SAVE_HOOK = null;
System.gc();
//因为finalize方法优先级很低,所以暂停0.5秒等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("No,I'm dead :)");
}
//下面这段代码与上面的完全相同,但是这次自救却失败了
SAVE_HOOK = null;
System.gc();
//因为finalize方法优先级很低,所以暂停0.5秒等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("No,I'm dead :)");
}
}
}
输出:
finalize method executed!
Yes,I'm still alive :)
No,I'm dead :)
JVM GC之对象生死的更多相关文章
- 如何避免后台IO高负载造成的长时间JVM GC停顿(转)
译者著:其实本文的中心意思非常简单,没有耐心的读者建议直接拉到最后看结论部分,有兴趣的读者可以详细阅读一下. 原文发表于Linkedin Engineering,作者 Zhenyun Zhuang是L ...
- JVM GC杂谈之理论入门
GC杂谈之理论入门 JVM堆布局介绍 JVM堆被划分成两个不同的区域:新生代 ( Young ).老年代 ( Old ).新生代 ( Young ) 又被划分为三个区域:Eden.From Sur ...
- JVM GC笔记
堆分区:所有new的对象都会存放在堆中 > 新生代(Young Generation):存放生命周期短的对象,具体还分为Eden和Survivor两个区,其中Survivor分为Fro ...
- JVM GC算法
在判断哪些内存需要回收和什么时候回收用到GC 算法,本文主要对GC 算法进行讲解. JVM垃圾判定算法 常见的JVM垃圾判定算法包括:引用技术算法.可达性分析算法. 引用技术算法(Reference ...
- 【转载】Java性能优化之JVM GC(垃圾回收机制)
文章来源:https://zhuanlan.zhihu.com/p/25539690 Java的性能优化,整理出一篇文章,供以后温故知新. JVM GC(垃圾回收机制) 在学习Java GC 之前,我 ...
- JVM GC机制
垃圾收集主要是针对堆和方法区进行. 回收机制: 现在的JVM基本都使用分代回收机制,把堆中内存区域分为新生代,老年代. 新生代: Eden(80%) Survivor0(10%) Survivor1( ...
- 深入浅出 JVM GC(3)
# 前言 在 深入浅出 JVM GC(2) 中,我们介绍了一些 GC 算法,GC 名词,同时也留下了一个问题,就是每个 GC 收集器的具体作用.有哪些 GC 收集器呢? Serial 串行收集器(只适 ...
- 深入浅出 JVM GC(2)
# 前言 在 深入浅出 JVM GC(1) 中,限于上篇文章的篇幅,我们留下了一个问题 : 如何回收? 这篇文章将重点讲述这个问题. 在上篇文章中,我们也列出了一些大纲,今天我们就按照那个大纲来逐个讲 ...
- 深入浅出 JVM GC(1)
# 前言 初级 Java 程序员步入中级程序员的有一个无法绕过的阶段------GC(Garbage Collection).作为 Java 程序员,说实话,很幸福,不用像 C 程序员那样,时刻关心着 ...
随机推荐
- 移动站Web开发图片自适应两种常见情况解决方案
本文主要说的是Web中图片根据手机屏幕大小自适应居中显示,图片自适应两种常见情况解决方案.开始吧 在做配合手机客户端的Web wap页面时,发现文章对图片显示的需求有两种特别重要的情况,一是对于图集, ...
- 【原创】大叔经验分享(69)docker启动java应用的时区问题
在docker中启动tomcat或java类应用,获取时间默认是UTC时间,这是因为容器内的locale没有设置为东8区,最简单的方式是增加JAVA_OPTS 如果是java,直接在java命令后增加 ...
- mybatis 插入语句 返回自增长id方法
背景:目前有个插入语句需要获取插入记录的id 因为id是自增长的,所以要在插入后返回这个id 错误1: mapper.xml: <!-- 新增 返回自增长id--> <insert ...
- vue进阶:vue-router之导航守卫、路由元信息、路由懒加载
1.导航被触发 2.在失活的组件里调用离开守卫:beforeRouteLeave —— 组件内守卫(离开组件). 3.调用全局的beforeEach守卫 —— 全局守卫(进入组件). 4.在重用组件里 ...
- Python实现串口通信(pyserial)
pyserial模块封装了对串口的访问,兼容各种平台. 安装 pip insatll pyserial 初始化 简单初始化示例 import serial ser = serial.Serial('c ...
- SQL Server 2005 实现数据库同步备份 过程--结果---分析
数据库复制: 简单来说,数据库复制就是由两台服务器,主服务器和备份服务器,主服务器修改后,备份服务器自动修改. 复制的模式有两种:推送模式和请求模式,推送模式是主服务器修改后,自动发给备份服务器, ...
- 深入学习Mybatis框架(二)- 进阶
1.动态SQL 1.1 什么是动态SQL? 动态SQL就是通过传入的参数不一样,可以组成不同结构的SQL语句. 这种可以根据参数的条件而改变SQL结构的SQL语句,我们称为动态SQL语句.使用动态SQ ...
- 获取iframe子页面内容高度给iframe动态设置高度
<!DOCTYPE html><html> <head> <meta charset="UTF-8" /> <meta nam ...
- altium designer 鼠线
第一: 按“L”进入View Configurations 要确保Default Color for New Nets是勾上的. 第二: 如果“PCB”的下拉列表处于“From-To Editor”状 ...
- hutools之批量更新
public class HutoolTest { private static DataSource dataSource = DSFactory.get(); //读取默认路径下的配置文件,数据库 ...