一、什么是垃圾
垃圾
是指在运行程序中 没有任何指针指向的对象,这个 对象 就是需要 被回收的垃圾。- 如果不及时对内存中的垃圾进行清理,这些 垃圾对象占用内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用,甚至可能 导致内存溢出。
二、如何回收垃圾
- 通过
GC 收集器
进行垃圾回收。 - GC 收集器产生背景,是有如下算法。
引用计数法
注意: 在 java 中是没有使用引用计数法
引用计数法产生背景:
- 在某些编程语言中,对象的内存分配和释放是由开发者手动管理的。当一个对象不再被使用时,需要显式地释放它所占用的内存,否则可能会导致内存泄漏。
当对象被 引用 时,计数器 加 1;
当对象的引用被 解除时,计数器 减 1;
当引计数器为 0 时,对象会被回收
在 Java 中,引用计数法并不是主要的内存管理技术,因为 Java 使用垃圾回收器(Garbage Collector)来自动管理对象的内存。垃圾回收器通过检测不再被引用的对象,并自动释放它们所占用的内存。
然而,为了了解引用计数法的工作原理,我们可以创建一个简单的示例来模拟引用计数的概念。请注意,这只是一个示例,不建议在实际开发中使用引用计数法。
package com.calvin.jvm.gc.algorithm;
/**
* 引用计数对象
*
* @author calvin
* @date 2023/10/08
*/
class ReferenceCountedObject {
private int referenceCount;
/**
* 引用计数对象
*/
public ReferenceCountedObject() {
referenceCount = 0;
}
/**
* 添加引用
*/
public void addReference() {
referenceCount++;
}
/**
* 删除引用
*/
public void removeReference() {
referenceCount--;
}
/**
* 获取引用计数
*
* @return int
*/
public int getReferenceCount() {
return referenceCount;
}
}
/**
* 引用计数示例
*
* @author calvin
* @date 2023/10/08
*/
public class ReferenceCountingExample {
/**
* 主要
*
* @param args arg 参数
*/
public static void main(String[] args) {
ReferenceCountedObject obj1 = new ReferenceCountedObject();
// 增加引用计数
obj1.addReference();
// obj2 引用 obj1
ReferenceCountedObject obj2 = obj1;
// 增加引用计数
obj2.addReference();
// 输出引用计数
System.out.println("obj1 reference count: " + obj1.getReferenceCount());
// 减少引用计数
obj1.removeReference();
System.out.println("obj1 reference count: " + obj1.getReferenceCount());
// 减少引用计数
obj2.removeReference();
System.out.println("obj1 reference count: " + obj1.getReferenceCount());
}
}
/**
* 输出结果:
*
* obj1 reference count: 2
* obj1 reference count: 1
* obj1 reference count: 0
*/
在这个示例中,我们创建了一个名为 ReferenceCountedObject
的类,它包含一个引用计数器 referenceCount
。
- 通过
addReference()
方法增加引用计数, - 通过
removeReference()
方法减少引用计数。 getReferenceCount()
方法用于获取当前的引用计数。
在 ReferenceCountingExample
类的 main()
方法中,我们创建了一个 ReferenceCountedObject
对象 obj1
,并增加了一个引用计数。然后,我们创建了另一个对象 obj2
,并将其设置为引用 obj1
,同时增加了一个引用计数。
最后,我们通过调用 removeReference()
方法来减少引用计数,并通过调用 getReferenceCount()
方法来获取当前的引用计数。输出结果将显示引用计数的变化。
请注意,这个示例只是为了演示引用计数法的概念,并不适用于实际的内存管理。在 Java 中,垃圾回收器会自动处理对象的内存释放,无需手动管理引用计数。
注意: 引用计数法存在无法处理循环引用的情况问题
可达性分析算法
判定对象是否存活的。
该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点。
从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象是不可用的。
例如:可达分析流程图
对象 object 5、object 6、object 7 虽然互有关联,但是它们到 GC Roots 是不可达的, 因此它们将会被判定为可回收的对象.
在 Java 技术体系里面,固定可作为GC Roots的对象
包括以下几种:
- 在
虚拟机栈(栈帧中的本地变量表)
中引用的对象, (例如: 各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等)。 - 在
方法区中类静态属性
引用的对象,(例如: Java 类的引用类型静态变量)。 - 在
方法区中常量
引用的对象,(例如: 字符串常量池(String Table)里的引用)。 - 在
本地方法栈中JNI(即通常所说的Native方法)
引用的对象。 Java虚拟机内部的引用
,(例如: 基本数据类型对应的 Class 对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器)。- 所有
被同步锁(synchronized关键字)持有
的对象。 - 反映
Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存
等。
三、垃圾回收算法
标记清除-算法
- 该算法从根对象(如全局变量、局部变量和静态字段)开始,递归遍历对象图,标记活动对象。标记阶段结束后,垃圾回收器会清理未被标记(不可达)的对象所占用的内存空间。
- 产生问题:
存储空间不连续,碎片空间多
- 一般应用于:
老年代对象
标记复制-算法
- 该算法将内存空间分为两个相等的部分:
"from"
空间和"to"
空间。 - 它从根对象开始,递归遍历对象图,将活动对象从
"from" 空间复制到 "to" 空间
。复制阶段结束后,两个空间的角色互换,"from" 空间变为空。 - 这种算法对于
管理内存碎片化
非常高效。 - 产生问题:
多数对象存活,复制来复制去,将过多的消耗性能。同时浪费一半内存空间
。 - 一般应用于:
新生代对象
标记整理-算法
- 将存活对象进行移动当内存空间另外一端(整理),在清除存活对象边界以外的垃圾对象进行清除。(解决了浪费内存问题,让内存得到高效利用,同时解决了碎片化内存不连续的问题。)
- 一般应用于:
老年代对象