Java垃圾回收与算法详解

首先来张祖传的思维导图:

  

  内存回收与垃圾收集器在很多时候都是影响系统性能、并发能力的主要因素,虚拟机之所以提供多种不同的收集器以及提供大量的调节参数,是因为只有根据实际应用需求、实现方式选择最优的垃圾收集方式才能获得最好的性能。

GC发生在那里:

  JVM虚拟机运行时内存区域主要分为(如下图):虚拟机栈、本地方法栈、程序计数器、Java堆、方法区。其中虚拟机栈、本地方法栈、程序计数器为线程私有区域,在这几个区域中就不需要过多的考虑垃圾回收的问题,因为方法结束或者线程结束时,内存自然就跟着回收了。而Java堆和方法区则不一样,这两个区域为线程共享区域,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建那些对象,这两部分内存的分配和回收都是动态的,垃圾回收主要发生在这两部分区域。JVM内存区域解析可参考:xxxxxx

如何给对象分配内存:

  详细讲解见:xxx

如何判断那些对象需要被回收:

  现在主流的虚拟机主要使用可达性分析算法来判断对象是否可以被回收,而不是使用引用计数算法来判断,并行进行二次标记筛选来最终回收对象。

1、引用计数算法:

  基本思想:给对象中加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1;任何时刻计数器为0时对象就是不能被使用,可回收了。

  例子:在发生gc时t1、t2对象是否会被回收?如果按照引用计数算法对象t1和t2之间相互引用,对象不会被回收,但是结果是要被回收的。说明虚拟机并不是使用引用计数算法来判断对象是否可以被回收。主要原因是引用计数算法很难解决对象之间相互引用的问题。

 1 public class TestGC {
 2     public Object instance = null;
 3     private static final int oneMb = 1024 * 1024;
 4     private byte[] bigSize = new byte[2 * oneMb];
 5
 6     public static void test(){
 7         TestGC t1 = new TestGC();
 8         TestGC t2 = new TestGC();
 9         t1.instance = t2;
10         t2.instance = t1;
11
12         t1 = null;
13         t2 = null;
14         //在这里发生GC, t1和t2是否能被回收?
15         System.gc();
16     }
17 }

2、可达性分析算法:

  基本思想:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则说明该对象不可用,可以被回收。

可以作为GC Roots的对象包括:

  (1)虚拟机栈中引用的对象

  (2)方法区中类静态属性引用的对象

  (3)方法区中常量引用的对象

  (4)本地方法栈中JNI引用的对象

  可以理解为:  

  (1)首先第一种是虚拟机栈中的引用的对象,我们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种情况是最常见的。

  (2)第二种是我们在类中定义了全局的静态的对象,也就是使用了static关键字,由于虚拟机栈是线程私有的,所以这种对象的引用会保存在共有的方法区中,显然将方法区中的静态引用作为GC Roots是必须的。

  (3)第三种便是常量引用,就是使用了static final关键字,由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也应该作为GC Roots。最后一种是在使用JNI技术时,有时候单纯的Java代码并不能满足我们的需求,我们可能需要在Java中调用C或C++的代码,因此会使用native方法,JVM内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为GC Roots。

  无论是通过引用计数算法还是可达性分析算法判断对象,判断对象是否存活都与引用有关,java中的引用包括4种(由强到弱):强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。

  • 强引用(StrongReference)
    强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。类似Object obj = new Object()。
  • 软引用(SoftReference)
    如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(下文给出示例)。
    软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
  • 弱引用(WeakReference)
    弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
    弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
  • 虚引用(PhantomReference)
    “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
    虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

3、最终判定(二次标记筛选):

  即使对象进行了可达性分析算法判定后,发现没有与GC Roots可达的引用链,此对象也不会立刻进行回收,还需要经历二次标记筛选过程,才能回收对象。

  (1)第一次标记:如果某个对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机执行过,则这两种情况下,虚拟机会视为这对象“没有必要执行”,对象会被回收。

  (2)第二次标记:以第一次标记情况相反,如果这个对象被判断为有必要执行finalize()方法,则将这个对象放入F-Queue队列中,此后虚拟机会自动建立一个低优先级的Finalizer线程对F-Queue中的对象进行二次标记,如果对象在finalize()方法中成功的重新与引用链上的任何一个对象建立关联,那么将把该对象移出“即将回收”的集合,该对象不会被回收,否则将被回收。每个对象都有finalize()方法(Object类里定义了该方法),finalize()方法是对象逃脱被回收的最后一次机会。

