-
一、概念
这里先科普一下“JVM的运行模式”、“Java中的引用类型”2个概念:
-
1.1)JVM的运行模式
解释模式、编译模式、混合模式三种,其中的区别如下:
- 1)解释模式:
只能使用解释器,执行一行JVM字节码就编译一行为机器码;
特点:启动快;
适用场景:只需要执行部分代码,且大多数情况下代码只会执行一次;
- 2)编译模式:
只使用编译器,现将所有JVM字节码一次编译为机器码,然后一次性执行所有机器码;
特点:启动慢,但后期执行快,因为编译成机器码后容量是JVM字节码的十倍以上或更多;
适合代码会被反复使用的场景;
- 3)混合模式:
依然使用解释模式执行代码,单对于一些“热点”代码采用编译模式执行,JVM一般采用混合模式执行代码;
特点:是JVM采用的默认方式。开始还是解释执行,对于部分“热点”代码采用编译模式执行,这些热点代码对应的机器码会被缓存,下次再执行无需再编译,就是我们常见的JIT(Just In Time Compiler)即时编译技术;
再即时编译过程中JVM可能会对我们的代码做一些优化,比如对象逃逸分析等;
-
1.2)Java中的引用类型
1)强引用:普通的变量引用
public static User user = new User();
public Integer i = 0;
2)软引用:将对象用SoftReference软引用类型的对象包裹,正常情况下不会被回收,但是GC昨晚后发现释放不出空间存放新的对象,就会把这些软引用的对象回收掉。软引用可以用来实现内存敏感的高速缓存;
public static SoftReference<User> user = new SoftReference<User>(new User());
3)弱引用:很少使用,GC会直接回收掉;
4)虚引用:也成为幽灵应用或者幻影引用,他是最弱的一种引用关系,几乎不用;
-
二、逃逸分析
逃逸分析是在即时编译过程中产生的;
就是分析对象动态作用域,当一个对象在方法中被定义后,他可能被外部方法所引用,例如作为调用参数传递到其他地方;
通俗的讲,就是对象不会跳出出方法、作用域范围的对象,JVM会将这个对象直接分配到栈帧内存中,不会放到堆上,但是如果分配的对象的内存栈帧没有空间存放的情况下还是会分配到堆上;
-
三、JVM内存分配与回收
-
3.1)内存回收的类型
Minor GC/Young GC:是发生在新生代的垃圾收集动作,Minor GC非常繁琐,回收速度一般也比较快。
Major GC/Full GC:一般是回收老年代、年轻代、方法区的垃圾,Major GC的速度一般会比Minor GC慢10倍以上;
-
3.2)进入老年代的条件:
大对象直接进入老年代:超过JVM参数“-XX:PretenureSizeThreshold”设置的值;这个参数单位是“字节”
长期存活的对象进入老年代:虚拟机会给每个对象分配年龄计数器,每次通过MinorGC的时候都会进行累加,当累加到一定程度(默认:15次)还没有被销毁,就会被推入老年代,可以通过“-XX:MaxTenuringThreshold”来进行设置;
MinorGC后存活的对象Survivor区放不下的情况,会进入老年代,注意这里是:会把存活的对象部分推入老年代,部分可能还会在Survivor区;
-
3.3)对象动态年龄判断(这里提到的参数数据可能会因本地环境而异同)
MinorGC执行时从Eden区分配到Survivor区的时候,假设现在有多个对象需要移入,JVM会根据对象的年龄进行分组,并求出每个组的总和,然后每个年龄对象相加,如果相加结果大于Survivor区内存大小的50%,那么大于等于这批对象年龄最大值的对象就直接进入老年代;
注意:是在MinorGC执行之后才开始动态年龄判断
举例:
现有:年龄1对象、年龄2对象、年龄3对象、年龄4对象、....、年龄N对象;
如果:
1、年龄1对象+年龄2对象<Survivor.size/50%(这里的50%是JVM内部设置的):不做任何处理;
2、年龄1对象+年龄2对象+年龄3对象>=Survivor.size/50%;这时会将年龄3对象、年龄4对象、....、年龄N对象全部移到老年代;
设计意图:是希望那些可能长期存活的对象尽早进入老年代;
-
3.4)老年代空间分配担保机制
再每次执行MinorGC之前,JVM都会计算一下老年代的剩余可用空间;
对老年代的空间与年轻代中所有对象的大小进行比较;
如果老年代剩余可用空间小于年轻代中所有对象(包括垃圾对象)的大小,查看是否配置了JVM老年代担保参数:-XX:-HandlePromotionFailure如果没有,直接执行FullGC;
如果设置了JVM担保参数:-XX:-HandlePromotionFailure,判断老年代剩余可用空间是否小于每一次MinorGC后进入老年代的对象的平均大小;
如果小于进入老年代的对象的平均大小,执行FullGC,否则还是执行MinorGC;
总结:在执行MinorGC之前有可能会直接触发FullGC;
JDK1.8中参数-XX:-HandlePromotionFailure是默认开启的;
-
四、如何判断对象是否被回收
判断对象是否被回收的方法有“引用计数器方法”及“可达性分析算法”2种;
-
4.1)引用计数器方法:
此方法已经很陈旧了,目前基本不再使用;这种方法不能处理内部交叉引用的情况,例如:
1 A a = new A(); 2 B b = new B(); 3 //A对象中存在一个B对象的属性或者Object 4 a.data = b; 5 //B对象中存在一个A对象的属性或者Object 6 b.data = a; 7 a=null; 8 b=null;
这里引用计数器就不能识别A对象中data属性对B的引用,B对象中data属性对A的引用,所以这里垃圾回收是不会回收A与B对象的;
-
4.2)可达性分析算法:
通过GCRoots根进行标注,标注有效对象
GCRoots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等;
大致的步骤:
1、栈内存的栈帧中找所有的变量、符号等,是否存在引用对象,递归查找,一直找到最后一个元素;
2、将找出来的所有对象标记为非垃圾对象;
-
五、如何判断一个类是无用的类
1)该类所有的实例都已经被回收,也就是Java堆中不存在任何该类的任何实例;
2)加载该类的ClassLoader已经被回收;
3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何对方通过发射访问该类的方法;