java 垃圾收集(Garbage Collection)之 哪些内存需要回收
Posted BBinChina
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java 垃圾收集(Garbage Collection)之 哪些内存需要回收相关的知识,希望对你有一定的参考价值。
Garbage Collection : 垃圾收集,简称GC
在使用java的时候,我们将内存交由虚拟机管理,而不需开发人员过度关注,但并不意味着我们可以不学习什么是垃圾回收和内存分配。因为当需要排查各种内存溢出、内存泄漏问题时,垃圾回收成为系统达到更高并发量的瓶颈时(gc需要占用工作线程),我们需要能进行监控和调节。
Java的内存运行区数据中,程序计数器、虚拟机栈、本地方法栈这3个区域随线程而生亡,其栈帧分配多少内存基本上是在类结构确定下来时就已知的,所以这3个区域的内存分配和回收都具备确定性,而不需要关注如何回收:当方法结束或者线程结束时,内存自然就回收了
那么哪个区域的内存需要考虑呢:java堆和方法区
比如:一个接口的多个实现类需要的内存可能不同,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有在运行期才能知道创建哪些对象,创建多少个对象。这部分内存的分配和回收是动态的,我们将讨论java这部分内存的分配和回收
如果让你设计垃圾回收,可以有哪些方法
引用计数算法
在使用c++的智能指针share_ptr时,其采用一个计数器来记录对象的引用情况,当新增引用时,计数器+1,引用失效时,计数器就-1.当计数器值为0时,对象可以被自动化释放。
采用引用计数算法比较简单,但随着的问题在于解决循环引用的问题,而在Java中,大多数的JVM并没有选用引用计数算法,主要在于JVM整体不像c++的share_ptr这样简化。
可达性分析算法
这个算法的思路是通过一系列称为"GC Roots"的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则表明该对象不可能再被使用。
在Java体系中,固定可作为GC Roots对象包括以下几种:
1、在虚拟机栈(栈帧中的本地变量表)中引用的对象
2、在方法区中类静态属性引用的对象
3、在方法区中常量引用的对象
4、在本地方法栈中JNI(Native方法)引用的对象
5、Java虚拟机内部的引用
6、所有被同步锁(synchronized)持有的对象
7、反映JVM内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
了解哪些是GC Roots对象,我们再回头看什么是引用?哎?引用不就是 数据中存储的数值代表的另外一块内存的起始地址么?这有啥好解释的?
Java 引用
强引用(Strongly Reference):程序中普遍存在的引用赋值,只要强引用在,对象就永不会回收,如 Object A = new Object();
软引用(Soft Reference):类SoftReference用于实现软应用,描述还有用,但非必须的对象,在内存溢出异常前才列入回收对象范围进行回收。
弱引用(Weak Reference):类WeakReference实现弱引用,描述非必须对象,在gc时不管内存是否足够都进行回收。
虚引用(Phantom Reference):类PhantomReference实现虚引用,为对象设置虚引用的唯一目的是在该对象回收时,收到一个系统通知。
Java哪些对象将被回收
通过可达性分析算法,JVM判定不可达的对象列入回收范围,但并不是立马进行回收执行,还得再经历一次标记,即对象是否需被回收经历两次标记:
1、可达性分析,当没有引用链时,进行一次标记
2、该对象执行了finalize()方法或者没必要执行finalize()方法时,进行一次标记
什么叫没必要执行:
当对象没有覆盖finalize()方法,或者finalize()方法已经被JVM调用过,称这两种情况为"没有必要执行"
而如果要执行finalize()方法,也并非立马进行标记,而是将对象放置F-Queue队列之中,并由JVM的Finalizer线程去执行队列中对象的finalize()方法,该线程并不会等待finalize()方法执行完成,避免对象Finalize()方法异常导致回收子系统崩溃等情况。在执行finalize()方法时,可以将this(即自己)赋值给某个类变量或者对象的成员变量,让当前对象重回调用链,那么可以让自己避免被标记,从而自我拯救。
需要注意的是对象的finalize()方法只能被系统自动调用一次
/**
* 1、对象可以在被GC时自救
* 2、自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
*/
public class FinalizeEscapeGC {
private static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive() {
System.out.println("im fine");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizeEscapeGC();
SAVE_HOOK = null;
System.gc();
//Finalizer方法优先级很低,先暂停
Thread.sleep(500);
if(SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("gg");
}
//再一次gc,finalize不会再被调用
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if(SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("gg");
}
}
}
以上代码便是利用finalize()方法来自我拯救,避免被回收, 一般场景下,也不会有使用finalize()方法的时候。
回收方法区
上文说到,回收的内容除了Java堆外,还有方法区的内容:放弃的常量和不再使用的类型。
而判断是否废弃的条件:
1、该类的所有实例都已经被回收,即Java堆中不存在该类以及任何派生子类的实例。
2、加载该类的类加载器已经被回收。
3、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
以上是关于java 垃圾收集(Garbage Collection)之 哪些内存需要回收的主要内容,如果未能解决你的问题,请参考以下文章
java垃圾回收Garbage Collection(垃圾收集算法)
[JVM 相关] Java 新型垃圾回收器(Garbage First,G1)