Java 垃圾回收机制(GC)简述

1. 既有 GC 机制,为什么还会有内存泄露的情况

 理论上 Java 因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是 Java 被广泛使用于服务器端编程的一个重要原因)。然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被 GC 回收,因此也会导致内存泄露的发生。
 例如 hibernate 的 Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。

2. 对于 Java 的 GC 哪些内存需要回收

  • 内存运行时 JVM 会有一个运行时数据区来管理内存。主要包括 5 大部分:程序计数器(Program CounterRegister)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap).
  • 而其中程序计数器、虚拟机栈、本地方法栈是每个线程私有的内存空间,随线程生亡。如栈中每个栈帧中分配多少内存基本上在类结构确定是哪个时就已知了,因此这 3 个区域的内存分配和回收都是确定的,无需考虑内存回收的问题。
  • 但方法区和堆就不同了,一个接口的多个实现类需要的内存可能不一样,只有在程序运行期才知道会创建哪些对象,这部分内存的分配和回收都是动态的,GC 主要关注的是这部分内存。
  • 总而言之,GC 主要进行回收的内存是 JVM 中的方法区和堆;

3. Java 的 GC 什么时候回收垃圾

如何判断一个对象已经死去?

1.1. 引用计数法:最主要因为该算法无法解决对象之间的相互循环引用的问题,Java语言没有选用引用计数算,这里不多做介绍
2.1. 根搜索算法(GC Roots),Java实际上使用的是GC Roots算法。在Java语言里,可作为GC Roots的对象包括下面几种:

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

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

  • 方法区中的常量引用的对象

  • 本地方法栈中JNI(一般说的Native方法)的引用的对象

3.1. 判断一个“无用的类”呢?需要同时满足下面三个条件才能算是“无用的类”

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。

  • 加载该类的ClassLoader已经被回收

  • 该类对应的java.lang.Class对象没有任何地方被引用,无法再任何地方通过放射访问该类的方法

3. 内存溢出么原因有哪些?解决方法

常见的有以下几种:

  1. 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;

  2. 集合类中有对对象的引用,使用完后未清空,使得 JVM 不能回收;

  3. 代码中存在死循环或循环产生过多重复的对象实体;

  4. 使用的第三方软件中的 BUG;

  5. 启动参数内存值设定的过小;

内存溢出的解决方案:

  1. 第一步,修改 JVM 启动参数,直接增加内存。(-Xms,-Xmx 参数一定不要忘记加。)

  2. 第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。

  3. 第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

  4. 第四步,使用内存查看工具动态查看内存使用情况


重点排查以下几点:

  • 检查对数据库查询中,是否有一次获得全部数据的查询。一般如一次取十万条记录到内存,可能引起内存溢出。这问题比较隐蔽,上线前数据库数据少,不容易出问题,上线后,数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。

  • 检查代码中是否有死循环或递归调用。

  • 检查是否有大循环重复产生新对象实体。

  • 检查 List、MAP 等集合对象是否有使用完后,未清除的问题。List、MAP 等集合对象会始终存有对对象的引用,使得这些对象不能被 GC 回收。

年轻代-老年代-持久代

  • 所有新生成对象首先都是放在年轻代的。分三个区。一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需注意,Survivor的两个区是对称的,没先后关系,同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。且,Survivor区总有一个是空的。同时根据程序需要,Survivor区可配置为多个的(多于两个),可增加对象在年轻代中的存在时间,减少被放到年老代的可能。
  • 年老代:
    在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
  • 持久代:
    用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

什么时候GC?


  1. 程序调用System.gc时可以触发;
  2. 系统自身来决定GC触发的时机。

新生代:eden满了minor gc.
老年代:升入老年代的对象大于老年代剩余空间full/major gc
调用System.gc时,系统建议执行Full GC,但是不必然执行
老年代空间不足

对什么东西?

从GC root搜索不到,且经过第一次标记、清理后,仍然没有复活的对象。

做什么?

当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收
新声代:做的是复制清理,进行由Eden区、from survivor区向to survivor区复制时
老年代:做的是标记清理/整理

你可能感兴趣的:(java,Java)