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 内部的对象
ConstXiong 备案号:苏ICP备16009629号-3