推荐相关文章:GC算法
1.运行时内存区
要想了解GC垃圾回收机制,首先要了解虚拟机中内存分布以及管理。如下图所示,运行时数据区分为方法区、堆、虚拟机栈、本地方法栈和程序计数器等。
每一块区域解释如下:
方法区:是线程共享的内存区域,用来存储类加载的信息、常量、静态变量、即时编译器编译后的代码等。其中方法区中还有个经常会用到的区域叫做运行时常量池,主要用于存储一些常量,当创建一个常量时,首先会在运行时常量池查看是否有,有则直接使用,否则重新创建。
堆:堆是最大的一块内存区域,也是垃圾回收管理的主要区域,主要用于存放对象实例。
程序计时器:线程私有的,每个线程都会分配一个线程计时器,用来表示当前线程执行的字节码的行号指示器。在多线程中,一个线程执行的时候释放锁,另一个线程执行完,再回来执行前面线程的时候,就是通过程序计时器来获取继续执行的位置。
虚拟机栈:虚拟机栈主要存储基本数据类型变量和引用类型变量。其中与堆的区别就是如:Obj obj=new Object();等号左边则是在虚拟机栈上分配栈区存储引用类型变量的句柄Obj obj,等号右边则是存储对象实例,栈区的句柄是指向堆区的对象实例的,一般通过句柄访问堆区的对象实例。
本地方法栈:与虚拟机栈意义相似,区别在于虚拟机栈用于使Java方法,而本地方法栈则是针对于Native方法服务。
2.内存分配与回收策略
下图所示是堆中内存分配示意图,创建一个对象,首先会在eden区域分配区域,如果内存不够,就会将年龄大的转移到Survivor区,当survivor区域存储不下,则会转移年老代的。对于一些静态变量不需要使用对象,直接调用的,则会被放入永生代。一般来说长期存活的对象最终会被存放到年老代,还有一种特殊情况也会被存放到年老代,就是创建大对象时,比如数据这种需要申请连续空间的,如果空间比较大的,则会直接进入年老代。
在回收过程中,有一个参数比较重要,就是对象的年龄,如果在一次垃圾回收过程中有使用该对象的,则将对象年龄加1,否则减1,当计数为0,则进行回收,如果年龄达到一定数字则进入老生代。总的来说内存分配机制主要体现在对象创建之后是否仍在使用,已经不使用的则回收,继续使用的则对其年龄进行更新,达到一定程度,转移到年老代。
3.垃圾回收算法
1.标记-清除算法
该算法先标记,后清除,将所有需要回收的算法进行标记,然后清除;这种算法的缺点是:效率比较低;标记清除后会出现大量不连续的内存碎片,这些碎片太多可能会使存储大对象会触发GC回收,造成内存浪费以及时间的消耗。
2.复制算法
复制算法将可用的内存分成两份,每次使用其中一块,当这块回收之后把未回收的复制到另一块内存中,然后把使用的清除。这种算法运行简单,解决了标记-清除算法的碎片问题,但是这种算法代价过高,需要将可用内存缩小一半,对象存活率较高时,需要持续的复制工作,效率比较低。--新生代
3.标记整理(压缩)算法
标记整理算法是针对复制算法在对象存活率较高时持续复制导致效率较低的缺点进行改进的,该算法是在标记-清除算法基础上,不直接清理,而是使存活对象往一端游走,然后清除一端边界以外的内存,这样既可以避免不连续空间出现,还可以避免对象存活率较高时的持续复制。这种算法可以避免100%对象存活的极端状况,因此老年代不能直接使用该算法。所以使用标记-清理-压缩算法:
- 结合使用标记清理算法(Mark-Sweep)和标记压缩算法(Mark-Compact)
- 并不是每次标记清理都会执行压缩,而是多次执行GC后,才会执行一次Compact
--用于老年代
4.分代收集算法
分代收集算法就是目前虚拟机使用的回收算法,它解决了标记整理不适用于老年代的问题,将内存分为各个年代,在不同年代使用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率搞,没有额外空间对它进行分配担保,所以只能使用标记清除或者标记整理算法。
5.引用计数法
这也是一个较为朴素的解决方案,对于内存中的每一个对象都保存它们的引用计数,一个对象给一个初始值15,一次不可达减一,一次可达加一,当某个对象的引用计数减为0时,就把它所占用的内存空间回收。 --应用在edn区