内存分区
jdk1.8之前:堆中:新生区、老年区;方法区中:永久区
jdk1.8之后:堆中:新生区、老年区;内存中:元空间
GC垃圾回收,主要是在新生区和老年区。
详解
新生区、老年区
新生区分为:伊甸园区(Eden Space)、幸存区0区、幸存区1区。
- 伊甸园区内存满时,触发一次轻GC,将GC后存活的对象存入幸存区0区和1区。
- 幸存区0区和1区存满时,出发一次重GC,将GC后存活的对象存入老年区。
永久区(元空间)
用来存放JDK自身携带的Class对象。Interface元数据,存储的是Java运行的一些环境或类信息,这个区域不存在垃圾回收!只有关闭JVM虚拟机才会释放这个区域的内存。
- jdk1.6之前:永久代,常量池在方法区中;
- jdk1.7:永久代,但提出 “去永久代” 概念,将常量池放在堆中;
- jdk1.8:无永久代,常量池在元空间;
将永久代改为元空间的原因,详见:JDK1.8之后的JVM内存模型
调优参数
-Xms:初始堆大小,默认1/64
-Xmx:最大堆大小,默认1/4
-XX:+PrintGCDetails :打印垃圾回收细节
-XX:+HeapDumpOnOutOfMemoryError :在内存溢出时生成Dump文件
常用GC算法
标记清除法
标记:对活着的对象进行标记。
清楚:对没有标记的对象进行清除。
优点: 不需要额外空间。
缺点: 两次扫描严重浪费时间,会产生内存碎片。
标记压缩法
对标记清除法的基础上添加了压缩步骤。
压缩:再次扫描,向一端移动存活的对象。
优、缺点同标记清除法,但不会产生内存碎片。
复制算法
将两个幸存区分为from和to。GC流程如下:
1.将Eden Space中存活的对象移到to区中;
2.将from中数据复制到to区;
3.清空from中数据;
4.将原from区设置为to区,原to区设置为from区。(一次GC完成)
5.当一个对象经历了15次GC,则转移至老年区。
优点: 没有内存碎片。
缺点: 浪费了一半幸存区的空间。
最佳使用场景: 对象存活度较低,因此用于轻GC。
引用计数法
为每个对象分配一个计数器,当对象被引用时计数加1,结束引用时计数减1,GC时清除所有引用计数为0的对象。
可达性分析算法
采用引用计数法来判断一个对象是否需要GC时,可能会出现循环引用的情况。例如:
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
object1.object = object2;
object2.object = object1;
object1 = null;
object2 = null;
此时,即使object1和object2为null,但计数器不为1,触发不了GC。
可发行分析则是通过一些“GC Roots”对象作为起点,将引用的对象向下链接,以此形成一条条的“引用链”。当进行GC时,会判断一个对象有没有可到达的“GC Root”,如果没有,则表明该对象没有引用,进行清除。
可作为“GC Roots”的对象包含以下几种:
- 虚拟机栈中引用的对象:当程序正常创建一个对象时,对象会在堆中开辟一块空间,同时将这个空间的地址作为引用保存到虚拟机栈中。如果对象生命周期结束,则该引用会出栈,因此如果虚拟机栈中有引用,则说明该对象存活。大部分对象都以此为GC Root。
- 方法区中静态属性引用或常量引用的对象:即使用了static、final关键字。
- 本地方法栈中引用的对象:即使用了native关键字。