Java虚拟机--垃圾回收机制

  Java与C++相比,具有动态分配内存垃圾回收机制的技术优势,使得我们不用把精力集中在内存的管理上,那我们为什么还要去了解GC和内存分配呢?原因很简单:当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。

1.为什么进行垃圾回收

   随着程序的运行,系统内存中存在的对象实例、各种变量越来越多,如果不进行垃圾回收,会影响到程序的性能,当占用内存过多时,还会产生OOM等系统异常。

2.哪些内存需要回收

  关于JVM内存结构,分为:程序计数器、虚拟机栈、本地方法栈、堆、方法区。其中前三者随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而有条不紊的执行者出栈和入栈的操作,当方法调用结束或线程结束时,内存就自然跟着回收了,所以只有堆和方法区需要GC。那如何鉴定GC的对象呢?简单讲:如果某个对象已经不存在任何引用,那么它可以被回收。

3.什么时候进行回收

  引用计数算法来判断对象是否存活:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;引用失效则减1;任何时刻计数器为0的对象就是不能被使用的。这种算法并没有在主流的Java虚拟机里使用,因为它有一个很大的缺点:很难解决对象之间互相循环引用的问题。

  可达性分析算法判断对象是否存活:基本思路就是通过一系列的称为“GC Roots”的对象为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,则证明此对象是不可用的。如图所示:虽然object5、6、7相关联,但它对于GC Roots来说是不可达的,所以是可以回收的对象。

             

  :在Java语言中,可以作为GC Roots对象的包括以下几种:虚拟机栈(栈帧中的本地变量表)中引用的对象方法区中类静态属性引用的对象方法区常量引用的对象本地方法栈中Native方法引用的对象

  拓展:JDK 1.2之后,Java将引用的概念进行了扩充,分为:强、软、弱、虚,强度逐级递减。

    强(Strong Refence):指在程序代码中普遍存在的,类似“Object o = new Object()”这类引用,只要强引用在,垃圾回收器就永远不会回收调被引用的对象。

    软(Soft Refence):用来描述一些还有用但非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,会把它列入可回收的范围内,回收之后内存仍不够,才会抛出内存溢出异常。

    弱(Weak Refence):同样是用来描述一些有用但非必须的对象,但比软引用还弱,被弱引用关联的对象,只能生存到下一次垃圾手机发生之前。

    虚(Phantom Refence):最弱的一种引用关系,它的存在,完全不会对其关联的对象的生存时间构成影响,也无法通过虚引用来取得一个对象的实例,它的唯一作用就是能在这个关联对象被回收时收到一个系统通知。

  生存还是死亡?

  即使是可达性分析算法中不可达,也并非确认“死亡”,要真正宣告一个对象“死亡”,至少要经历两次标记过程:当发现不可达后,会进行第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法或finalize()方法已经被虚拟机调用过,虚拟机将视这两种情况为“没有必要执行”;如果这个对象被判定为有必要执行finalize()方法,将会被放入F-Queue队列中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它,此处“执行”指触发这个方法。finalize()方法是摆脱“死亡”的最后一次机会,稍后GC将对F-Queue中的对象进行第二次标记。

  总结:什么样的类需要回收呢?满足条件:1>.该类的所有实例对象都已经被回收  2>.加载该类的ClassLoader已经被回收  3>.该类对应的反射类java.lang.Class对象没有被任何地方引用。

4.如何回收

  标记清除算法:分为“标记”和“清除”两个阶段,标记所有需要回收的对象,然后清楚。缺陷:标记和清楚两个阶段的效率不高;产生大量的空间碎片,可能导致分配大对象时空间不足而提前进行一次GC。

  复制算法:将内存分为大小相等的两块,只用一块,当一块空间满时会将活着的对象复制到另一块中,然后对原先使用过的那块内存空间进行一次清理。优点:运行高效,不用担心空间碎片的产生。缺陷:将内存一分为二,代价稍高。BUT,在虚拟机的实际设计中并不是这么分的,因为新生代中绝大多数都是“朝生夕死”,所以不需要1:1划分,而是分为Eden:From Survivor:To Survivor(8:1:1),当内存回收时,将Eden和From Survivor中的存活对象复制到另一块Survivor中。

  标记整理算法:当对象成活率很高时,复制算法就不合适了,例如老年代。标记整理算法是把被标记的对象都移动到一段,直接清理端界以外的内存。

  分代收集算法:当前商业虚拟机都采用的此算法,根据新生代和老年代各自的特征选择对应的回收算法。新生代-复制算法;老年代-“标记-清除”或“标记-整理”。

