前言

在计算机科学中,垃圾回收(GC: garbage collection)是内存自动管理的一种方式,它并不是同 Java 语言一起诞生的,实际上,早在 1959 年为了简化 Lisp 语言的手动内存管理,该语言的作者就开始使用了内存自动管理技术。 垃圾收集手动内存管理刚好相反,后者需要编程人员自己去指定需要释放的对象然后将内存归还给操作系统,而前者不需要关心给对象分配的内存回收问题。Java 语言使用自动垃圾收集器来管理对象生命周期中的内存,要进行垃圾收集首先需要明确三个问题:1. 哪些内存需要回收2. 什么时候进行回收3. 怎么进行内存回收。接下来让我们一起看看 Java 语言对这些问题是如何处理的。

哪些内存需要回收

为了方便管理和跨平台,Java 虚拟机规范规定在执行 Java 程序的时候把它所管理的内存划分为若干个不同的数据区域。这些区域都有着各自不同的用途以及创建和销毁的时间,有的数据区域随着用户线程的启动和结束而建立和销毁,有的区域会随着虚拟机进程的启动和停止而存在和销毁。更多有关运行时数据区域的内容请看 Java 运行时数据区域

由于 Java 运行时数据区域中的 程序计数器虚拟机栈本地方法栈和线程的生命周期一致,随线程的启动和结束而建立和销毁。而且当我们的类结构确定了之后,在编译期间,一个栈帧需要分配内存的大小基本上也就确定下来了,这三个区域的内存分配和收回都是具备确定性的,不需要我们过多的去考虑内存回收问题。主要考虑Java 堆方法区的内存回收的问题。

什么时候进行回收

Java 语言中,一个对象的生命周期分为以下三个阶段:

  • 对象创建阶段 通常我们使用 new 关键字进行对象创建 e.g. Object obj = new Object();,当我们创建对象时,Java 虚拟机将分配一定大小的内存来存储该对象,分配的内存量可能会根据虚拟机厂商的不同而有所不同。
  • 对象使用阶段 在这个阶段,对象被应用程序的其它对象使用(其它活动对象拥有指向它的引用)。在使用期间,该对象会一直驻留在内存当中,并且可能包含对其它对象的引用。
  • 对象销毁阶段 垃圾收集系统监视对象,如果发现对象不被任何对象引用了,则进行该对象内存回收操作。

那么问题来了,该如何去判断一个对象有没有被引用呢?目前,主要有两种判断对象是否存活的算法,分别是 引用计数算法(Reference counting algorithm)可达性分析算法(Accessibility analysis algorithm)

引用计数算法

首先我们看看引用计数算法是如何判断的,该算法的主要思想就是给每个对象都添加一个引用计数器,当该对象被变量或者另一个对象引用时该计数器值就会加 1,同时当对象的一个引用无效时,对象计数器的值会相应的减 1。当对象引用计数器的值为 0 时,说明该对象已经不再被引用了,那么就可以销毁对象进行内存回收操作了。这个算法的实现比较简单,对象是否“存活”的判断效率也比较高,这个算法看起来确实不错,但是它有个致命的缺点就是:无法解决对象间相互引用的问题。相互引用简单来说就是,有两个对象 object1object2 都有一个引用类型字段 ref,并且做了如下赋值操作:

object1.ref = object2;
object2.ref = object1;

这两个对象除了上面这个赋值之外,不被其它任何对象引用,实际上这两个对象都不可能再被访问了,但是因为它们俩都互相引用了对方,导致引用计数器不为 0,导致使用引用计数器算法的 垃圾收集器 无法收集它们,它们就会一直存在于内存之中直到虚拟机进程结束。正是因为这个原因,市场上主流的 Java 虚拟机大部分都没有选用这个算法来管理内存,下面介绍的 可达性分析算法 就可以很好的避免了对象间相互引用的问题。

可达性分析算法

Java 虚拟机是通过可达性分析算法来判断对象是否存活的,该算法的主要思想是将一系列称为 GC Root 的对象作为起点,向下进行搜索,搜索经过的路径称为引用链(Reference chain),当一个对象到 GC Root 对象没有任何引用链的时候,则表示该对象是不可达的,可以对其进行内存回收。

Java 虚拟机中,规定以下几种情况可以作为 GC Root 对象:

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中 Native 方法引用的对象

怎么进行内存回收

当我们创建的对象不可达之后,Java 虚拟机会在后台自动去收集回收不可达对象的内存,自 Java 语言诞生以来,在垃圾收集算法上进行了许多更新,主要有标记-清除算法(Mark and sweep algorithm)复制算法(Copying algorithm)标记—整理算法(Mark and compact algorithm)分代收集算法(Generational collection algorithm),根据这些算法实现的垃圾收集器在后台默默运行以释放内存,下面让我们看看它们是如何工作的。

