Java垃圾回收机制整理

花名提莫 2019-07-01 15:21:13 ⋅ 800 阅读

https://www.cnblogs.com/lb-alex/p/11112483.html

一、垃圾回收器和finalize() 

 java垃圾回收器只负责回收无用对象占据的内存资源。但是如果你的对象不是通过 new 创建的(所有的new 对象都往堆中开辟资源,在一个地方,方便清理/管理资源),它会不知道该如果释放该对象的这块特殊内存。为了应对这个情况,Object自带一个finalize()方法。

  finalize()这方法的原理是:一旦垃圾回收器准备释放该对象占用的存储空间,将会先调用其继承/重写的fialize(),并且调用方法后不是立即执行回收,而是在下一次(JVM觉得需要更大内存的时候)回收动作发生时,才会真正回收对象占用的内存。所以一般自己重写fialize()方法,是在回收的最后时刻做一些重要的清理工作。

java垃圾回收几个特点:

1、对象可能不被垃圾回收

   你创建的对象做了某个功能,比如显示在电脑的屏幕上。那么除非你特别处理从屏幕上擦除,它永远不可能得到清理。所以如果在finalize()方法中做擦除屏幕的处理,当垃圾回收时,finalize()被调用,屏幕图像清除。请注意:垃圾回收器只有在JVM觉得需要更大内存的时候才会运行(虽然开销小,但是一直运行还是有开销的),所以大部分回收动作是发生在濒临存储空间用完的那一刻,逼得JVM去运行垃圾回收器。如果程序执行结束 (或者中断运行),那些资源也会全部还给操作系统。

2、垃圾回收并不等于析构

  这个是C的概念,因为java和C的牵扯太深,所以经常拿来对比。简单说C有一个东西叫析构函数,在销毁对象前必须执行这个析构函数。这里的垃圾回收并不代表析构。finalize()就是类似功能但是不等于。

3、垃圾回收只与内存有关

  这里就要讲到finalize()的真正用途。该方法内部执行的操作也应该和内存及其回收有关,所以fialize()方法不是通用的方法。你可能会想到,当对象包含成员对象属性的时候,finalize()是否应该明确要清除那些对象呢?不正确。应该这样理解:无论对象如果创建,垃圾回收器都会负责释放对象占用的所有内存。所以finalize()一般是来处理通过创建对象以外的方式为对象分配存储空间。说起来有些绕口,但是举个例子就知道了。

  java跟踪源码的时候经常遇到关键字native修饰的方法,这些方法也叫“本地方法“。在使用这些本地方法的时候,内部调用的是非java代码的方式(不是C就是C++)。.这些非代码中,也许会用到C的malloc()函数系列来分配存储空间。这样除非调用C的free()方法,否则存储空间将不会释放。所以可以在finalize()使用native方式调用free。

  以上,就是建议尽量少重写finalize()的道理。

二、垃圾回收条件

  既然fialize()使用场景这么生僻,那就不要指望频繁使用fialize()。你必须创建其他的清理方法,来自己根据业务清理。但是fialize()有个特点是:程序调用它,是该对象“终结条件“的验证。也就是被标记了,该对象已死,可以回收了。例如:某个对象代表打开的一个文件,在对象被回收前程序员应该关闭文件。只要对象存在没有被适当清理的部分,程序就存在隐晦的缺陷。fialize()可以用来最终发现这种情况。

// Using finalize() to detect an object that// hasn't been properly cleaned up.class Book { boolean checkedOut = false; Book(boolean checkOut) { checkedOut = checkOut; } void checkIn() { checkedOut = false; } protected void finalize() { if(checkedOut) System.out.println("Error: checked out"); // Normally, you'll also do this:// super.finalize(); // Call the base-class version }}
public class TerminationCondition { public static void main(String[] args) { Book novel = new Book(true); // Proper cleanup: novel.checkIn(); // Drop the reference, forget to clean up: new Book(true); // Force garbage collection & finalization: System.gc(); }}/* Output:Error: checked out*///:~


这个例子的终结条件是:所有Book对象在被当做垃圾回收前都应该checkIn。但是在main里面,第二本书没有checkIn,这个时候通过finalize(),就能明确知道,有的对象没有在销毁前处理干净了。另外,代码中还使用了System.gc();这是强制唤起垃圾回收机器,来触发BOOK的finalize();当然,如果不这样强制唤起也行,当程序运行到被分配了大量内存的时候(可以大量反复创建BOOK),逼得垃圾回收器会自动触发。如果BOOK有继承某个父类,要触发该父类的finalize(),可以使用super.finalize();调用。

三、垃圾回收器如何工作