5.内存回收的具体实现----垃圾收集器

            

                 注:如果两个收集器间有连线,则可以搭配使用。

  Serial收集器:一个单线程收集器,除了只会使用一个CPU或一条垃圾收集线程去回收垃圾,更重要的是它在垃圾收集时会停止进程,知道收集结束,即“Stop The World”。当然也有优点:简单而高效(与其他收集器的单线程比)。

  ParNew收集器:Serial收集器的多线程版本,是运行在Server模式下的虚拟机中首选的新生代收集器,其中一个很重要的原因是可以和CMS搭配使用。

  Parallel Scavenge收集器:一个并行的、使用复制算法、作用于新生代的收集器,它与CMS等收集器的不同在于关注点不一样。CMS关注于尽可能的缩短垃圾收集时用户线程的停顿时间;而Parallel Scavenge收集器则是达到一个可控制的吞吐量(运行用户代码时间/(运行用户代码时间+垃圾收集时间)),主要适合在后台运算而不需要太多交互的任务。

  Serial Old收集器:使用“标记-整理”算法的Serial收集器的老年代版本,此收集器的意义在于给Client模式下的虚拟机使用。

  Parallel Old收集器:Parallel Scavenge的老年代版本,使用多线程和“标记-整理”算法。

  CMS收集器:是一种以获取最短回收停顿时间为目标的收集器,基于“标记-清楚”算法实现,运作过程分为四个步骤:初始标记并发标记重新标记并发清除

    ■ 初始标记:标记GC Roots能直接关联到的对象,速度很快。

    ■ 并发标记:进行GC Roots Tracing(判断对象是否可回收)的过程。

    ■ 重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。

    ■ 并发清除:清除可回收的对象。和并发标记过程一起,都可以与用户线程一起工作

  CMS优点:并发收集、低停顿。缺点也很明显,如下

    ● CMS对CPU资源非常敏感。在并发阶段,虽不会导致用户线程停顿,但会占用CPU资源,导致程序变慢,总吞吐量下降。

    ● CMS无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败导致另一次Full GC。因为并发清理阶段和用户线程同步运行,会不断的产生新的垃圾,在被标记后,无法在当次垃圾收集时对其进行回收,只能等到下次,这些就成为“浮动垃圾”。JDK 1.5默认老年代使用68%空间就会激活CMS,之所以这样留出空间也是因为并发的原因。

    ● CMS基于“标记-清除”,产生大量空间碎片。

  注:并发(Concurrent):指用户线程与垃圾收集线程同时执行(不一定是并行,可能交替执行),用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。

    并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。

  G1收集器:一款面向服务端的垃圾收集器,具有如下特点:

    ▲ 并行与并发:集两种方式的优势于一体。

    ▲ 分代收集 :同其他收集器一样,虽然G1不需要其他收集器配合就可以管理整个GC堆,但能采用不同方式去管理新生代和老年代,且效果不错。

    ▲ 空间整合:整体来看基于“标记-整理”,局部来看基于“复制”;无论怎么看也不会产生空间碎片。

  

    

  

  

  

原文地址:https://www.cnblogs.com/lemon-pomelo/p/9255274.html

时间: 2024-08-28 03:28:31

Java虚拟机--垃圾回收机制的相关文章

Java虚拟机垃圾回收机制

在Java虚拟机中,对象和数组的内存都是在堆中分配的,垃圾收集器主要回收的内存就是再堆内存中.如果在Java程序运行过程中,动态创建的对象或者数组没有及时得到回收,持续积累,最终堆内存就会被占满,导致OOM. JVM提供了一种垃圾回收机制,简称GC机制.通过GC机制,能够在运行过程中将堆中的垃圾对象不断回收,从而保证程序的正常运行. 垃圾对象的判定 我们都知道,所谓“垃圾”对象,就是指我们在程序的运行过程中不再有用的对象,即不再存活的对象.那么怎么来判断堆中的对象是“垃圾”.不再存活的对象呢?

老生常谈Java虚拟机垃圾回收机制(必看篇)

二.垃圾收集 垃圾收集主要是针对堆和方法区进行. 程序计数器.虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收. 判断一个对象是否可被回收 1. 引用计数算法 给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1.引用计数为 0 的对象可被回收. 两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收. 正因为循环引用的存在,因此 Java 虚拟机不使用引用计数

