《深入java虚拟机》读书笔记之垃圾收集器与内存分配策略

该读书笔记用于记录在学习《深入理解Java虚拟机——JVM高级特性与最佳实践》一书中的一些重要知识点,对其中的部分内容进行归纳,或者是对其中不明白的地方做一些注释。主要是方便之后进行复习。

目录

《深入java虚拟机》读书笔记之Java内存区域

垃圾收集器与内存分配策略 哪些内存需要垃圾回收

在上一节中有提到在运行时数据区域包括:堆、虚拟机栈、本地方法栈、程序计数器、方法区(JDK1.7及之前)、元空间(JDK1.8及之后)。在这些区域中,程序计数器占用内存极小,可以忽略;栈区域在编译期就可以确定下来,并且其声明周期随线程保持一致,也不用管;而Java堆和方法区、元空间中接口的不同实现类需要的内存不同,方法的不同实现需要的内存也不同,而且这些所需的内存需要在运行时才能确定,所以垃圾回收关注的主要内容就是这些区域。

对象是否不再使用 引用计数法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器加一;引用失效的时候,计数器就减一;在任何时候只要计数器为0就代表该对象就是不会再被使用的。

该方法的优点:

实现较为简单

判定效率很高,基本没有其他额外的操作

缺点:

很难解决对象之间相互循环引用的问题。即两个对象相互持有对方的引用,除此之外再没有别的地方使用这两个对象,但是因为相互引用导致计数器不可能为0,所以无法被回收

可达性分析算法 算法描述

通过选择一些满足一定条件的对象作为节点,从这些节点开始往下搜索,搜索经过的路径被称为引用链(有直接或间接引用关系的对象都在引用链上),这些对象被成为"GC Roots",当一个对象达到GC Roots没有任何引用链时则判定该对象不可用,即使该对象仍旧被其他对象引用,只要其与GC Roots没有关系既是不可用的。

可作为GC Roots的对象

虚拟机栈中引用的对象。

方法区中常量引用的对象。

方法区中类静态属性引用的对象。

本地方法区中JNI引用的对象。

简单来说包括以下几种类型:

Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的

Thread - 活着的线程

Stack Local - Java方法的local变量或参数

JNI Local - JNI方法的local变量或参数

JNI Global - 全局JNI引用

Monitor Used - 用于同步的监控对象

Java中的引用

在最初的Java中,引用仅仅是指一个对象的数据中存储的值是另外一块内存的起始地址。在JDK1.2之后将引用分为多种:

强引用:强引用是类似于User user = new User(),是在代码中最常用的一种方式。只要强引用存在,那么垃圾回收器就永远不会回收掉被引用的对象。

软引用:软引用用于描述一些有用但是并不是一定需要的对象,对于软引用的对象,当内存将要发生溢出时,会将这些对象列入回收范围中进行一次回收,如果将软引用的对象回收后内存还是不足才会抛出内存溢出异常。在JDK中使用SoftReference类实现软引用。SoftReference<Object> softReference = new SoftReference<Object>(new Object());

弱引用:弱引用用于描述非必须的对象,弱引用对象在下一次垃圾回收时一定会被回收,无论当前内存是否足够。在JDK中使用WeakReference定义弱引用。

虚引用:一个对象是否存在虚引用对其生存时间不会有任何关系,只是在这个对象呗收集器回收时收到一个系统通知。在JDK中使用PhantomReference来实现虚引用。

对象的自救

实际上,在可达性算法中即使是不可达的对象也并非一定会被回收的,判断其是否会被回收还需要走以下流程:

如果对象在可达性分析中被判定没有与GC Roots相连接的引用链那么改对象将会被标记,然后进行一次筛选。

筛选的条件是判断该对象是否有必要执行finalize()方法。是否有必要执行finalize()方法的条件是当对象没有覆盖finalize()方法或者该对象的finalize()方法已经被虚拟机调用过,这两种情况都会被判定为没有必要执行。

如果被判定为有必要执行finalize方法,则会将其放在一个队列中,稍后执行。在finalize()方法中是对象逃脱被回收的最后机会,只要重新与引用链中的任何一个对象建立关系即可。

public class FinalizeEscape { private static FinalizeEscape escape = null; public static void main(String[] args) throws InterruptedException { escape = new FinalizeEscape(); //模拟对象使用后断开引用链 escape = null; //对象自救 System.gc(); Thread.sleep(500); if(escape != null){ System.out.println("对象没有被清除!"); }else { System.out.println("对象已经被清除!"); } //模拟第二次逃脱gc escape = null; System.gc(); Thread.sleep(500); if(escape != null){ System.out.println("对象没有被清除!"); }else { System.out.println("对象已经被清除!"); } } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("finalize execute!"); escape = this; } } 运行结果: finalize execute! 对象没有被清除! 对象已经被清除!

在对同一对象进行两次模拟逃脱gc,第一次成功第二次失败,是因为一个对象的finalize()方法只会被调用一次。

方法区回收

在方法区的回收主要包括两个方面:

废弃常量

无用的类

废弃常量

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygpfp.html