03 JVM的垃圾回收机制

1、前言

理解JVM的垃圾回收机制(简称GC)有什么好处呢?作为一名软件开发者,满足自己的好奇心将是一个很好的理由,不过更重要的是,理解GC工作机制可以帮助你写出更好的Java程序。

在学习GC前,你应该知道一个技术名词:“stop-the-world” ,无论你选择哪种GC算法,“stop-the-world”都会发生。“stop-the-world”意味着JVM停止应用程序,而去进行垃圾回收。当“stop-the-world”发生时,除了进行垃圾回收的线程,其他所有线程都将停止运行。被中断的任务将在GC任务完成后恢复执行。GC调优往往意味着减少“stop-the-world”的时间。

2、分代垃圾收集机制

在HotSpot虚拟机中,将内存分为 年轻代(young generation)、老年代(old generation) 和 永久代(permanent generation)

我们看一下这幅图:

年轻代: 新创建的对象都存放在这里。因为大多数对象很快变得不可达,所以大多数对象在年轻代中创建,然后消失。当对象从这块内存区域消失时,我们说发生了一次“minor GC”。

老年代: 没有变得不可达,存活下来的年轻代对象被复制到这里。这块内存区域一般大于年轻代。因为它更大的规模,GC发生的次数比在年轻代的少。对象从老年代消失时,我们说“major GC”(或“full GC”)发生了。

永久代: 永久代(permanent generation) 也称为“方法区(method area)”,它存储class对象和字符串常量。所以这块内存区域绝对不是永久的存放从老年代存活下来的对象的。发生在这里的垃圾回收也被称为major GC。

3、垃圾收集算法

垃圾收集算法如下:

  • 年轻代-复制算法
  • 老年代-标记清除算法
  • 老年代-标记整理算法
  • 永久代-方法区回收

年轻代-复制算法

该算法的核心是将可用内存按容量划分为大小相等的两块,每次只用其中一块,当这一块的内存用完,就将还存活的对象复制到另外一块上面,然后把已使用过的内存空间一次清理掉。这使得每次只对其中一块内存进行回收,分配也就不用考虑内存碎片等复杂情况,实现简单且运行高效。如下图:

老年代-标记清除算法

该算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象(可达性分析),在标记完成后统一清理掉所有被标记的对象。如下图:

该算法会有以下两个问题:

  1. 效率问题:标记过程和清除过程的效率都不高;
  2. 空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致在运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集。

老年代-标记整理算法

标记清除算法会产生内存碎片,而复制算法需要有额外的内存担保空间,于是针对老年代的特点,又有了标记整理算法。标记整理算法的标记过程与标记清除算法相同,但后续步骤不再对可回收对象直接清理,而是让所有存活的对象都向一端移动,然后清理掉这一端边界以外的内存。如下图:

永久代-方法区回收

在方法区进行垃圾回收一般“性价比”较低,因为在方法区主要回收两部分内容:废弃常量无用的类

回收废弃常量与回收其他年代中的对象类似。

但是判断一个类是不是无用的类的条件则相当苛刻:

  • 该类所有的实例都已经被回收,Java堆中不存在该类的任何实例;
  • 该类对应的Class对象没有在任何地方被引用;
  • 加载该类的ClassLoader已经被回收;

但即使满足以上条件也未必一定会回收,Hotspot VM还提供了-Xnoclassgc参数控制(关闭CLASS的垃圾回收功能)。因此在大量使用动态代理、CGLib等字节码框架的应用中一定要关闭该选项,开启VM的类卸载功能,以保证方法区不会溢出。

4、垃圾回收的两个重要方法

System.gc()方法 和 finalize()方法

System.gc()方法

使用System.gc() 可以不管JVM使用的是哪一种垃圾回收的算法,都可以请求Java的垃圾回收。在命令行中有一个参数-verbosegc可以查看Java使用的堆内存的情况,它的格式如下:java -verbosegc classfile 由于这种方法会影响系统性能,不推荐使用,所以不详细介绍。

finalize()方法