  一般印象里面,在堆内分配新资源会比较慢,毕竟比不了堆栈快。但是其实JVM在这方面是做了大量的优化,其中垃圾回收器对于提高对象的创建速度,具有明显效果。即使用垃圾回收器释放存储空间有利于未使用存储空间的分配。通俗点就是说,垃圾回收器回收的内存越多,创建对象理论上会更快(还是有临界的,一般认为媲美其他语言在堆栈中创建对象,比如C)。C的堆就好像一个院子,里面的每个对象各管各的存储空间。一段时间以后某个对象被销毁了。它的空间必须被重新使用。在某些JVM中,堆就像一个传送带,分配一个新的对象,它就往前移动一格。这个意味着空间分配会非常快(寻址快)。java的寻址指针只需要简单移动到尚未分配的区域就行,这样效率比得上C在堆栈上分配的速度。其中,记录对象空间地址“下标“方面,还是有部分开销的,但是比C需要查找堆的开销,小得多。其实,java中的堆未必完全是像传送带,因为这会造成频繁的内存页面调度(内存是分页的,翻页时是要移出硬盘,放在虚拟内存上)。页面调度会显著影响性能,最终,在创建足够的对象后,内存(大量虚拟内存充斥)资源耗尽。

  这里就轮到垃圾回收器登场了,当垃圾回收器工作的时候,一边回收空间,一边使堆中的对象排列紧凑。这样“堆指针”就可以很容易移动到更靠近传送带的开始处(java堆分配空间是先进),也就尽量避免了页面错误。垃圾回收期会对对象重新排列,实现高速的、有无限空间(?)可以分配的堆模型。

下面是垃圾回收(不止java)常用的三种设计方式:

1、引用计数

  每个对象含有一个应用计数。当有引用连接到对象的时候+1,引用离开作用域或者赋值null的时候-1。好了,那么当发现某个对象的引用是0的时候,就释放它占用的空间(这里会出现一变为0就释放空间)。这里就存在缺陷,如果对象循环引用,即A引用B,B引用A,就出现“对象可以被回收,但是引用计数不是0”的情况。对于垃圾回收器来说,定位这种互相引用的对象组开销极大。另外,管理引用计数的开销不大,但是这个会在整个程序生命周期内持续发生。引用计数的方式一般用来表述垃圾收集,但没有应用于任何一种JVM中。

2、stop-and-copy

  这种方式是先暂停程序(不是后台运行,而是停止程序,执行垃圾回收),然后将所以存活的对象从当前的堆中复制到另外一个堆,剩下的都是垃圾。当对象被复制到新的家(堆)时,会把这些对象一个挨着一个,所以新堆保持紧凑排列。这个就是前面说的JVM虚拟机的垃圾回收期为什么能做到使对象紧凑排列了。复制过程会产生新的开销,以及所有指向就旧对象的引用都要指向新的地址。这里可能出现来自非堆的引用(不是new出来的对象),这些会在遍历旧堆的引用的时候被找出来,重新指向新堆。

  这种回收方式,效率低。首先,是因为要有两个堆,然后对象要在两个堆中复制转移,所以实际上维护的空间比理论上大一倍。例如,某些JVM的做法是,在堆里面分配几个大的内存块,复制的操作在这里个内存块中进行。其次,当程序运行趋于稳定以后,产生的垃圾比较少,甚至可能没有垃圾。这个时候来回复制就很浪费了。为了避免浪费,JVM会进行检查,要是没有新垃圾产生,就自动转换成下一种模式。

3、mark-and-sweep

  Sun公司早期版本的虚拟机使用的就是这个。这种方式的思路是从堆和静态存储区出发,遍历所有的引用,然后就能找到所有存活对象。每当找到一个存活对象,就给该对象一个标记,记上一笔。所有标记都做完以后,清理开始。在清理的时候,没有标记的对象将被释放,不会发生复制动作。这个时候堆中就有点像C的样子,所以如果要让剩下的对象内存连续,就需要重新整理剩下的对象。

  

小结:

  Sun的文献把垃圾回收看做是低优先级的后台进程,指的是因为stop-and-copy:毕竟要暂停程序。但是在早期版本中,JVM使用的是mark-and-sweep。现在这两种回收方式通过JVM进行监视,如果有所对象都很稳定,垃圾回收器效率降低的话,就切换到mark-and-sweep。同样的,如果mark-and-sweep的效果不好,堆中出现了很多垃圾碎片(无引用对象),就会切换到stop-and-copy。在stop-and-copy使用的时候,因为内存分配以较大的“块”为单位,如果对象较大,它会占用整个块。在stop-and-copy运行的到停止程序运行的操作前,会把所有存活的对象复制到新的块(堆)中,这个时候旧的块就会被废弃,垃圾回收器就可以向废弃的块复制新对象进去,灵活利用资源。每个块都有相应的代数(count)来记录它是否还存活。如果块在某处被引用,count会增加。垃圾回收器将对上次回收动作之后新分配的块进行整理。这个対短命的对象很有帮助。垃圾回收期会定期进行完整的清理动作----大型对象仍然不会被复制,内含小型对象的那些块还是会被复制并且整理。