如何回收内存垃圾:

  当然是使用各种垃圾垃圾收集算法来对内存进行清理,包括有:标记-清除算法(Mark-Sweep)、复制算法(Copying)、标记-整理算法(Mark-Compact)、分代收集算法(Generational Collection)。

  1、标记-清除算法(Mark-Sweep):

  基本思想:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,它的标记过程是根据可达性分析算法和二次标记过程来判断和标记。

  缺点:(1)标记和清除两个过程效率不高;(2)标记清除后会产生大量不连续的内存碎片;内存碎片太多会导致以后在程序运行需要分配较大对象时,无法找到足够连续的内存空间而不得不提前触发另一次的垃圾回收动作。

  2、复制算法(Copying):

  基本思想:它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效。

  复制算法主要应用于回收新生代,新生代内存划分为:Eden区、From Servivor区、To Servivor区,如图:

  缺点:(1)内存缩小为原来的一半。(2)如果对象存活率较高时需要进行较多次的复制操作,效率会降低。(3)需要有额外的空间进行分配担保。

  3、标记-整理算法(Mark-Compact):

  基本思想:标记过程与标记-整理算法一样,但后续步骤不是直接对可回收的对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

  4、分代收集算法(Generational Collection):

  基本思想:根据对象的存活时间不同,将java堆分为新生代和老年代,根据各个年代的特点采用最合适的收集算法。在新生代中,每次垃圾收集时都发现有大量的对象死去,只有少数存活,可以采用复制算法,只需要复制出少量存活的对象就可以完成收集。在老年代中对象的存活率高,没有额外空间进行分配担保,可以采用“标记-清除”或者“标记-整理”算法。当前商业虚拟机都是采用此算法。

垃圾收集器

  如下图所示,java垃圾收集器主要有一下7种,作用于新生代:Serial收集器、ParNew收集器、Parallel Scavenge收集器。作用于老年代:Serial Old收集器、Parallel Old收集器、CMS(Concurrent Mark Sweep)收集器。G1(Garbage First)收集器主要面向服务端应用的垃圾收集器。两个收集器之间存在连线,说明它们可以搭配一起使用。    

未完,待续...  

本文参考:

  《深入理解java虚拟机》周志明

注:本文主要是学习《深入理解java虚拟机》一书的读书笔记,以此来总结和学习。本文图片大部分来源于网络,侵删。

原文地址:https://www.cnblogs.com/oush/p/11686671.html

时间: 2024-10-08 22:39:47

Java垃圾回收与算法详解的相关文章

Java垃圾回收机制(GC)详解

Java垃圾回收机制(GC)详解 简介: 垃圾回收GC(Garbage Collection)是Java语言的核心技术之一,之前我们曾专门探讨过Java 7新增的垃圾回收器G1的新特性,但在JVM的内部运行机制上看,Java的垃圾回收原理与机制并未改变.垃圾收集的目的在于清除不再使用的对象.GC通过确定对象是否被活动对象引用来确定是否收集该对象.GC首先要判断该对象是否是时候可以收集.两种常用的方法是引用计数和对象引用遍历. 垃圾收集的算法分析: Java语言规范没有明确地说明JVM使用哪种垃圾

Java 垃圾回收 - 收集算法

续前节,前面列举了Java判断对象是否可回收的两种方法:可达性分析与引用权重法.多数虚拟机所采用的的是可达性分析方法.下面写一下Java中的垃圾收集算法. Java中垃圾收集算法主要有4中算法分别为:标记-清除法:复制算法:标记-整理法:分代收集算法.详情如下. 1.标记-清除法 标记清除法算法主要分两个阶段进行:①.标出所有需要回收的内存片段,②.回收所标记内存.此种方法存在两种不足.一.标记.清除两个过程对性能损耗较大,效率太低:二.标记清除后会产生大量的内存碎片.如下图: 2.复制算法 复