java的垃圾回收机制的特点

浅谈java的垃圾回收机制的特点: 1.垃圾回收机制的目标是回收无用对象的内存空间(记住:不是对象),这些内存空间是JVM堆内存的内存空间.垃圾回收只回收内存资源,对于那些物理资源,如数据库连接,Socket,I/O流等资源无能无能为力,我们要自己关闭回收. 2.为了加快垃圾回收机制回收那些无用对象所占的内存空间,我们可以讲对象的引用变量置于null(记住:置于null后,垃圾回收机制不会立即执行的). 3.垃圾回收机制的潜在缺点它的开销会影响性能.Java虚拟机必须跟踪程序中有用的对象才可以确

记录Java的垃圾回收机制和几种引用

一.Java的垃圾回收机制 Java的垃圾回收机制(java garbage collection)是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的堆内存空间. 注意粗体字的地方,java的垃圾回收线程是优先级比较低的线程,什么时候进行垃圾回收难以确定.当某些对象被标记为垃圾对象后,等垃圾回收线程运行时,就会将这些对象回收(确切的说应该是回收这些对象所占的堆内存空间). 二.什么样的对象会被标记成垃圾对象呢? 一般来说,所有指向对象的引用都已失效,不可能再有

Java的垃圾回收机制(转载)

引用自 <http://lemote.blog.163.com/blog/static/1748395072013111641050934/> 引自文章:<http://blog.csdn.net/zsuguangh/article/details/6429592>    Java的垃圾回收机制是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间.需要注意的是:垃圾回收回收的是无任何引用的对象占据的内存空间而不是对象本身,很多人来我公司面试

面试官,不要再问我“Java GC垃圾回收机制”了

Java GC垃圾回收几乎是面试必问的JVM问题之一,本篇文章带领大家了解Java GC的底层原理,图文并茂,突破学习及面试瓶颈. 楔子-JVM内存结构补充 在上篇<JVM之内存结构详解>中有些内容我们没有讲,本篇结合垃圾回收机制来一起学习.还记得JVM中堆的结构图吗? 图中展示了堆中三个区域:Eden.From Survivor.To Survivor.从图中可以也可以看到它们的大小比例,准确来说是:8:1:1.为什么要这样设计呢,本篇文章后续会给出解答,还是根据垃圾回收的具体情况来设计的.

Java的垃圾回收机制笔记

Java的垃圾回收机制笔记 java垃圾回收的意义 确保不再被引用的对象的内存空间被回收. 确保被引用的对象的内存不被错误回收. 再分配内存. java垃圾回收的常用方法 引用计数收集器 堆中的每个对象(不是对象的引用)都有一个引用计数.当一个对象被创建时,给该对象分配一个变量,该变量计数设置设置为1.当任何其他变量被赋值为这个对象的引用,计数加1(a=b,则b引用的对象计数+1),但当一个对象的某个引用超过生命周期或者被设置为一个新值的时候,引用的计数减1(a=c,则a不再指向b指向的对象,而

Java的垃圾回收机制

以前很少关注内存的问题,基本没有关注,这方面的小白,原因在于自己都是写的自我娱乐的小程序,不关注性能,不是提供服务.而企业级别的应用在程序稳健性方面的要求大大提高,因此要考虑更多的问题.对于大公司来说,为了应对各种情况,服务器资源肯定充足,但是由于应用很多,那么我们要尽可能的节省资源,对于Java程序,内存资源相当宝贵,那么理解java里面怎么做垃圾回收,是很重要的. 我阅读的参考主要是两个内容[1,2],但还没有做具体的实践,也没有切身体会,只能纸上谈兵了. 那么首先要介绍Java里面的内存分

java JVM垃圾回收机制

Java语言出来之前,大家都在拼命的写C或者C++的程序,而此时存在一个很大的矛盾,C++等语言创建对象要不断的去开辟空间,不用的时候有需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都在重复的allocated,然后不停的~析构.于是,有人就提出,能不能写一段程序在实现这块功能,每次创建,释放控件的时候复用这段代码,而无需重复的书写呢? 1960年 基于MIT的Lisp首先提出了垃圾回收的概念,用于处理C语言等不停的析构操作,而这时Java还没有出世呢!所以实际上GC并不是Jav