简述垃圾回收机制:
在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机JVM自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
一、垃圾回收机制具有以下的特点:
1、 垃圾回收机制只负责回收堆内存,不会回收任何物理资源
2、 程序无法精确控制垃圾回收的进行,会在合适的时候进行
3、 在垃圾回收机制回收的任何对象之前,总会先调用它的finalize()方法
finallize() 是一个 java.lang.Object 里的方法,所以所有的对象都有该方法。默认的实现没有做任何事情。这个方法将在这个对象已经没有被引用,垃圾收集决定回收它的时候调用。因此这里的代码没有保证会被执行,所以这个方法不能被用于执行实际的功能。相反,它被用来清理资源,例如文件的引用。一个对象中,这个方法只会被JVM调用一次。
注意:垃圾回收回收的是无任何引用的对象占据的内存空间而不是对象本身
二、对象在内存中的状态
三、垃圾回收机制中的算法
基础:
java中的内存是由java虚拟机自己去管理的,java的内存分配分为两个部分,一个是数据堆,一个是栈
堆内存用来存放由new创建的对象和数组,在堆中分配的内存由java虚拟机的自动垃圾回收器来管理;
栈用来存放类的信息的,它和堆不同,运行期内GC不会释放空间,当超过变量的作用域后,java会自动释放掉为该变量所分配的内存空间:
1、如果程序声明了static的变量,就直接在栈中运行的,进程销毁了,不一定会销毁static变量。
2、在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配
为什么我们要分别用到堆和栈:
堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。缺点就是要在运行时动态分配内存,所以存取速度较慢。
栈的优势是存取速度比堆要快,缺点是存在栈中的数据大小与生存期必须是确定的所以无灵活性。
为什么java需要垃圾回收机制?
System.gc()即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。
如果你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问,该块已分配出来的内存也无法再使用,随着服务器内存的不断消耗,而无法使用的内存越来越多,系统也不能再次将它分配给需要的程序,产生泄露。一直下去,程序也逐渐无内存使用,就会溢出。
我们有哪些方法知道对象有没有被引用呢?
判断一个对象是否存活即被引用有两种方法:
1. 引用计数法
所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收.
引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象A引用对象B,对象B又引用者对象A,那么此时A,B对象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。2.可达性算法(引用链法)
该算法的思想是:从一个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。
在java中可以作为GC Roots的对象有以下几种:(略)
- 虚拟机栈中引用的对象
- 方法区类静态属性引用的对象
- 方法区常量池引用的对象
- 本地方法栈JNI引用的对象
虽然这些算法可以判定一个对象是否能被回收,但是当满足上述条件时,一个对象不一定会被回收。
当一个对象不可达GC Root时,这个对象并 不会立马被回收,而是处于一个死缓的阶段,若要被真正的回收需要经历两次标记;
如果对象在可达性分析中没有与GC Root的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者已被虚拟机调用过,那么就认为是没必要的。
如果该对象有必要执行finalize()方法,那么这个对象将会放在一个称为F-Queue的对队列中,虚拟机会触发一个Finalize()线程去执行,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完,这是因为如果finalize()执行缓慢或者发生了死锁,那么就会造成F-Queue队列一直等待,造成了内存回收系统的崩溃。GC对处于F-Queue中的对象进行第二次被标记,这时,该对象将被移除”即将回收”集合,等待回收。
java中垃圾收集的方法有哪些?
- 标记-清除:
这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:1.效率不高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次GC动作。- 复制算法:
为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。
于是将该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大那份内存叫Eden区,其余是两块较小的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满,就将对象复制到第二块内存区上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制复制到老年代中。(java堆又分为新生代和老年代)- 标记-整理
该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。- 分代收集
现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。
java垃圾回收机制的优缺点?
优点:
开发人员无须过多地关心内存管理,而是关注解决具体的业务。虽然内存泄漏在技术上仍然是可能出现的,但不常见。
GC 在管理内存上有很多智能的算法,它们自动在后台运行。与流行的想法相反,这些通常比手动回收更能确定什么时候是执行垃圾回收的最好时机。
缺点:
当垃圾回收发生时将影响程序的性能,显著地降低运行速度甚至将程序停止。所谓 “Stop the world” 就是当垃圾回收发生的时候应用程序的其他任务都将被冻结。对于应用程序的要求来说,这将是不可接收的,虽然 GC 调优可以最小化甚至消除这个影响。
虽然GC有很多方面可以调优,但你不能指定应用程序在何时怎样执行GC。
补充:为什么GC要分代?
大多数应用程序都有大量的生命周期短暂的对象。在一次GC中分析程序的所有对象将非常慢而且耗时,因此将“短命”的对象分开处理以快速收集它们就非常有必要了,这样就产生了代际GC。