目录
什么是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