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 程序员那样,时刻关心着 ...
随机推荐
- Java学习 面向对象(下)——Java疯狂讲义th4
面向对象(下) [TOC] 包装类 通过包装类可以把8个基本类型的值包装成对象使用. 自动拆箱.自动装箱 把字符串类型值转换成基本类型的值: 包装类的 parseXxx(String s)静态方法 包 ...
- Laravel 实现多级控制器(实现Api区分版本)
路由: Route::get('', 'v1\\UserController@index'); 文件夹分层 User控制器命名空间: namespace App\Http\Controllers\v1 ...
- Java 串口通信 Ubuntu
说一下我的操作过程吧 在Windows上先用阿猫串口网络调试助手,进行调试: 在网上找Java代码,我选择的是RXTXcomm,网上代码很多,基本都一样. 在Windows电脑上把rxtx压缩包中的r ...
- 使用webpack + momentjs时, 需要注意的问题
注意开发HTML页面charset, 如是不是utf-8, 比如是shift_jis, 一般会在webpack里用插件EncodingPlugin把开发的utf-8格式转码成shift_jis格式 ...
- Redis总结2
一.Redis效率高的原因 众所周知,Redis常用来做缓存,从而提高项目QPS(每秒查询率).QPS = 并发量 / 平均响应时间 然而其效率高的原因包含但不仅限于如下几点: 1.Redis基于内存 ...
- payload免杀之msbuild利用
0x00 前言 红队必备技巧免杀之一,现在主要是.net4.0下实现.待我过几天有空实现一下.net2.0. 0x01 免杀过程 利用cs生成c#的payload,如图所示: 将paypload内容填 ...
- firefox(火狐中的兼容问题总结)
1.firefox 下 默认情况 <input type="number"> 只允许整数其他的都会报错,红色提示: 这时候可以添加参数 step="0.0 ...
- C++typedef struct和struct的区别
#include "pch.h" #include struct struct1 { int a; char b; char* c; }test1;//定义结构体变量 typede ...
- VS2012中--查找定义后从未被使用的函数
操作步骤如下: 选择项目==>右键属性==>代码分析(选择Microsoft的所有规则) 注:默认为 托管建议规则 注:CA1804 CA1811规则 例如需要查看某个项目从未被使用的函数 ...
- 四、Attribute(2)授权角色过滤器
一.授权过滤器 1.新建一个mvc 项目 2.首先创建一个过滤器 MyAuthorizeAttribute 继承AuthorizeAttribute,并重写 AuthorizeCore public ...