概述

Java运行时区域中,程序计数器,虚拟机栈,本地方法栈三个区域随着线程的而生,随线程而死,这几个区域的内存分配和回收都具备确定性,不需要过多考虑回收问题。而Java堆和方法区则不一样,一个接口的多个实现类需要的内存不一样,一个方法的多个分支需要的内存可能也不一眼,我们只有在运行期,才能知道会创建的对象,这部分的内存分配和回收,是垃圾回收器所关注的。垃圾回收器需要完成三个问题:那些内存需要回收;什么时候回收以及如何回收。

那些垃圾需要回收

垃圾回收的基本思想是考察一个对象的可达性,即从根节点开始是否可以访问到这个对象,如果可以,则说明对象正在被使用,相反如果从根节点无法访问到这个对象,说明对象已经不再使用了,一般来说此对象就是需要被回收的。这个算法为根搜索算法。

可达性分析

但是实际中,一个不可达的对象有可能在某种条件下“复活”自己,那么对它的回收就是不合理的。为此给出一个对象可达性状态的定义,并规定了在什么状态下可以安全的回收对象。可达性对象包含了以下三种状态。

  • 可达的:从根节点开始,按照引用节点,可以搜索到这个对象
  • 可复活的:对象的所有引用都被释放,但是对象可能在finalize()方法中复活自己。
  • 不可达的:对象的finalize()方法被调用,并且没有复活,那么就进入不可达状态。不可达的对象不可能会被“复活”,因为finalize()方法只能调用一次。
/**
*
* <p>Description: 1.对象被GC时,可以通过finalize拯救 2.finalize只被调用一次 </p>
* @date 2019年8月25日
* @version 1.0
*/
public class FinalizeTest { private static FinalizeTest currentObj; @Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize invoke");
//重新引用
currentObj = this;
} public void alive() {
System.out.println("live");
} public static void main(String[] args) throws InterruptedException {
currentObj = new FinalizeTest(); currentObj = null;
System.gc();
//finalize优先级地,先等待
Thread.sleep(500);
if(currentObj == null) {
System.out.println("dead");
}else {
currentObj.alive();
} currentObj = null;
System.gc();
//finalize优先级地,先等待
Thread.sleep(500);
if(currentObj == null) {
System.out.println("dead");
}else {
currentObj.alive();
}
}
}

上面代码有一处一样的断码片段,但是得到的结果却并不相同,一次对象“拯救复活”成功,另一次失败,那么就可以被正常回收。

