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

在Java虚拟机中,对象和数组的内存都是在堆中分配的,垃圾收集器主要回收的内存就是再堆内存中。如果在Java程序运行过程中,动态创建的对象或者数组没有及时得到回收,持续积累,最终堆内存就会被占满,导致OOM。

JVM提供了一种垃圾回收机制,简称GC机制。通过GC机制,能够在运行过程中将堆中的垃圾对象不断回收,从而保证程序的正常运行。

垃圾对象的判定

我们都知道,所谓“垃圾”对象,就是指我们在程序的运行过程中不再有用的对象,即不再存活的对象。那么怎么来判断堆中的对象是“垃圾”、不再存活的对象呢?

引用计数法

每个对象都有一个引用计数的属性,用来保存该对象被引用的次数。当引用次数为0时,就意味着该对象没有被引用了,也就不会在使用这个对象了,可以判定为垃圾对象。但是,这种方式有一个很大的Bug,就是无法解决对象间相互引用或者循环引用的问题:当两个对象相互引用,他们两个和其他任何对象也没有引用关系,它俩的引用次数都不为0,因此不会被回收,但实际上这两个对象已经不再有用了。

可达性分析(根搜索法)

为了避免使用引用计数法带来的问题,Java采用了可达性分析法来判断垃圾对象。

这种方式可以将所有对象的引用关系想象成一棵树,从树的根节点GC Root遍历所有引用的对象,树的节点就为可达对象,其他没有处于节点的对象则为不可达对象。

那么什么样的对象可以作为GC的根节点呢?

虚拟机栈(帧栈中的本地变量表)中引用的对象

方法区中静态属性引用的对象

方法区中常量引用的对象

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

引用状态

垃圾回收机制,不管采用是引用计数法,还是可达性分析法,都与对象的引用有关,Java中存在四种引用状态:

强引用 - 我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,就表示它处于可达状态,垃圾回收器绝不会回收它,即便系统内存非常紧张,Java虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会回收被强引用所引用的对象。因此,强引用是造成Java内存泄露的主要原因之一。

软引用 - 一个对象只具有软引用,如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

弱引用 - 一个对象只具有弱引用,那就类似于是可有可无的。弱引用和软引用很像,但弱引用的引用级别更低。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

虚引用 - 一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收的活动,我们平常一般不会使用。

垃圾回收算法

通过可达性分析算法能够判定哪些对象是需要回收的了,那么回收具体需要怎样去执行呢?

标记-清除算法

首先需要标记可以回收的对象内存,然后在对回收的内存进行清除。

标记-清除算法(回收前)

标记-清除算法(回收后)

但是这样的话,随着程序的运行,会不断分配释放内存,在堆中会产生很多的不连续的空闲内存区,即内存碎片。这样即使有足够多的空闲内存,也不一定能分配出足够大的内存,并且可能会造成频繁的GC,影响效率,甚至OOM。

标记-整理算法

和标记-清除算法不同的是,标记-整理算法在标记后不直接清理可回收内存,而是将存活对象都移动到一端,然后清除掉可回收内存。

标记-整理算法(回收前)

标记-整理算法(回收后)

这样做的好处就是不会产生内存碎片。

复制算法

复制算法需要先将内存分为两块,先在其中一块内存上分配内存,当这块内存被分配完后,则执行垃圾回收,然后把存活对象全部复制到另一块内存上,第一块内存则全部清空。

复制算法(回收前)

复制算法(回收后)

这种算法不会产生内存碎片,但是相当于只能使用一半的内存空间。同时,复制算法和存活对象的数量有关,如果存活对象的数量多,那么复制算法的效率会大大降低。

分代收集算法

在Java虚拟机中,对象的生命周期有长有短,大部分对象的生命周期很短,只有少部分的对象才会在内存中存留较长时间,因此可以依据对象生命周期的长短将它们放在不同的区域。在采用分代收集算法的Java虚拟机堆中,一般分为三个区域,用来分别储存这三类对象:

新生代 - 刚创建的对象,在代码运行时一般都会持续不断地创建新的对象,这些新创建的对象有很多是局部变量,很快就会变成垃圾对象。这些对象被放在一块称为新生代的内存区域。新生代的特点是垃圾对象多,存活对象少。

