概述

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. JAVA面试题 StringBuffer和StringBuilder的区别,从源码角度分析?

    面试官Q1:请问StringBuffer和StringBuilder有什么区别? 这是一个老生常谈的话题,笔者前几年每次面试都会被问到,作为基础面试题,被问到的概率百分之八九十.下面我们从面试需要答到 ...

  2. SGU495 Kids andPrices[期望DP]

    也许更好的阅读体验 \(\mathcal{Description}\) 有\(n\)个格子,每次等概率随机给一个格子染色,问涂\(m\)次后期望有多少格子被染色了 \(\mathcal{Solutio ...

  3. php 排序和置顶功能实现

    (1)排序操作思路 一般来说都是按照发布时间排序.时间戳大的靠前,所以用倒序desc,而不是asc (2)置顶操作思路: 点击置顶时,修改数据库addtime字段值为当前时间即可.因为排序是按照时间戳 ...

  4. DedeCMS自定义表单制作和调用办法

    [摘要]在很多建站需求中,需要一些额外的表单供前台用户提交,以便于收集.统计.分析及处理更多的数据,利用DedeCMS自带提供的自定义表单功能即可满足大多数的此类需求,本文就讲一下如何使用DedeCM ...

  5. python课堂整理12---递归

    一.递归特性 1.必须有一个明确的结束条件 2.每次进入更深一层递归时,问题规模相比上次递归都应有所减少 3.递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据 ...

  6. iOS 类知乎”分页”效果的实现?

    我们先看张gif图看一下效果(LICEcap录制的有点卡, 凑合看) 好像还是卡, 怼个视频演示链接吧: https://m.weibo.cn/1990517135/4398431764047996 ...

  7. idea新建javaweb工程

    最近尝试了idea的使用,将idea建立javaweb工程的步骤记录下来 1.方框里边是重点 2.next后输入工程文件名点击finish 3.如图看到项目文件夹里边没有WEB-INF文件夹及里边的w ...

  8. web设计_9_CSS常用布局,响应式

    一个完整的页面和其中的组件该如何具备灵活性. 怎么样利用CSS来实现无论屏幕.窗口以及字体的大小如何变化,都可以自由扩展和收缩的分栏式页面. 要决定使用流动布局.弹性布局还是固定宽度的布局,得由项目的 ...

  9. jboss反序列化漏洞复现(CVE-2017-7504)

    jboss反序列化漏洞复现(CVE-2017-7504) 一.漏洞描述 Jboss AS 4.x及之前版本中,JbossMQ实现过程的JMS over HTTP Invocation Layer的HT ...

  10. hdoj 4762 Cut the Cake

    题意很简单就不说了. 解题的关键就是这个公式 answer=n/(m^(n-1)); 要用到大数的乘法.然后java水过. import java.util.*; import java.math.* ...