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 程序员那样,时刻关心着 ...
随机推荐
- Open API
OAuth和SSO都可以做统一认证登录,但是OAuth的流程比SSO复杂.SSO只能做用户的认证登录,OAuth不仅能做用户的认证登录,开可以做open api开放更多的用户资源. Open API即 ...
- [C#]访问共享文件夹或者磁盘(需要用户名密码)
有项目要求使用对方本地管理员访问访问对方D盘,网上找到一段API,刚开始可以使用一段时间,升级到1903就失效了,一脸懵逼啊 using System; using System.Collection ...
- 电脑主板插线方法图解_JFP1主板插线图解
电脑主板插线方法图解_JFP1主板插线图 仔细看主板上有对应的英文标识的,一对一插就行分别是电源,复位,硬盘灯,电源灯的负极,正极
- WebFont技术使用之如何在app中使用自定义字体
参考 H5自定义字体解决方法(mark) 移动Web字体的使用 [原]移动web页面使用字体的思考 CSS @font-face规则 引用外部服务器字体
- 【强化学习】MOVE37-Introduction(导论)/马尔科夫链/马尔科夫决策过程
写在前面的话:从今日起,我会边跟着硅谷大牛Siraj的MOVE 37系列课程学习Reinforcement Learning(强化学习算法),边更新这个系列.课程包含视频和文字,课堂笔记会按视频为单位 ...
- 由于MTU设置不当导致的访问超时
现象 工作中遇到一件怪事:搭建好服务器后(VPN服务器,创建了虚拟网卡),服务器和客户端之间响应正常且很稳定,客户端也能正常通过服务器访问外网.但是访问个别网站时可以打开文字,但是部分图片打不开(也不 ...
- centos php7 amqp
yum install -y librabbitmq-devel /home/git/php/bin/pecl install amqp echo "extension=amqp.so&qu ...
- js去掉url后某参数【函数封装】
function delParam(paramKey) { var url = window.location.href; //页面url var urlParam = window.location ...
- python中的函数def和函数的参数
'''函数: 1.减少代码重用性 2.易维护 3.可扩展性强 4.类型function 定义函数: def 函数变量名(): 函数的调用: 1.函数名加括号 2.函数如果没被调用,不会去执行函数内部的 ...
- P3806 离线多次询问 树上距离为K的点对是否存在 点分治
询问树上距离为k的点对是否存在 直接n^2暴力处理点对 桶排记录 可以过 #include<cstdio> #include<cstring> #include<algo ...