老年代 - 一些对象很早被创建了,经历了多次GC也没有被回收,而是一直存活下来。这些对象被放在一块称为老年代的区域。老年代的特点是存活对象多,垃圾对象少。

永久代 - 一些伴随虚拟机生命周期永久存在的对象,比如一些静态对象,常量等。这些对象被放在一块称为永久代的区域。永久代的特点是这些对象一般不需要垃圾回收,会在虚拟机运行过程中一直存活。(在Java1.7之前,方法区中存储的是永久代对象,Java1.7方法区的永久代对象移到了堆中,而在Java1.8永久代已经从堆中移除了,这块内存给了元空间。)

分代收集算法也就根据新生代和老年代来进行垃圾回收的。

对于新生代区域,每次GC都会有很多垃圾对象被回收,只有少量存活。因此采用复制回收算法,GC时把剩余很少的存活对象复制过去即可。

在新生代区域中,并不是按照1:1的比例来进行复制回收,而是按照8:1:1的比例分为了Eden、SurvivorA、SurvivorB三个区域。其中Eden意为伊甸园,形容有很多新生对象在里面创建;Survivor区则为幸存者,即经历GC后仍然存活下来的对象。

Eden区对外提供堆内存。当Eden区快要满了,则进行Minor GC(新生代GC),把存活对象放入SurvivorA区,清空Eden区;

Eden区被清空后,继续对外提供堆内存;

当Eden区再次被填满,此时对Eden区和SurvivorA区同时进行Minor GC(新生代GC),把存活对象放入SurvivorB区,此时同时清空Eden区和SurvivorA区;

Eden区继续对外提供堆内存,并重复上述过程,即在 Eden 区填满后,把Eden区和某个Survivor区的存活对象放到另一个Survivor区;

当某个Survivor区被填满,且仍有对象未被复制完毕时,或者某些对象在反复Survive 15次左右时,则把这部分剩余对象放到老年代区域;当老年区也被填满时,进行Major GC(老年代GC),对老年代区域进行垃圾回收。

老年代区域对象一般存活周期较长,每次GC时,存活的对象比较多,因此采用标记-整理算法,GC时移动少量存活对象,不会产生内存碎片。

触发GC的类型

Java虚拟机会把每次触发GC的信息打印出来,可以根据日志来分析触发GC的原因。

GC_FOR_MALLOC:表示是在堆上分配对象时内存不足触发的GC。

GC_CONCURRENT:当我们应用程序的堆内存达到一定量,或者可以理解为快要满的时候,系统会自动触发GC操作来释放内存。

GC_EXPpCIT:表示是应用程序调用System.gc、VMRuntime.gc接口或者收到SIGUSR1信号时触发的GC。

GC_BEFORE_OOM:表示是在准备抛OOM异常之前进行的最后努力而触发的GC。