标记-清除算法(mark and sweep algorithm)

标记—清除算法是初始且非常基本的算法,主要分为以下两个阶段:

  1. 标记需要回收对象,找出程序中所有需要回收的对象并标记。
  2. 清除所有标记对象,在标记完成后统一回收被标记对象。

首先标记出需要回收的对象,标记完成后再统一回收被标记对象。这个算法是最基础的垃圾收集算法,后面将要介绍的几个算法都是在它的基础上优化改进的,算法主要有两个不足的地方:① 效率不高,标记和清除过程的效率都不高。② 空间利用率不高,标记清除之后会产生大量不连续的内存碎片,后面如果要分配大对象的时候由于连续内存不足可能会再次触发垃圾收集操作。

复制算法(copying algorithm)

复制算法就是为了解决标记—清除算法的效率问题的,主要思想就是将可用的内存分为大小相等的两个部分,每一次都只使用其中的一块,当这块内存使用完了之后,就将依然存活的对象复制到另一块内存上去,然后再把这块含有可回收对象的内存清理掉,这样每次都是清理一半的连续内存了,就不会存在内存碎片的情况。但是这个算法的缺点也很明显,它把可用内存的大小缩小到了一半。

标记-整理算法(mark and compact algorithm)

如果对象的存活率比较低的情况下,上面介绍的复制算法效率还是很高的,毕竟只要复制少部分存活对象到另一块内存中即可,但是当对象的存活率比较高时就会进行多次复制操作。比如老年代,老年代的对象是经过多次垃圾回收依然存活的对象,对象的存活率相对来说比较高,根据老年代的这个特点,于是针对这种情况就有了另一个算法称之为标记-整理算法,主要思想和其名字一样也是分为标记整理两个阶段,第一个标记阶段依然和标记—清除算法一样,后面的第二个整理阶段就不是直接对可回收对象进行清理了,而是让所有存活的对象都向内存的同一侧移动,然后就直接清除掉另一侧的内存。

分代收集算法(generational collection algorithm)

根据不同分代的特点,现在商业上的虚拟机针对不同的分代采取适合的垃圾收集,一般是把 Java 堆分为新生代和老年代。在新生代中,对象大部分存活时间都很短每次垃圾收集都会有很多的对象被清除,只有少部分对象可以存活下来,那么此时就可以使用复制算法,只需要复制出少部分存活的对象即可效率高。然而在老年代中大部分对象的存活时间比较长,则需采用标记-清除算法或者标记-整理算法来进行垃圾收集。

垃圾收集算法对于垃圾回收来说类似于我们程序中的接口,是一套垃圾回收的指导算法,算法的具体实现我们称之为垃圾收集器。但是 Java 虚拟机规范中并没有对垃圾收集器的实现有任何规定。所以不同的厂商和不同版本的虚拟机实现的垃圾收集器也不一样,不过一般都会提供一些配置参数来让用户根据自身情况来设置所需的垃圾收集器。

JVM 相关 GC 配置

Java 虚拟机部分垃圾收集(Garbage Collection,GC)相关配置如下

参数 描述
-Xms2048m 设置初始堆大小(新生代 + 老年代)
-XX:InitialHeapSize=3g 设置初始堆大小(新生代 + 老年代)
-Xmx3g 设置最大堆大小(新生代 + 老年代)
-XX:MaxHeapSize=3g 设置最大堆大小(新生代 + 老年代)
-XX:NewSize=128m 设置堆初始新生代大小
-XX:MaxNewSize=128m 设置堆最大新生代大小
-XX:PermSize=512m(JDK 1.7) 设置初始永久代(元空间)大小
-XX:MetaspaceSize=512m(JDK 1.8+) 设置初始永久代(元空间)大小
-XX:MaxPermSize=1g(JDK 1.7) 设置最大永久代(元空间)大小
-XX:MaxMetaspaceSize=1g(JDK 1.8+) 设置最大永久代(元空间)大小
-XX:+DisableExplicitGC 忽略应用程序对 System.gc() 方法的任何调用
-XX:+PrintGCDetails 打印输出 GC 收集相关信息

参考文章

