JAVA | JVM笔记(三)

149 阅读3分钟

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

垃圾的回收

JVM本身自带垃圾回收机制,一个后台自动运行的线程

思考题 方法区内会不会进行垃圾回收? 会! 不过有条件

  • 首先该类的所有实例对象都已经从Java堆内存里被回收
  • 其次,加载这个类的ClassLoader已经被回收掉
  • 最后,对该类的Class对象没有任何引用

满足三个条件就可以回收了 / 每个线程都有Java虚拟机栈,里面也有很多局部变量等,这个虚拟机栈需要回收吗? 不会回收,出栈即清空数据

什么样的对象需要回收? -- 垃圾 怎样才能算为垃圾对象? -- 没有任何一个局部变量、静态变量、常量指向它 JVM就会定期清理这些垃圾对象

JVM的分代模型

image.png

年轻代(新生代)
存放着很快被回收的对象 大部分的正常对象,都是优先在新生代分配内存的,而对象躲过10多次(15次)垃圾回收,就能成为一个老年人了,会被认为是一个长期存活的对象,就转移到老年代中。
一旦新生代内存空间不够时,就会引起 Minor GC 回收垃圾

进入老年代的机会

  • 躲过15次GC之后进入老年代 (-XX:MaxTenuringThreshold 设置进入老年代的年龄)
  • 动态对象年龄判断 大致规则,在Survivor区域内,一批对象的总大小大于Survivor区域的内存大小50%时,就会把大于这批对象年龄最大的对象移到老年代中去。一批对象(年龄1+年龄2+年龄n > 50%), 年龄> n 的移动到老年代。
  • 大对象直接进入老年代 (-XX:pretenureSizeThreshold 设置进入老年代的字节数)
  • Minor GC后存活对象总内存大小 > Survivor内存大小,移到老年代中
  • 老年代空间分配担保规则
    预防Survivor存活下来的对象大于老年代的可用内存空间
    步骤一:看“-XX:-HandlePromotionFailure”的参数是否设置,判断老年代大小是否大于新生代Minor GC 全部存活的对象大小,如果老年代大小小于了,下一步
    步骤二:如果设置了,判断之前每一次MinorGC后进入老年代的平均大小(假如平均大小为10MB,而老年代可用内存>10MB);
    步骤三:就有下面三种可能
第一种(Minor GC 过后,剩余存活对象大小 < Survivor 可用内存大小)直接存进Survivor区
第二种(老年代可用内存大小 > Minor GC 过后,剩余存活对象大小 > Survivor 可用内存大小)直接存进老年代
第三种(老年代可用内存大小 < Minor GC 过后,剩余存活对象大小 )引发老年代 Full GC

Full GC 过后还不够空间,会导致 “OOM” 内存溢出

老年代 存放着长期存在的对象

永久代(元空间) 就是上面的方法区,存放一些类信息

为什么要分成年轻代和老年代
年轻代对象回收频率很高,创建之后很快就要被回收了,而老年代里的对象,他们的特点是需要长期存在,要用不同的回收算法来处理。

回收

新生代内存的垃圾回收-> "Minor GC"("Young GC")

只要当前实例对象有被GC Roots(方法的局部变量,静态变量)引用了,就不会回收

JVM中使用了一种可达性分析算法来判断,分析当前实例对象有没有GC Roots

e.g.

class world{
    void say(){  Systemt.out.println("Hello world!"); }
}
class test{
    public static void main(String[] args) {
        sayHello();
    }
    public void sayHello(){
        world w = new world();
        w.say();
    }
}

如图所示,当sayHello这个栈帧使用中的时候,它引用着world的实例对象。那么w这个局部变量就是GC Roots。

image.png

不同的引用类型

强引用、软引用、弱引用、虚引用

引用类型回收时
强引用有GC Roots引用着,不会被回收
软引用有GC Roots引用着,而内存不够用,就会被回收
弱引用引用着相当于没引用,必被回收