可以作为GC Roots包括下面几种:

  • 虚拟机栈(栈帧中的本地表量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用(即一般Native的方法)的对象

四种引用类型

在JDK1.2之后对引用进行了扩充,分为强引用,软引用,弱引用,虚引用4种,这四种强度一次减弱。通过对引用的扩充,可以依据内存的使用来描述这样的对象:当内存足够,则保留内存中;如果内存空间进行垃圾回收后还是很紧张,则可以抛弃这类对象。很多系统的缓存功能符合这样的应用场景。

  • 强引用

    在Java中最常见的就是强引用, 把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。

  • 软引用

    ​ 软引用需要用SoftReference类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。

  • 弱引用

    ​ 弱引用需要用WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。

  • 虚引用

    ​ 虚引用需要PhantomReference类来实现,它不能单独使用,必须和引用队列联合使用。 虚引用的主要作用是跟踪对象被垃圾回收的状态。

什时候回收

按HotSpot VM的serial GC的实现来看触发条件主要分为以下几种:

  • young GC:当young gen中的eden区分配满的时候触发。注意young GC中有部分存活对象会晋升到old gen,所以young GC后old gen的占用量通常会有所升高。
  • full GC:当准备要触发一次young GC时,如果发现统计数据说之前young GC的平均晋升大小比目前old gen剩余的空间大,则不会触发young GC而是转为触发full GC(因为HotSpot VM的GC里,除了CMS的concurrent collection之外,其它能收集old gen的GC都会同时收集整个GC堆,包括young gen,所以不需要事先触发一次单独的young GC);或者,如果有perm gen的话,要在perm gen分配空间但已经没有足够空间时,也要触发一次full GC;或者System.gc()、heap dump带GC,默认也是触发full GC。

HotSpot VM里其它非并发GC的触发条件复杂一些,不过大致的原理与上面说的其实一样。并发GC的触发条件就不太一样。以CMS GC为例,它主要是定时去检查old gen的使用量,当使用量超过了触发比例就会启动一次CMS GC,对old gen做并发收集。

如何回收

如何回收主要就涉及到垃圾回收的算法了。下面介绍几种垃圾回收算法的思想。

标记清除法(Mark-Sweep)

标记清除算法是现代垃圾回收算法的思想基础。它主要分为两个阶段:标记阶段和清除阶段。在标记阶段,首先通过根节点,标记所有从根节点开始的可达队对象,因此未被标记的对象就是未被引用的垃圾对象。然后在清除阶段,清除所有的未被标记的对象。

标记清除算法的不足有:效率的问题和标记清除后产生的大量不连续的内存碎片。而内存碎片太多可能会导致在分配大对象时,无法找到连续的内存而不得不提前触发另外一次垃圾回收。

复制算法(Coping)

复制算法的核心思想是:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中存活对象复制到未使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。

如果系统中的待回收的对象很多,复制算法需要复制的存活对象就会相对较少,真正的垃圾回收时刻,复制算法的效率就会很高。而且对象是在垃圾回收过程中的,统一复制到新的内存空间,再清除原来使用的内存,因此可以确保回收后的内存空间是没有碎片的。但是另一方面,复制算法的代价是需要使用更多的内存空间。

复制算法比较适用于新生代。因为新生代垃圾对象通常多余存活对象,复制算法的效率会比较高。

标记整理算法(Mark Compact)

在老年代,大部分的对象都是存活对象。如果依然用复制算法,由于存活的对象多,复制的成本也将提高。因此基于老年代的垃圾回收特性,需要使用其他的算法。标记整理算法是一种老年代的回收算法。它在标记算法的基础上做了一些优化。和标记清除算法一样,它也是从更节点开始,但是并不是清除未标记的对象,而是将存活的对象压缩到内存的一边,之后清除边界外所有空间。这种方法避免了碎片的产生,又不需要过多的内存空间,因此性价比比较高。

标记整理法的最终效果等同于标记清除算法执行完成后,再进行一次内存碎片的整理,因此也可以把它称为标记清除整理(MarkSweepComact)。

分代算法(Generational Collecting)

分代算法是根据对象存活周期不同将内存化为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最合适的收集算法。新生代中的特点是对象朝生夕死,大约90%的新建对象会被回收,因此新生代适合复制算法。当一个对象经过几次回收后依然存活,对象就会被放入老年代的内存空间。在老年代中可以认为对象在一段时间内,甚至在程序的整个生命周期,是常驻内存的,可以对老年代使用标记清除和标记整理算法。

对于新生代和老年代来说,通常新生代的回收频率很高,但是每次回收的耗时都很短,而老年代回收的频率比较低,但是会消耗更多的时间。

分区算法(Region)

一般来说,相同条件下,堆空间越大,一次GC所需要的事件越长,从而产生的停顿也越长。为了更好的靠之停顿时间,将一块大的内存区域分割成多个大小形同的小区域,依据目标的停顿时间,每次回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿。分区算法是将整个堆空间划分为连续的不同小区间。每个小区间独立使用,独立回收。

谈谈JVM垃圾回收的更多相关文章

  1. 谈谈JVM垃圾回收机制及垃圾回收算法

    一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理.由于有个垃圾回收机制 ...

  2. JVM垃圾回收机制和常用算法

    由于疫情的原因,所以目前一直在家远程办公,所以很多时间在刷面试题,发现2019大厂的面试虽然种类很多,但是总结了一下发现主要是这几点:算法和数据结构. JVM.集合.多线程.数据库这几点在面试的时候比 ...

  3. JVM垃圾回收机制总结:调优方法

    转载: JVM垃圾回收机制总结:调优方法 JVM 优化经验总结 JVM 垃圾回收器工作原理及使用实例介绍

  4. JVM内存管理和JVM垃圾回收机制

    JVM内存管理和JVM垃圾回收机制(1) 这里向大家描述一下JVM学习笔记之JVM内存管理和JVM垃圾回收的概念,JVM内存结构由堆.栈.本地方法栈.方法区等部分组成,另外JVM分别对新生代和旧生代采 ...

  5. JDK分析工具&JVM垃圾回收(转)

    转自:http://blog.163.com/itjin45@126/blog/static/10510751320144201519454/ 官方手册:http://docs.oracle.com/ ...

  6. 老李分享:jvm垃圾回收

    老李分享:jvm垃圾回收   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq:908821478 ...

  7. jvm - 垃圾回收

    jvm - 垃圾回收 注意 : 本系列文章为学习系列,部分内容会取自相关书籍或者网络资源,在文章中间和末尾处会有标注 垃圾回收的意义 它使得java程序员不再时时刻刻的关注内存管理方面的工作. 垃圾回 ...

  8. JVM垃圾回收机制概述

    JVM垃圾回收机制概述 1.定义 是指JVM用于释放那些不再使用的对象所占用的内存. 2.方式 2.1引用计数(早期) 当引用程序创建引用以及引用超出范围时,JVM必须适当增减引用数.当某个对象的引用 ...

  9. Java虚拟机学习笔记——JVM垃圾回收机制

    Java虚拟机学习笔记——JVM垃圾回收机制 Java垃圾回收基于虚拟机的自动内存管理机制,我们不需要为每一个对象进行释放内存,不容易发生内存泄漏和内存溢出问题. 但是自动内存管理机制不是万能药,我们 ...

随机推荐

  1. RSA premaster secret error 错误解决

    报错信息如下: Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: 驱动程序无法使用安全套接字层(SSL)加密与 SQL Serve ...

  2. linux中type 、file、stat三者的区别

    1.type 用来查看命令类型,以区别是内部命令还是外部命令 示例:[root@localhost ~]# type cd cd 是 shell 内嵌    [root@localhost ~]# t ...

  3. python递归-三元表达式-列表生成式-字典生成式-匿名函数-部分内置函数-04

    递归 递归: # 函数在调用阶段直接或间接地又调用了自身 应用场景: # 将列表中的数字依次打印出来(循环的层数是你必须要考虑的点)   -->  l = [1, [2, [3, [4, [5, ...

  4. 搭建python环境

    参考文章:https://blog.csdn.net/qq_33855133/article/details/73106176 对于配置环境变量,懂些技术的人来说,都是很简单. 变量是在操作系统中一个 ...

  5. Python基础总结之第四天开始【格式化‘字符串’】(新手可相互督促)

    年薪20万... 字符串格式化: 先看小案例-------: a = 'hello,my name is %s,I like %s.'%('XiaoHong','football') #在字符串中,[ ...

  6. java - try catch finally 用法

    try { //执行的代码,其中可能有异常.一旦发现异常,则立即跳到catch执行.否则不会执行catch里面的内容 } catch { //除非try里面执行代码发生了异常,否则这里的代码不会执行 ...

  7. golang在多个go routine中进行map或者slice操作应该注意的对象。

    因为golang的map和列表切片都是引用类型,且非线程安全的,所以在多个go routine中进行读写操作的时候,会产生“map read and map write“的panic错误. 某一些类型 ...

  8. Python秒算24点,行还是不行?

    周末闲来无事,看到隔壁家的老王在和隔壁家的媳妇玩24点,就进屋看了看.发现老王是真不行啊,那不行,这也不行. 就连个24点都玩不过他媳妇,给他媳妇气的,啥都不能满足,这不能,那也不能. 我坐下来和他媳 ...

  9. C#航空查询及预订

    关于航空查询及预订项目中出现的问题 namespace Flight{ public partial class Flight : Form { public Flight() { Initializ ...

  10. some (1)

    每次在写博客的时候,都是自己觉得在工作中非常重要的东西,写东西的时候,也是一个思考的过程.好的东西不光帮助别人,也使自己有进一步的理解.