[转载] JVM中对象的回收过程

参考链接: JVM是否创建Main类(具有main()的类)的对象

当我们的程序开启运行之后就,就会在我们的java堆中不断的产生新的对象,而这是需要占用我们的存储空间的,因为创建一个新的对象需要分配对应的内存空间,显然我的内存空间是固定有限的,所以我们需要对没有用的对象进行回收,本文就来记录下JVM中对象的销毁过程。 

 

 

 文章目录

 1.怎么判断对象是没用的了引用计数算法可达性分析算法

  2.对象的引用分类强引用软引用弱引用虚引用

  3.finalize方法4.方法区的回收

 

 

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虚拟机》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/540427.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c语言格式对齐填充_C ++中类的大小 课堂上的填充和对齐| 派生类的大小

c语言格式对齐填充Prerequisite: 先决条件&#xff1a; sizeof() operator in C/C C / C 中的sizeof()运算符 Size of struct in C C中的struct大小 We know that a struct size is not only the summation of all the data members, rather its the minimum sum guaranteed. …

ELK系列~对fluentd参数的理解

这段时候一直在研究ELK框架&#xff0c;主要集成在对fluentd和nxlog的研究上&#xff0c;国内文章不多&#xff0c;主要看了一下官方的API&#xff0c;配合自己的理解&#xff0c;总结了一下&#xff0c;希望可以帮到刚入行的朋友们&#xff01; Fluentd&#xff08;日志收集与…

[转载] Java中的50个关键字

参考链接&#xff1a; Java平台如何独立 Java中的50个关键字 关键字也称为保留字&#xff0c;是指java语言中规定了特定含义的标示符。对于保留字&#xff0c;用户只能按照系统规定的方式使用&#xff0c;不能自行定义。Java中有50个常用关键字&#xff1a; 与数据类型相关…

MySQL 直接存储图片并在 html 页面中展示,点击下载

数据库实体类&#xff1a; package com.easy.kotlin.picturecrawler.entityimport java.util.* import javax.persistence.*Entity Table(indexes arrayOf(Index(name "idx_url", unique true, columnList "url"),Index(name "idx_category"…

css 文本背景色透明_如何使用CSS将文本或图像的背景设置为透明?

css 文本背景色透明Introduction: 介绍&#xff1a; In web development, there are numerous ways by which we can style our websites or web pages. You can make use of lots of properties for creating attractive and responsive websites. 在Web开发中&#xff0c;我…

[转载] 1.1Java使用JDBC原生方式连接MySql数据库

参考链接&#xff1a; Java数据库连接JDBC驱动程序 前言&#xff1a;今天有朋友问我原生的java连接数据库&#xff0c;因为框架的使用&#xff0c;如果基础不牢固的人&#xff0c;是很容易遗忘原生的连接方式。今天正好趁此做一下回顾&#xff1a; 这里只考虑原生方式&#x…

maven安装及集成myeclipse

第一步&#xff1a;下载和安装 1、官网下载Maven&#xff1a;http://maven.apache.org/download.cgi 2、解压到一个文件夹2、设置环境变量&#xff1a;如&#xff1a;M2_HOME&#xff1a;D:\JAVA\apache-maven-3.0.5在path中添加;%M2_HOME%\bin;第二步&#xff1a;和MyEclipse集…

[转载] Java泛型详解:<T>和Class<T>的使用。泛型类,泛型方法的详细使用实例

参考链接&#xff1a; Java中的main()函数是强制性的吗 一、引入 1、泛型是什么 首先告诉大家ArrayList就是泛型。那ArrayList能完成哪些想不到的功能呢&#xff1f;先看看下面这段代码&#xff1a; [java] view plain copy ArrayList<String> strList new ArrayL…

数字和数字根的总和_使用8086微处理器查找8位数字的数字总和

数字和数字根的总和Problem statement: 问题陈述&#xff1a; Write an assembly language program in 8086 microprocessor to find sum of digit of an 8 bits number using 8 bits operation. 在8086微处理器中编写汇编语言程序&#xff0c;以使用8位运算找到8位数字的位数…

