JVM-垃圾回收
1 如何辨别一个对象已死亡
1.1 引用计数法与可达性分析
1.1.1 引用计数法
概述
- 为每个对象添加一个计数器,当该对象被一个变量引用时,计数器就会加一.当指向该对象的引用被赋了其他的值,对象的引用就会减一.
缺点:
- 需要额外的空间储存计数器.
- 不能处理循环引用,回造成内存泄露.
1.1.2 可达性分析法-java虚拟机主流算法
概述
- 将一系列GC Roots作为初始存活对象的集合.从该集合出发,所有能被该集合引用到的对象都加入这个集合.这个过程被称为标记.所有未被标记的都认为是不可达的,是可以被回收的.
GC Root,包括但不限于:
- java 方法栈帧中的局部变量
- 已加载类的静态变量
- JNI handles;
- 已启动且未停止的线程.
缺点
- 多线程情况下,其他线程可能会更新已经访问过的对象中的引用,从而导致漏报或者误报.
如何解决误报
- stop the world ,即在垃圾回收时停止其他一切非垃圾回收的活动.回收的时机为当所有的线程都到达了安全点.
- 线程的几种状态
本地代码:当 Java 程序通过 JNI 执行本地代码时,如果这段代码不访问 Java 对象、调用 Java 方法或者返回至原 Java 方法,那么 Java 虚拟机的堆栈不会发生改变,也就代表着这段本地代码可以作为同一个安全点。
解释执行字节码:执行一条字节码就进行一次安全点检测.
执行即时编译器生成的机器码:在编译机器码时插入安全检查点.在生成代码的出口以及非计数循环的循环回边插入.
线程阻塞:安全
2 垃圾回收方法
2.1 清除
把死亡对象占据的内存标记为空闲对象,并记录在一个空闲列表之中.要新建对象时,就把这些空间分配给新的对象.
- 缺点,会造成内存碎片.
2.2 压缩
把存活的对象聚集到内存区域的起始位置. - 缺点,压缩算法的开销.
2.3 复制
把内存区域分成两份,分别用两个指针from, to 来处理.当发生对象回收时,将存活的对象从from复制到to的区域,然后交换from和to的指针. - 缺点,堆空间使用效率低下.
3 java虚拟机的划分
java虚拟机垃圾分代基于的基本假设-大部分对象只存活一小段时间,小部分对象存活很长时间.
- 新生代-老年代
-
新生代又分为Eden区和两个大小相同的Survivor区
-
当调用new指令时,会在Eden划分出一块区域.当Eden区域内存满了,Java 会出发一次Minor GC收集新生代垃圾.
- 生存下来的对象会被送到Survivor区.
- 发生Minor GC时,Survivor区的存活对象也会被从from移到to区.并交换from和to的指针.
- Survivor区的对象被来回复制超过15次以后,就会被送到老年代.
- 如果单个Survivor区的内存使用量超过50%,较高复制次数的对象也会被晋升至老年代.
- TLAB(Thread Local Allocation Buffer)-这个技术是用于解决多线程竞争堆内存分配问题的,核心原理是对分配一些连续的内存空间
- 如果老年代的对象引用了新生代的对象,那么这个引用也会被加入到GC Roots中.这会导致全堆扫描
- 如何应对全堆扫描-卡表,大致地标出可能存在老年代到新生代引用的内存区域。