以上这篇老生常谈Java虚拟机垃圾回收机制(必看篇)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java 垃圾回收机制详解(动力节点Java学院整理)

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

  • 深入java垃圾回收的详解

    1.垃圾收集算法的核心思想Java语言建立了垃圾收集机制,用以跟踪正在使用的对象和发现并回收不再使用(引用)的对象.该机制可以有效防范动态内存分配中可能发生的两个危险:因内存垃圾过多而引发的内存耗尽,以及不恰当的内存释放所造成的内存非法引用. 垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配.垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能,因

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

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

  • 老生常谈Java网络编程TCP通信(必看篇)

    Socket简介: Socket称为"套接字",描述IP地址和端口.在Internet上的主机一般运行多个服务软件,同时提供几种服务,每种服务都打开一个Socket,并绑定在一个端口上,不同的端口对应于不同的服务.Socket和ServerSocket类位于java.net包中.ServerSocket用于服务端,Socket是建立网络连接时使用的.连接成功时,应用程序两端都会产生一个Socket实例,通过操作这个实例完成所需会话. Socket常用方法: -int getLocalP

  • 老生常谈java垃圾回收算法(必看篇)

    1.引用计数法(Reference Counting Collector) 1.1算法分析 引用计数是垃圾收集器中的早期策略.在这种方法中,堆中每个对象实例都有一个引用计数.当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1.当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1.任何引用计数器为0的对象实例可以被当作垃圾收集.当一个对象实例被垃圾收

  • 老生常谈Java反射机制(必看篇)

    什么是反射机制 反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作.例如它允许一个java的类获取他所有的成员变量和方法并且显示出来.这个能特定我们不常看到,但是在其他的比如C或者C++语言中很不就存在这个特性.一个常见的例子是在JavaBean中,一些组件可以通过一个构造器来操作.这个构造器就是用的反射在动态加载的时候来获取的java中类的属性的. 主要的类 Class 类的实例表示正在运行的 Java 应用程序中的类和接口.Class没

  • 简单理解Java的垃圾回收机制与finalize方法的作用

    垃圾回收器要回收对象的时候,首先要调用这个类的finalize方法(你可以 写程序验证这个结论),一般的纯Java编写的Class不需要重新覆盖这个方法,因为Object已经实现了一个默认的,除非我们要实现特殊的功能(这 里面涉及到很多东西,比如对象空间树等内容). 不过用Java以外的代码编写的Class(比如JNI,C++的new方法分配的内存),垃圾回收器并不能对这些部分进行正确的回收,这时就需要我们覆盖默认的方法来实现对这部分内存的正确释放和回收(比如C++需要delete). 总之,f

  • Java的垃圾回收机制实例分析

    本文实例讲述了Java的垃圾回收机制.分享给大家供大家参考,具体如下: 一 点睛 当程序创建对象.数组等引用类型实体时,系统都会在堆内存中为之分配一块内存区,对象就保存在这块内存区中,当这块内存不再被任何引用变量引用时,这块内存就变成了垃圾,等待垃圾回收机制进行回收. 垃圾回收机制的特点: 垃圾回收机制只负责回收堆内存中对象,不会回收任何任何物理资源(例如数据库连接,网络IO等资源). 程序无法精确控制垃圾回收的运行,垃圾回收会在合适时候进行垃圾回收.当对象永久性地失去引用后,系统就会在合适时候

  • 老生常谈计算机中的编码问题(必看篇)

    计算机中的编码问题 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是255(二进制11111111=十进制255),如果要表示更大的整数,就必须用更多的字节.比如两个字节可以表示的最大整数是65535,4个字节可以表示的最大整数是4294967295. 一.目前常用的编码 ASCII编码:由于计算机是美国人发明的,因此,最早只有127个字母被编码到计算机里,也就是大小

  • 老生常谈python的私有公有属性(必看篇)

    python中,类内方法外的变量叫属性,类内方法内的变量叫字段.他们的私有公有访问方法类似. class C: __name="私有属性" def func(self): print(C.__name) class sub_C(C): def info(self): print(C.__name)#派生类中不可以访问父类的私有字段 obj=C() obj.func() obj=sub_C() obj.info() 方法.属性的访问于上述方式相似,即:私有成员只能在类内部使用 以上这篇老

  • java文件上传Demo(必看篇)

    说到文件上传我们要做到: 1.引入两个包:commons-fileupload-1.2.1.jar和commons-io-1.3.2.jar 2.将form改为上传文件模式:enctype="multipart/form-data" 3.开始编写相关代码 这里会用到几个关键的类:磁盘文件工厂DiskFileItemFactory : 创建servlet文件上传类:ServletFileUpload 还有几个重要的方法:DiskFileItemFactory类用于将以临时文件形式保存在磁

  • Java基本语法笔记(菜鸟必看篇)

    一.关键字&标识符 1.关键字:被Java语言赋予了特殊含义,用作专门用途的字符串(单词): 2.保留字:现有Java版本尚未使用,但以后版本可能会作为关键字使用.自己命名时要避免使用这些保留字: 3.标识符:凡是自己可以起名字的地方都叫标识符: 4.标识符命名规则(必须遵守): 1.由26个英文字母大小写,0-9,_或$组成: 2.数字不可以开头: 3.不可以使用关键字以及保留字,但能包含关键字以及保留字: 4.Java中严格区分大小写,长度无限制: 5.标识符不能包含空格: 5.Java中的

随机推荐