java的垃圾回收

目录

 

什么是Java的垃圾回收机制

垃圾回收机制的意义

内存泄漏与内存溢出

概念

内存溢出的原因

解决内存溢出的方法

垃圾回收

如何确定哪些对象需要进行回收

什么时候进行回收操作

如何回收

标记-清除算法

标记-整理算法

复制算法

分代算法

新生代

老年代

永久代

垃圾收集器


什么是Java的垃圾回收机制

垃圾回收机制,简称 GC

  • Java 语言不需要程序员直接控制内存回收,由 JVM 在后台自动回收不再使用的内存
  • 提高编程效率
  • 保护程序的完整性
  • JVM 需要跟踪程序中有用的对象,确定哪些是无用的,影响性能

Java垃圾回收与c语言的垃圾回收最大的区别在于:java的垃圾回收由jvm在后台自动回收不在使用的内存,而C语言需要人为的控制内存的回收。

垃圾回收机制的意义

内存也只是一个容器,如果说一直往内存放数据,而不进行管理与清除回收的话,总有一天,这块区域会被完全占满,而且在这个过程中还可能会出现两个问题:内存泄漏与内存溢出。

内存泄漏与内存溢出

概念

内存泄漏:应用程序不再使用对象,但是垃圾回收器无法删除它们,因为它们已被引用。

内存溢出:指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。

大量的内存泄漏的后果就是会造成内存溢出,但是内存溢出不一定是因内存泄漏造成的。

内存溢出的原因

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据; 
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收; 
3.代码中存在死循环或循环产生过多重复的对象实体; 
4.使用的第三方软件中的BUG; 
5.启动参数内存值设定的过小

解决内存溢出的方法

  • 修改JVM启动参数

    (-Xms:设置初始分配大小,   -Xmx:最大分配内存

  • 检查错误日志
  • 对代码进行分析,查找可能会造成内存溢出的地方

垃圾回收

如何确定哪些对象需要进行回收

此处有两种方法来判断:引用计数法和可达性分析算法

引用计数发:给对象添加一个引用计数器,如果有对该对象引用时,那么计数器加1,引用失效时,计数器减1.但是现在jvm并没有使用这个算法,因为不能解决对象间循环引用的问题

可达性分析算法:通过判断对象的引用链是否可达来决定对象是否可以被回收。程序把所有的引用关系看成一张图,由GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索的路径称为引用链,当一个对象到GC Roots没有任何一个引用链时,则认为此对象不可用。

此图中虽然Object5还保留有对6和7的引用,但是已经没有引用链通向GC Roots,所以认为该对象已经没有引用。

在java中可以作为GC Roots的对象:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中 JNI(Native 方法)引用的对象。

什么时候进行回收操作

  • CPU空闲的时候自动进行回收
  • 堆内存满了之后
  • 主动调用System.gc()

如何回收

标记-清除算法

最基础的一种算法,分为两步。第一步进行标记,第二步进行回收。经过分析后对可以回收的对象进行标记,而后回收被标记的对象。

优点:简单易懂

缺点:回收之后会产生不连续的内存碎片,影响存储。

执行如下:

回收之前

执行之后:

标记-整理算法

与标记-清除算法类似,,区别在于对可回收对象回收后还会对存货对象进行整理,不会产生不连续的内存碎片。

执行如下:

回收之前:

回收之后:

复制算法

将内存分为大小相等的两块,每次只使用其中的一块,垃圾收集时,遍历该内存空间内所有的对象,将还存活的对象复制到另一块内存中,然后清除这一块的所有的对象。这样就不用考虑会产生不连续的内存碎片,只是原本的空间会被分为两份,导致可用空间变小,回收频率会增加

执行如下:

回收之前:

回收之后:

分代算法

   分代算法其实并不是一个新的算法,只是不同的对象生命周期不同,故采取不同的算法回收已提高效率。

根据生命周期的不同,将内存划分为:年轻代,老年代,永久代。

新生代

大部分新产生的对象都是存放在新生代中,目的就是快速的回收那些生命周期较短的对象,这种回收也称为Minor GC,使用的算法为复制算法。

新生代又划分为3部分,一个Eden区和两个Survivor区(Survivor1和Survivor2),默认情况下内存大小比例为8:1:1。新产生的大部分对象都是存在于Eden区,当发生回收时,会将存活对象复制到Survivor1中,然后清空Eden区。当Survivor1也存满时,会将Survivor1与Eden区的存活对象复制到Survivor2中,清空Eden与Survivor1。

出现以下两种情况时,则不再将存活对象复制到Survivor2而是老年代:

1)、当Survivor2内存不够存放Eden与Survivor1的存活对象时,会直接复制到老年代

2)、在Survivor中经过多次GC后依然存活的对象,默认是15次。

由于新生代的对象生命周期较短,产生对象的速度也快,所以Minor GC频率高,但是这种方法不会影响到老年代的GC。

老年代

前面说到大部分的新生的对象都是存入到新生代,当然还有例外,那就是较大的新生对象会直接放入老年代而不是新生代,因为新生代与老年代在配置时,新生代的内存会远小于老年代,如果对象较大的话可能放不下。

老年代中存储的对象还有就是经过新生代多次GC后依旧存活的对象,所以老年代中存储的对象生命周期都比较长,故使用的算法为标记清除和标记整理算法,这种回收也称为Full GC。

Full  GC的频率较少,但是Full GC是针对整个堆内存的回收,耗时长。

永久代

JDK8取消了永久代,诞生了元空间。元空间与永久代的本质类似,但是有个最大的区别就是:元空间使用的内存区域不在虚拟机,而是本地空间。

垃圾收集器

新生代使用的:Serial、ParNew、Parallel Scavenge

老年代使用的:Serial Old、Parallel Old、CMS

还有一个堆回收器:G1

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章