[转载] Java笔试题集锦

参考链接&#xff1a; 关于Java中文件名和类名的误解 Java笔试题集锦 1.MVC的各个部分都有那些技术来实现?如何实现? 答&#xff1a;MVC是Model&#xff0d;View&#xff0d;Controller的简写。"Model" 代表的是应用的业务逻辑&#xff08;通过JavaBean&#xff…

gcc -pthread_错误-在GCC Linux中使用C程序未定义对'pthread_create'的引用

gcc -pthread在Linux中修复对pthread_create的未定义引用 (Fixing undefined reference to pthread_create in Linux) This is a common error while compiling C program in GCC/G Linux. This error occurs when you are using pthread_create function to create threads in…

[转载] Java面试题全集(上)

参考链接&#xff1a; 如何运行不同目录中的Java类文件 2013年年底的时候&#xff0c;我看到了网上流传的一个叫做《Java面试题大全》的东西&#xff0c;认真的阅读了以后发现里面的很多题目是重复且没有价值的题目&#xff0c;还有不少的参考答案也是错误的&#xff0c;于是我…

python重载运算符乘法_Python | 使用乘法运算符创建一个字符串的多个副本

python重载运算符乘法Given a string and we have to create its multiple copies by using multiplication operator in Python? 给定一个字符串&#xff0c;我们必须通过在Python中使用乘法运算符来创建其多个副本&#xff1f; If you want to create multiple copies of …

一次前端笔试总结

1.有一个长度未知的数组a&#xff0c;如果它的长度为0就把数字1添加到数组里面&#xff0c;否则按照先进先出的队列规则让第一个元素出队。 分析&#xff1a;这道题主要是考核了数组的队列方法和栈方法。另外&#xff0c;原题还有字数限制的&#xff0c;只有在字数小于30并且结…

Java文件类boolean setLastModified(long set_new_time)方法,包含示例

文件类boolean setLastModified(long set_new_time) (File Class boolean setLastModified(long set_new_time)) This method is available in package java.io.File.setLastModified(long set_new_time). 软件包java.io.File.setLastModified(long set_new_time)中提供了此方法…

[转载] Linux里面的文件目录类指令

参考链接&#xff1a; 如何运行不同目录中的Java类文件 引用&#xff1a;尚硅谷韩老师的《尚硅谷-Linux-经典升级》 日常总结 pwd 指令 &#xff08;显示当前工作目录的绝对路径&#xff09; 基本语法 pwd (功能描述&#xff1a;显示当前工作目录的绝对路径) …

[转载] 微服务安全和治理

参考链接&#xff1a; 微服务介绍 在整体式架构中&#xff0c;由于运行应用程序的运行时环境相对隔离&#xff0c;所以治理和安全保护很简单。微服务架构具有典型的革新特征&#xff0c;给活动的治理和应用程序的安全威胁保护带来了更多挑战。 微服务架构中的安全性 微服务…

SSL

今天遇到一位网友要求老蒋将他当前已经在使用的WDCP面板环境&#xff0c;给某个站点添加SSL证书&#xff0c;实现HTTPS网址访问。在过去的几篇文章中&#xff0c;老蒋也有分享过不少在Linux VPS中对应的WEB环境安装SSL证书的经历&#xff0c;其实总体来看都大同小异&#xff0c…

[转载] Java中如何引用另一个类里的集合_Java工程师面试题整理

参考链接&#xff1a; 在Java中将预定义的类名用作类或变量名 花了一星期把学过的都整理一遍 尽量易懂&#xff0c;从基础到框架 最新版大厂面经汇总出炉&#xff0c;持续更新中 汇总完了上传网盘&#xff0c;设计到后端架构师的一切知识 如果没更新就代表我死了 一&#xff0…

应用宝认领应用

2019独角兽企业重金招聘Python工程师标准>>> 【Android】命令行jarsigner签字和解决找不到证书链错误 1、签名失败 $jarsigner -verbose -keystore /Volumes/Study/resourcesLib/Qunero-achivements/AndroidApp/QuLordy-signed-key -signedjar ./signed_XiaomiVerif…