网站建设培训简报,手机app界面怎么做,专业网站建设联系,科技展厅效果图设计图一、引言
我们学习了Java内存运行时区域的各个部分#xff0c;其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生#xff0c;随线程而灭。栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来就已知的…一、引言
我们学习了Java内存运行时区域的各个部分其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生随线程而灭。栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来就已知的因此这几个区域的内存分配和回收都具备确定性在这几个区域内就不需要过多考虑如何回收的问题当方法结束或者线程结束时内存自然就跟随着回收了。
而Java堆和方法区这两个区域则有着很显著的不确定性一个接口的多个实现类需要的内存可能会不一样一个方法所执行的不同条件分支所需要的内存也可能不一样只有处于运行期间我们才能知道程序究竟会创建哪些对象创建多少个对象这部分内存的分配和回收是动态的。垃圾收集器所关注的正是这部分内存该如何管理一般讨论“内存”分配与回收也仅仅特指这一部分。 二、引用计数算法
引用计数算法是一种内存管理算法用于追踪对象的引用数量。它的基本原理是为每个对象维护一个计数器记录当前有多少个指针指向该对象。当计数器的值变为0时表示该对象不再被引用可以被回收。
引用计数算法的实现思路如下 在对象中添加一个引用计数器初始值为0。当有一个指针指向该对象时引用计数器加1。当一个指针不再指向该对象时引用计数器减1。当引用计数器的值为0时表示没有指针指向该对象可以将该对象回收。 引用计数算法的优点 实时性引用计数算法可以实时地进行内存回收不需要等待垃圾回收器的运行。简单高效引用计数算法的实现相对简单不需要遍历整个对象图只需要维护计数器即可。 引用计数算法的缺点 循环引用问题当存在循环引用时引用计数算法无法正确地回收内存。例如对象A和对象B相互引用它们的引用计数器都不会变为0导致内存泄漏。计数器更新开销每次引用发生变化时都需要更新计数器导致额外的开销。 因为引用计数算法存在循环引用问题所以现代的垃圾回收器往往不使用纯粹的引用计数算法而是采用其他算法如标记-清除算法、复制算法、标记-整理算法等与引用计数算法结合来解决循环引用的回收问题。 三、代码分析
以下是一个简单的引用计数算法的代码案例
class ReferenceCounting {private int count; // 引用计数器public ReferenceCounting() {count 0;}public void addReference() {count;}public void removeReference() {count--;}public int getCount() {return count;}
}
class Object {private ReferenceCounting refCount; // 引用计数对象public Object() {refCount new ReferenceCounting();refCount.addReference(); // 对象创建时增加引用计数}public void addReference() {refCount.addReference();}public void removeReference() {refCount.removeReference();if (refCount.getCount() 0) {// 引用计数为0时执行回收操作System.out.println(Object is reclaimed.);// 执行回收操作}}
}
public class ReferenceCountingDemo {public static void main(String[] args) {Object obj1 new Object(); // 创建对象1Object obj2 new Object(); // 创建对象2obj1.addReference(); // obj1引用计数加1obj1.addReference(); // obj1引用计数加1obj2.addReference(); // obj2引用计数加1obj1.removeReference(); // obj1引用计数减1obj1.removeReference(); // obj1引用计数减1计数为0执行回收操作obj2.removeReference(); // obj2引用计数减1计数不为0不执行回收操作}
}
在上述代码中ReferenceCounting类是引用计数器类用于记录对象被引用的次数。Object类是被引用的对象类其中包含了一个ReferenceCounting对象。当创建对象时引用计数加1当移除对象引用时引用计数减1。当引用计数为0时表示对象不再被引用可以执行回收操作。 在ReferenceCountingDemo类的main方法中我们创建了两个对象obj1和obj2分别增加和减少引用计数演示了引用计数算法的基本原理。 在下一个案例前我们首先要学会在IDEA中输出gc日志信息 循环引用代码分析
class A {private B b;public void setB(B b) {this.b b;}
}
class B {private A a;public void setA(A a) {this.a a;}
}
public class ReferenceCountingDemo {public static void main(String[] args) {A a new A();B b new B();a.setB(b);b.setA(a);// 解除对A和B对象的引用a null;b null;// 这里无法回收A和B对象因为它们之间存在循环引用System.gc();}
}
在上述案例中我们创建了两个类A和B它们分别有一个成员变量用于相互引用。在main方法中我们创建了一个A对象和一个B对象并通过setB和setA方法将它们相互引用起来。但是由于它们之间存在循环引用即A对象引用B对象B对象引用A对象导致它们的引用计数器都不会变为0无法被回收。
尽管在最后我们将a和b设置为null解除了对它们的引用但由于循环引用的存在它们的引用计数仍然不为0无法执行回收操作。 控制台输出 从运行结果可以看到内存回收日志包含“Pause Full (System.gc()) 2M-0M(14M) 3.909ms”意味着虚拟机并没有因为这两个对象互相引用就放弃回收它们这也从侧面说明了Java虚拟机并不是通过引用计数算法来判断对象是否存活的。