JVM垃圾回收器在回收一个对象之前,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java提供了缺省机制来终止该对象从而释放资源,这个方法就是finalize() 。它的原型为:protected void finalize() throws Throwable 在finalize() 方法返回之后,对象消失,垃圾收集开始执行。原型中的throws Throwable 表示它可以抛出任何类型的异常。

之所以要使用finalize(),是因为存在着垃圾回收器不能处理的特殊情况。例如:

  1. 由于在分配内存的时候可能采用了类似 C语言的做法,而非Java通常的new 。这种情况主要发生在 native 方法中,比如 native 方法调用了C/C++malloc()函数来分配存储空间,除非调用 free() 函数,否则这些内存空间将不会得到释放,那么这个时候就可能造成内存泄漏。但是由于 free() 是C/C++中的函数,所以 finalize() 中可以用本地方法来调用它。以释放这些“特殊”的内存空间。
  2. 或者是打开的文件资源,这些资源不属于垃圾回收器的回收范围。

5、触发GC(Garbage Collector)的条件

  1. GC在优先级最低的线程中运行,一般在应用程序空闲即没有应用线程在运行时被调用。但下面的条件例外。
  2. Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制调用GC线程。若GC一次之后仍不能满足内存分配,JVM会再进行两次GC,若仍无法满足要求,则JVM将报“out of memory”的错误,Java应用将停止。

6、减少GC开销的措施

  1. 不要显式调用System.gc() 。此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。大大的影响系统性能。
  2. 尽量减少临时对象的使用。临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。
  3. 对象不用时最好显式置为Null。一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。
  4. 尽量使用StringBuffer,而不用String来累加字符串。由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如 Str5=Str1+Str2+Str3+Str4; 这条语句执行过程中会产生多个垃圾对象,因为每次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
  5. 能用基本类型如 int, long 就不用包装类型Integer, Long。基本类型变量占用的内存资源比包装类型占用的少得多,如果没有必要,最好使用基本变量。
  6. 尽量少用静态对象变量。静态变量属于全局变量,不会被GC回收,它们会一直占用内存。
  7. 分散对象创建或删除的时间。集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。

7、几种垃圾收集器

在JDK7中,有5种垃圾收集器:

  • Serial收集器
  • Parallel收集器
  • Parallel Old收集器 (Parallel Compacting GC)收集器
  • Concurrent Mark & Sweep GC (or “CMS”)收集器
  • Garbage First (G1) 收集器

其中,Serial 收集器一定不能用于服务器端。这个收集器类型仅应用于单核CPU桌面电脑。使用Serial收集器会显着降低应用程序的性能。



本文永久更新地址:https://github.com/nnngu/LearningNotes/blob/master/JVM/03%20JVM%E7%9A%84%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6.md

原文地址:https://www.cnblogs.com/nnngu/p/8490393.html

时间: 2024-11-05 14:46:10

03 JVM的垃圾回收机制的相关文章

JVM的垃圾回收机制详解和调优

JVM的垃圾回收机制详解和调优 gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集操作. 1.JVM的gc概述 gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集操作. 在充分理解了垃圾收集算法和执行

JVM的垃圾回收机制 总结(垃圾收集、回收算法、垃圾回收器)

如果想了解Java内存模型参考:jvm内存模型-和内存分配以及jdk.jre.jvm是什么关系(阿里,美团,京东) 相信和小编一样的程序猿们在日常工作或面试当中经常会遇到JVM的垃圾回收问题,有没有在夜深人静的时候详细捋一捋JVM垃圾回收机制中的知识点呢?没时间捋也没关系,因为小编接下来会给你捋一捋. 一. 技术背景你要了解吧 二. 哪些内存需要回收? 2.1 引用计数算法 2.1.1 算法分析 2.1.2 优缺点 2.1.3 是不是很无趣,来段代码压压惊 2.2 可达性分析算法 2.3 Jav

JVM:垃圾回收机制和调优手段

