Java 是一门不需要自己手动控制内存释放的语言。那在程序运行中,它是如何判断哪些内存可以回收呢?
从 JVM 的实现角度总体来看
-
主要考虑的是堆内存区域的 Java 对象的回收
-
一般使用可达性分析算法,通过 GC Roots 找到不可达对象进行回收
-
回收对象时需要考虑对象被引用的强度
-
回收对象前,会调用对象的 finalize() 方法,若在方法里把当前对象赋值给其他变量引用,会有且仅有一次避免被回收的机会
可回收内存区域
JVM 在运行的内存分配区域包括线程私有和线程共享两大类。
线程私有的内存区域:
分为程序计数器、虚拟机栈、本地方法栈这三个区域,三者的生命周期与线程相同,即线程执行结束这三个区域的内存就可以被回收了。
线程共享的内存区域:
包括堆和方法区,这两部分内存是在程序执行中动态分配与回收的,具有不确定性,所以垃圾收集器主要关注这两部分区域的内存回收。
绝大多数 Java 对象是在堆内存中分配的;方法区中存放了 Class 信息、常量、静态变量、即时编译器编译后的代码缓存等数据,排除大量动态类信息创建和卸载的情况,一般方法区内存回收的效率是很低的。所以垃圾收集器最主要关注的是堆内存的回收。
方法区的垃圾回收主要包含
1、废弃的常量,包括:字符串常量、接口 方法 和 字段的符号引用。
2、不再使用的类,满以下三个要求可以被回收但并非必然。
-
堆中不存在类及其子类的实例对象
-
该类的加载器已被回收
-
Class 对象未被引用,也无法反射访问
另外,直接内存不在 JVM 规范中定义的内存区域,使用 Unsafe 的 native 方法直接分配 (allocateMemory) 和释放 (freeMemory) 内存。nio 中通过在堆内存分配 DirectByteBuffer 对象,该对象可以操作直接内存,DirectByteBuffer 对象被回收后 gc 回收未释放的直接内存。
说到这,Java 对象的内存回收的问题,大致可以转变为「如何判断堆中 Java 对象占用的内存可以被回收?」,那得看下面具体的判断对象可回收的算法。
内存可回收的算法
常见的判断对象可以被回收的算法有两种:
-
引用计数算法。理解起来很简单,对象里添加计数器,被引用计数加一,去除引用计数减一,计数为零的时刻就认为对象可以被回收。由于这种算法不好解决 Java 对象循环引用的问题,所以主流垃圾回收器不使用。
-
可达性分析算法。这个算法的核心思想就是先找到内存中的所有根对象,即 "GC Roots",然后根据引用关系搜索引用对象,未被查到的对象就是可以被回收的对象。
GC Roots,根对象包括:
-
虚拟机栈中引用的对象,如方法中的局部变量、参数等
-
类的静态变量引用的对象
-
常量引用的对象
-
native 方法引用的对象
-
JVM 内部引用,如基本类型的 Class 对象、异常对象、系统类加载器对象
-
synchronized 锁住的对象
-
JVM 内部的对象
细分引用类型
为了提供类似缓存的能力,内存足够时不着急回收对象、解决对象一直被错误地引用导致的内存溢出等问题,JDK 1.2 开始将引用分了为了四种类型。
引用强度由强到弱,分别为
-
强引用(Strong Reference):String s = new String("ConstXiong"),代码中直接的赋值,引用关系在对象不会被回收
-
软引用(Soft Reference):SoftReference 实现,系统发生内存溢出前,回收软引用对象
-
弱引用(Weak Reference):WeakReference 实现,下一次垃圾收集会回收掉即会回收弱引用对象
-
虚引用(Phantom Reference):PhantomReference 实现,对垃圾收集器回收内存行为没有影响,仅为了虚引用对象被回收时能收到系统通知
例如:ThreadLocal 中的 ThreadLocalMap 中的 Entry 就继承了 WeakReference 类,用来解决线程本地变量内存泄漏的问题。
最后一次逃离的机会
对象被回收前会调用类的 finalize() 方法,在finalize() 方法中把当前对象赋值给其他变量引用,会有且仅有一次避免被回收的机会。
【Java学习资源】整理推荐
- MAT 分析堆内存快照
- jclasslib 查看类信息
- 增大 MAT 堆内存
- JVM 常用配置参数(Java 8)
- 40 道JVM面试题解答
- JVM 问题排查常用指令
- jvisualvm 远程连接服务器 JVM
- 字节码指令分类
- -verbose:gc 和 -XX:+PrintGC 的区别
- JVM字节码指令表
- 图解 Class 文件结构
- Class 文件的结构
- JVM 故障处理工具列表
- HotSpot 内存分配的主要规则
- 开启 GC 日志
- 垃圾收集器及特点
- JVM垃圾回收算法
- 什么样的 Java 对象会被当垃圾回收?
- 内存溢出复现
- HotSpot VM 中对象的内存分析
- JVM 控制的内存区域
- JVM家族
- Java 的发展史
- 常见 JVM dump 指令
【Java面试题与答案】整理推荐