(自己画的一个JAVA内存图)
方法区:类的信息、常量、静态,永久区 (Full GC还是会回收的)
堆内存:使用new创建的对象、定义的数组
JAVA栈:基本数据类型,局部变量,线程私有的
本地方法栈:JAVA语言调用外部语言(C语言)
程序计数器是一块较小的内存空间,可以看做是当前线程的字节码的行号指示器。
垃圾回收机制:JVM不定时去堆内存回收不可达的对象,较频繁的去新生代回收。
不可达:对象没有被引用或者没有存活。(如果将对象置为null,相当于提示JVM,我可以被回收啦)
finalize():在垃圾回收之前,做一些清理的工作
System.gc():提示GC进行垃圾回收,但是不是立即进行回收;GC是守护线程,优先级较低,跟随主线程的结束而结束。
堆内存的划分
1、新生代:刚出生的对象
1.1 eden区
1.2 S0区(from区)
1.3 S1区(to区)
新出生的对象,先存放在eden区,然后经过S0区、S1区,若一个对象被统计使用的次数超过15次,则将从新生代晋级到老年代区(提前是比较活跃的对象,也有在半路就被回收走的)
其中S0区喝S1区大小相同,主要用于复制算法的交换,减少碎片化(祥见下述垃圾回收机制之复制算法)。
2、老年代:比较稳定、经常被引用的对象
新老在堆内存的占比为1:2
在对象的晋升之路,主要是判断该对象是否还存活,下面说一下判断的算法
如何判断对象是否存活之引用计数法
GC线程在不定时回收时,若一个对象正在被引用,则次数加1;若没有被引用,次数减1。如果次数为0,则被认为不可达对象,将被回收。
已经被淘汰掉了,因为它无法处理闭环依赖的引用,导致这样的对象无法被回收。
如何判断对象是否存活之根搜索法
如果对象和GC Roots有直接或间接的关联,就认为是可达对象。
其中GC Roots是什么呢?
1、虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象;
2、方法区中类静态属性引用的对象;
3、方法区中常量引用的对象;
4、本地方法栈中引用的对象。
例如:
方法区、本地栈和虚拟机栈中的对象可以称为GC Roots,在堆内存中object1、object4、object5和object6都和GC Roots有着直接或间接的联系,则认为它们是可达对象,而object2和object3之间虽有引用关系,但是被认为是不可达对象。
垃圾回收机制之标记清除算法
顾名思义,就是通过上面两个算法判断对象是否存活后,相应的给它们做一个标记,例如,存活记为0,不存活记为1。
但是该方法存在很大的bug,就是内存碎片化
垃圾回收机制遍历堆内存中标识为不可达对象,然后进行逐个清理,存在明显的缺点就是清理效率低,删除不够连贯,容易造成内存碎片化。
该算法一般用在堆内存中的老年代区域,因为此处清理不用太频繁,而且能够解决循环依赖问题。
垃圾回收机制之复制算法
上面说到在堆内存的新生代区域,划分为三个区:eden区、s0区和s1区
模拟一下对象在新生代移动的过程:
1、新生对象一开始都会存放在eden区
2、垃圾回收机制发现Object1和Object2经常被引用时,将其移动至s0区
3、此时垃圾回收机制发现Object1不再被引用,不再存活,这时将s0区其他对象复制到s1区,并清除s0区
4、又新创建一个Object3,放在eden区
5、垃圾回收机制发现Object3经常被引用,将其移动至s1区
6、垃圾回收机制发现Object2不再被引用,不再存活,这时将s1区其他对象复制到s0区,并清除s1区
所以:s0区和s1区大小相同,有一个必须为空的,目的就是为了存放下一次复制,能够解决碎片化的问题,批量删除效率较高,但是浪费空间。
该算法一般用在新生代区域。
垃圾回收机制之标记压缩算法
此算法是在标记清楚算法之上,解决内存碎片化问题。方法很简单,就是在原来的基础上,将可达和不可达对象排序后划分在不同的区域,在清理时一次性清除所有不可达对象。
其中在每个区域中,对象的排序有三种方式:
1、任意顺序:不考虑原先对象的排列顺序,也不考虑对象之间的引用关系,随意移动对象;
2、线性顺序:考虑对象的引用关系,例如Object1对象引用了Object2对象,则尽可能将Object1和Object2移动到一起;
3、滑动顺序:按照对象原来在堆中的顺序滑动到堆的一端。
垃圾回收机制之分代算法
整合上述算法,在新生代使用复制算法,在老年代使用标记算法。