转载请注明出处: jiq?钦's technical Blog - 季义钦 引言: 我们都知道JVM内存由几个部分组成:堆.方法区.栈.程序计数器.本地方法栈 JVM垃圾回收仅仅针对公共内存区域即:堆和方法区进行. 本文主要讨论两点,一是垃圾回收策略,二是调优的方法. 一.垃圾回收机制 1.1 分代管理 将堆和方法区按照对象不同年龄进行分代: u  堆中会频繁创建对象,基于一种分代的思想,按照对象存活时间将堆划分为新生代和旧生代两部分,我们不能一次垃圾回收新生代存活的对象就放入旧生代,而是要经过

JVM 及 垃圾回收机制原理

JVM Java 虚拟机 Java 虚拟机(Java virtual machine,JVM)是运行 Java 程序必不可少的机制.JVM实现了Java语言最重要的特征:即平台无关性.原理:编译后的 Java 程序指令并不直接在硬件系统的 CPU 上执行,而是由 JVM 执行.JVM屏蔽了与具体平台相关的信息,使Java语言编译程序只需要生成在JVM上运行的目标字节码(.class),就可以在多种平台上不加修改地运行.Java 虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行.因此实

JVM的垃圾回收机制(GC)

1.什么是垃圾回收                程序运行会产生各种各种的数据,那么这些数据存在于内存当中,这些数据不可能是永久存在的,无效的资源对象需要进行垃圾回收,释放内存            2.不同的编程语言都有GC垃圾回收                java语言自带GC垃圾回收器,并且有JVM自动进行垃圾回收,程序员主要关注代码实现,不关注垃圾回收 System.gc();                C/C++语言当中,程序员new一个对象,相当于申请了一块内存,如果需要释放

jvm的垃圾回收机制

jvm的垃圾(GC)回收机制,我就想java的垃圾不是jvm自己处理吗,不用我们去像过去C,C++似的需要我们程序员来手动处理,这也是jvm的好处,但话是这么说,我却没有弄明白,jvm是怎么样来进行处理的,然后我就百度了一下看了看,发现有四大处理器 1) 标记-清除收集器 Mark-Sweep 2) 复制收集器        Copying 3) 标记-压缩收集器 Mark-Compact 4) 分代收集器 Generational 我们可以来以图解的方式看看各个处理器的作用以及不足之处: 1)

Java虚拟机(JVM)与垃圾回收机制(GC)的详解

一.JVM结构 根据<java虚拟机规范>规定,JVM的基本结构一般如下图所示: 从左图可知,JVM主要包括四个部分: 1.类加载器(ClassLoader):在JVM启动时或者在类运行时将需要的class加载到JVM中.(右图表示了从java源文件到JVM的整个过程,可配合理解. 关于类的加载机制,可以参考http://blog.csdn.net/tonytfjing/article/details/47212291) 2.执行引擎:负责执行class文件中包含的字节码指令(执行引擎的工作机

扒一扒JVM的垃圾回收机制,下次面试你准备好了吗

一. 技术背景你要了解吧 按照套路是要先装装X,谈谈JVM垃圾回收的前世今生的.说起垃圾回收(GC),大部分人都把这项技术当做Java语言的伴生产物.事实上,GC的历史比Java久远,早在1960年Lisp这门语言中就使用了内存动态分配和垃圾回收技术.设计和优化C++这门语言的专家们要长点心啦~~ 二. 哪些内存需要回收 猿们都知道JVM的内存结构包括五大区域:程序计数器.虚拟机栈.本地方法栈.堆区.方法区.其中程序计数器.虚拟机栈.本地方法栈3个区域随线程而生.随线程而灭,因此这几个区域的内存

005、JVM的垃圾回收机制是用来干嘛的?为什么要垃圾回收?

本文是个人内容总结,详细内容扫描二维码 1.前文回顾 脑子里一定要有一个会动的图: 1.启动一个线程执行业务代码(执行main方法就是开启一个main线程): 2.线程对应的程序计数器PC来记录程序执行到哪行字节码指令(线程与PC是1:1关系): 3.调用方法时会创建一个“栈帧”,放入线程对应的栈中(线程与栈.方法和栈帧都是1:1关系) 4.代码运行中创建的对象放在java堆内存(堆与JVM进程是1:1关系,堆是所有进程共享) 2.对象的分配与引用 1.启动main线程并创建与其对应的栈(称M栈