Java的Arrays部分算法详解

java的java.util.Arrays工具类提供了很多有用的方法,而且有很多方法是重载(overload)的,现在来研究一些部分算法的应用. 1. 二分查找double数组 public static int binarySearch(double[] a, int fromIndex, int toIndex, double key) { rangeCheck(a.length, fromIndex, toIndex); return binarySearch0(a, fromIndex,

Java排序需掌握算法 详解

package com.sxt.review; /*内部排序:(在内存) * 插入排序-->希尔排序 * 冒泡排序-->快速排序 * 选择排序-->堆排序 * 归并排序 * 基数排序 * 外部排序:(排序过程需访问外存) */ import java.util.Arrays; public class TestSort { public static void main(String[] args) { int[] arr = { 2, 45, 3, 0, 7, 9, 2, 88 };

java 垃圾回收机制

1. java的垃圾回收机制主要工作包括:确定哪些对象属于垃圾,回收无用的对象占用的空间,使堆中的存活对象紧密排列. 2. Java垃圾回收的算法: 引用计数(基本不用): 当引用指向一个对象时,该对象的引用计数器+1: 当引用离开对象挥着被标记为null时,引用计数器-1: 当引用计数器为0时,释放对象占用的空间. 缺陷:如果对象间存在循环引用,可能会发生对象无法被回收的情况. 停止-复制: 对象发现: 从堆栈和静态存储区出发遍历所有引用,找到引用的对象以及该对象包含的所有引用,从而找到所有活

java垃圾回收机制的理解

Java垃圾回收机制算法 标记----清除算法 复制算法 标记----整理算法 分代收集算法 为什么要进行垃圾回收 因为当一个对象的引用不可达,或者一个对象没有任何引用指向它,那么它就没有必要在内存中继续存在,此时它就处于可以被GC(垃圾回收器)回收的对象,jvm虚拟机动态的收集不可用的对象,达到释放内存的目的. 垃圾回收区域 通常情况下的jvm虚拟机把内存都分为了如下几块 虚拟机栈(生命周期和线程相同,每一个线程执行的时候会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出入口等信息,

详解java垃圾回收机制(转)及finalize方法(转)

详细介绍Java垃圾回收机制 垃圾收集GC(Garbage Collection)是Java语言的核心技术之一,之前我们曾专门探讨过Java 7新增的垃圾回收器G1的新特性,但在JVM的内部运行机制上看,Java的垃圾回收原理与机制并未改变.垃圾收集的目的在于清除不再使用的对象.GC通过确定对象是否被活动对象引用来确定是否收集该对象.GC首先要判断该对象是否是时候可以收集.两种常用的方法是引用计数和对象引用遍历. 引用计数收集器 引用计数是垃圾收集器中的早期策略.在这种方法中,堆中每个对象(不是

Java垃圾回收机制详解

1. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象:而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾.JVM的一个系统级线程会自动释放该内存块.垃圾回收意味着程序不再需要的对象是"无用信息",这些信息将被丢弃.当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用.事实上,除了释放没用的对象,垃圾回收也可以清除内存记录碎片.由于创建对象和垃圾回收器释放丢弃对象所占的内存空间,

机器学习经典算法详解及Python实现---朴素贝叶斯分类及其在文本分类、垃圾邮件检测中的应用

摘要: 朴素贝叶斯分类是贝叶斯分类器的一种,贝叶斯分类算法是统计学的一种分类方法,利用概率统计知识进行分类,其分类原理就是利用贝叶斯公式根据某对象的先验概率计算出其后验概率(即该对象属于某一类的概率),然后选择具有最大后验概率的类作为该对象所属的类.总的来说:当样本特征个数较多或者特征之间相关性较大时,朴素贝叶斯分类效率比不上决策树模型:当各特征相关性较小时,朴素贝叶斯分类性能最为良好.另外朴素贝叶斯的计算过程类条件概率等计算彼此是独立的,因此特别适于分布式计算.本文详述了朴素贝叶斯分类的统计学