1. JVM 分代模型及对象分配流转
1.1. 背景引入
从之前的文章中了解到,想要 Java 程序运行起来,要把相关类加载到方法区,每个线程通过字节码执行引擎、程序计数器和 Java 虚拟机栈来执行字节码指令,存储线程局部变量等,这个过程中产生的对象存放在 Java 堆内存中。
上述只是最基本的流程,如果系统一直运行,会不断产生新的对象,达到指定条件后,出发垃圾回收机制,会回收释放空间。
如果每次达到指定条件,都要去全局判断,也是很耗费性能的。这里 JVM 有一个优化的机制,将堆内存区域划分两种类型,新生代和老年代。
分新生代和老年代的好处是什么呢?
我们代码中有静态常量等等,生存周期会很长的对象,这种对象肯定还有引用,每次垃圾回收都去判断会损耗性能。
如果新生代经过很多次垃圾回收(15次)对象还会存活,JVM 会认为这个对象就是长期存活的对象,会把这种对象放在老年代中,这样下次新生代垃圾回收就不会再继续判断这种对象了,提升了新生代的垃圾回收性能。
1.2. 分代模型:新生代和老年代
通过上面介绍,知道 Java 系统中不同对象生存周期是不同的,根据生存时间划分为新生对象及老年对象。
新生对象数据存放区域称为新生代。
老年对象数据存放区域称为老年代。
最开始对象都在新生代中,躲过多次垃圾回收后,满足指定条件,就会从新生代流转到老年代中。
- 注意:每躲过一次垃圾回收,可以说对象的年龄增加一岁。
新生代垃圾回收,称为Minor GC
,也称为Young GC
,一般方法执行完,出栈后,很对对象就没有引用了,变成垃圾对象,垃圾回收器不会立即进行回收,会运行一段时间,垃圾对象积攒很多,满足一定条件后,才会进行回收,这样减少回收频率,提升整体性能。
老年代垃圾回收,称为Major GC
,也成为Full GC
,当老年代达到某些条件时,也会进行垃圾回收,和新生代垃圾回收的思想方法有区别。
GC Roots
判断对象是否能回收,通过可达性算法来判定,该思想是判断对象都有谁在引用它,然后一层层向上判断,看是否有一个GC Roots。(在JVM规范中,局部变量就是可以作为GC Roots的;静态变量也可以看做是一种GC Roots。)
只要对象被方法的局部变量、类的静态变量给引用了,就不会回收它们。
finalize()
方法的作用
思考:在回收的时候,没有GC Roots引用的对象,一定立马被回收吗?
答:不是的,finalize()
方法可以避免被立即回收。
public class ReplicaManager {
public static ReplicaManager instance;
@Override
protected void finalize() throws Throwable {
ReplicaManager.instance = this;
}
}
如果一个对象要被垃圾回收了,这个对象重写了Object类中的finalize()
方法。
会先尝试调用一下他的finalize()
方法,看是否把自己这个实例对象给某个GC Roots变量。
如果重新让某个GC Roots变量引用自己,则该对象就不会被垃圾回收。
该方法平时很少用,了解即可。
1.3. 永久代及回收
永久代,顾名思义,代表的是“永久”存在的数据,指的就是我们的方法区。
但是这里也会有垃圾回收,并不是永久不回收的。当同时满足以下情况时,会进行垃圾回收。
- 该类的所有实例对象,都已经从 Java 堆内存里被回收。
- 加载这个类的 ClassLoader 已经被回收。
- 对应类的 Class 对象没有任何引用。
1.4. Java 虚拟机栈
当栈内方法栈帧执行完毕后,会出栈,出栈后,方法及对应的局部变量会直接就从内存清理掉了,这里不会有专门的垃圾回收机制。
1.5. Java中对象不同的引用类型
关于引用和垃圾回收的关系,要注意新的概念,就是Java里有不同的引用类型。分别是强引用、软引用、弱引用和虚引用。
比较常用的,是 强引用 和 软引用 ,强引用是绝对不能回收的对象,软引用是对象可有可无,如果内存实在不够了,可以回收掉。
强引用
一个变量引用一个对象,只要是强引用的类型,那么垃圾回收的时候绝对不会去回收这个对象的。
public class Kafka {
public static ReplicaManager replicaManager = new ReplicaManager();
}
软引用
实例对象用一个“SoftReference”软引用类型的对象给包裹起来了,此时这个引用就是软引用了。
正常情况下垃圾回收是不会回收软引用对象的,如果进行垃圾回收之后,发现内存空间还是不足,此时就会把软引用对象给回收掉,即使被变量引用了,也要被回收。
public class Kafka {
public static SoftReference<ReplicaManager> replicaManager =
new SoftReference<ReplicaManager>(new ReplicaManager());
}
弱引用
弱引用就跟没引用是类似的,如果发生垃圾回收,就会把这个对象回收掉。
public class Kafka {
public static WeekReference<ReplicaManager> replicaManager =
new WeekReference<ReplicaManager>(new ReplicaManager());
}
虚引用
虚引用,暂时忽略也行,因为很少用。
1.6. JVM 内存相关的几个核心参数
-Xms:Java堆内存的大小
-Xmx:Java堆内存的最大大小
-Xmn:Java堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了
-XX:PermSize:永久代大小
-XX:MaxPermSize:永久代最大大小
-Xss:每个线程的栈内存大小