Java 垃圾收集技术的更多相关文章

  1. 深入分析Java Web技术内幕(修订版)

    阿里巴巴集团技术丛书 深入分析Java Web技术内幕(修订版)(阿里巴巴集团技术丛书.技术大牛范禹.玉伯.毕玄联合力荐!大型互联网公司开发应用实践!) 许令波 著   ISBN 978-7-121- ...

  2. Java垃圾收集器

    概述 说起垃圾收集(Garbage Collection,GC),大部分人都把这项技术当做Java语言的伴生产物.事实上,GC的历史远远比Java久远,1960年诞生于MIT的Lisp是第一门真正使用 ...

  3. 《深入分析Java Web技术内幕》读书笔记 - 第1章 深入Web请求过程

    第1章 深入Web请求过程 1 1.1 B/S网络架构概述 2 基于统一的应用层协议HTTP来交互数据. 1.2 如何发起一个请求 4 HTTP连接本质是建立Socket连接.请求实现方式:工具包如H ...

  4. 面试官,不要再问我“Java 垃圾收集器”了

    如果Java虚拟机中标记清除算法.标记整理算法.复制算法.分代算法这些属于GC收集算法中的方法论,那么"GC收集器"则是这些方法论的具体实现. 在面试过程中这个深度的问题涉及的比较 ...

  5. 面试官,不要再问我“Java 垃圾收集器”了(转载)

    如果Java虚拟机中标记清除算法.标记整理算法.复制算法.分代算法这些属于GC收集算法中的方法论,那么"GC收集器"则是这些方法论的具体实现. 在 面试过程中这个深度的问题涉及的比 ...

  6. Java基础技术JVM面试【笔记】

    Java基础技术JVM面试[笔记] JVM JVM 对 java 类的使用总体上可以分为两部分:一是把静态的 class 文件加载到 JVM 内存,二是在 JVM 内存中进行 Java 类的生命周期管 ...

  7. JVM基础学习(二):内存分配策略与垃圾收集技术

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来 垃圾收集概述 Java内存模型中的堆和方法区是垃圾收集技术所需要关注的终点,因为其他的区域会跟 ...

  8. Java数据库连接技术——JDBC

    大家好,今天我们学习了Java如何连接数据库.之前学过.net语言的数据库操作,感觉就是一通百通,大同小异. JDBC是Java数据库连接技术的简称,提供连接各种常用数据库的能力. JDBC API ...

  9. java 深入技术八(内省)

    1. javabean的软件设计思想 2.内省:封装了java反射,提供直接操作属性的Setter和getter方法的方法 3.核心API:BeanInfo java 的描述信息,Introspect ...

随机推荐

  1. <JZOJ5944>信标

    emmm树形dp?好像是的 搬一个题解证明过来 由于在n>1时答案至少为1,我们枚举一个必须放的根, 所有深度不同的点就被区分开了. 设一个节点有c个儿子, 发现必须在其中至少c−1个儿子的子树 ...

  2. vue日常问题总结

    1.Vue项目启动后首页URL带的#该怎么去掉? vue-router中默认使用的是hash模式,URL中带有#号,我们可以用如下代码修改成history模式: import Vue from 'vu ...

  3. Linux系统添加新用户

    Linux系统中一般不直接使用root用户进行操作,需要添加新的用户. 首先,查看当前系统已有的用户 cat /etc/passwd 查看用户组 cat /etc/group 其次,添加想要的用户组和 ...

  4. 选拔赛 hash 字符串匹配 哈希算法(白书p374)

    hash   Description dr所在国度的有个奇怪的规定:他们的字母不是a~z,而是用1~1000表示. 利用这个奇怪的规定,dr想出了一个好玩的游戏:首先给出n个字符串(当然每个字符用1~ ...

  5. 企业级rancher搭建Kubernetes(采用rancher管理平台搭建k8s)

    一.简介 Rancher简介 来源官方:https://www.cnrancher.com/ Rancher是一个开源的企业级容器管理平台.通过Rancher,企业再也不必自己使用一系列的开源软件去从 ...

  6. python中使用xlrd读excel使用xlwt写excel

    原文地址 :http://www.bugingcode.com/blog/python_xlrd_read_excel_xlwt_write_excel.html 在数据分析和运营的过程中,有非常多的 ...

  7. 使用 javascript 配置 nginx

    在上个月的 nginx.conf 2015 大会上, 官方宣布已经支持通过 javascript 代码来配置 nginx,并把这个实现称命名为--nginscript.使用 nginscript,可以 ...

  8. python3.4多线程实现同步的四种方式

    临界资源即那些一次只能被一个线程访问的资源,典型例子就是打印机,它一次只能被一个程序用来执行打印功能,因为不能多个线程同时操作,而访问这部分资源的代码通常称之为临界区. 1. 锁机制 threadin ...

  9. C2C的道德边界:沦为从假运单到假病条的供假渠道

    你可能刚开始学会不去看网购平台上商品回评中的虚假好评,却又要开始应对同事在朋友圈等平台买来的虚开病假条带来的困扰.最近各大媒体包括党报热传的网购病假条事件,再度将人们的目光集中在这个C2C模式之上.从 ...

  10. idea激活教程(永久)支持2019 3.1 亲测

    此教程已支持最新2019.3版本 本教程适用Windows.Mac.Ubuntu等所有平台. 激活前准备工作 配置文件修改已经不在bin目录下直接修改,而是通过Idea修改 如果输入code一直弹出来 ...