  JVM有许多的附加技术用以提升速度。比如JIT(Just-In-Time)编译器的技术。这个会把程序全部或部分翻译成本地机器码,程序运行速度因此快上很多。当需要装载某个类的时候(创建该类的第一个对象,后续创建不会再次装载),编译器先找到对应的class文件,然后把字节码内容装入内存中。这个时候,有两种方式可以选。其一是让JIT编译所有代码转换成机器码。但是因为装载的发生不可控制,是零散在整个程序的生命周期内的,累加起来就需要花费很多时间,并且会增加可执行代码的长度(字节码要比JIT编译后展开的机器码小很多),这个可能导致内存页面调度,从而降低程序的速度。其二是惰性评估(lazy evaluatio),意思是JIT只有在必要的时候才编译代码,这样从不会被执行的代码(import 进来,但是没有使用)就压根不会被JIT编译。JDK中的Java HotSpot技术就是采用了类似方法,代码每次执行都会做一些优化,所以执行次数越多,它的速度就越快。



全部评论: 0

    我有话说:

    阿里大牛谈垃圾回收算法是如何设计的?

    前言 如果大家关注 JDK,会发现在频繁发布的 JDK 版本中,和垃圾回收相关的 JEP (JDK Enhancement Proposals,Java 增强提案)越来越多了,垃圾回收

    蚂蚁金服 Java RPC 开源框架—SOFARPC

    SOFARPC 是一个高可扩展性、高性能、生产级的 Java RPC 框架。

    微信开发神器全能微信Java开发工具包

    必须分享的微信神器 weixin-java-tools

    精品推荐:一览GitHub上最受程序欢迎的5大Java开源项目

    列举了GitHub上一些最流行的Java项目。从Mockitos到Guava,以及 java-design-patterns等供大家学习。

    2017 年度编程语言榜,Java 最流行、JavaScript 最没价值?

    2017 年度编程语言榜,Java 最流行、JavaScript 最没价值?

    GitHub精选:2018年11月份最热门的Java开源项目

    又到了揭晓 11 月份最热门 Java 开源项目排名的时候了,在本月的名单中,出现了几个新面孔,如Java 核心知识库、轻量级容错组件Resilience4j .....

    Google 宣布正式开源 Jib ,帮助 Java 应用快速容器化

    Google 本周宣布开源一款新的 Java 工具 Jib ,旨在让开发者使用他们熟悉的工具更轻松地将 Java 应用程序容器化。

    精品推荐:Java核心数据结构(List,Map,Set)使用技巧与优化

    JDK提供了一组主要的数据结构实现,如List、Map、Set等常用数据结构。这些数据都继承自 java.util.Collection 接口,并位于 java.util 包内。

    Java Web实战篇:发布和运维必备的12条Linux命令

    作为一名Java起步的从业人员,学会一些常用的Linux命令是必须的。

    Java Web实战篇-轻松提高千万级数据库查询效率

    通过优化数据库设计、java后台和数据库优化达到提高千万级数据查询的效率。

    Google发布Java 核心工具库——Guava 28.0

    Guava是一种基于开源的Java库,其中包含谷歌正在由他们很多项目使用的很多核心库。这个库是为了方便编码,并减少编码错误。这个库提供用于集合,缓存,支持原语,并发性,常见注解,字符串处理,I/O和

    Java 零注解文档生成工具—smart-doc,看完有替换swagger的冲动

    Tips:喜欢的话可以关注小萌哦~~~今天小萌给大家推荐的一个开源Java Restful API 文档生成

    「开源资讯」Guava 28.2 发布,Google 的 Java 核心工具库

    前言 Guava 28.2 发布了,Guava 是 Google 的一个开源项目,包含许多 Google 核心 Java 常用库,如:集合 [collections] 、缓存 [caching

    RPC调用与GC垃圾回收

    多个服务协同完成一次业务时,由于业务约束(如红包不符合使用条件、账户余额不足等)、系统故障(如网络或系统超时或中断、数据库约束不满足等),都可能造成服务处理过程在任何一步无法继续,使数据处于不一致的状态。

    「轻阅读」图解 Java 线程生命周期

    Java 线程生命周期中都包含哪些状态?生命周期中各个状态都是什么含义?

    11 个Javascript机器学习库

    1. Brain.js Brain.js是一个Javascript库,用于替代(现在已弃用的)“ 脑 ”库的神经网络,该库可与Node.js一起使用或在浏览器中使用(注释计算),并为不同任务提供不同类型的网络。以下是训练网络以识别色彩对比的演示。...

    前端实战篇:JavaScript 反调试技巧的简单应用(上)

    最近作者看了一些关于JavaScript反调试的帖子,今天给大家整理一下希望有帮助。

    Java Web实战篇-代码之美

    代码之美-小小的优化让你的代码Bug更少,执行效率更高