赞
踩
在做 Java 开发的过程中,我们会不断地创建很多的对象,这些对象数据会占用系统内存,如果得不到有效的管理,内存的占用会越来越多,甚至会出现内存溢出的情况,所以,我们需要进行对内存进行合理地释放,这个时候 GC 就派上大用场的。
垃圾回收(GC)是由 Java 虚拟机(JVM)垃圾回收器提供的一种对内存回收的一种机制,它一般会在内存空闲或者内存占用过高的时候对那些没有任何引用的对象不定时地进行回收。
Jdk1.8以前
Jdk1.8以后
虚拟机栈、本地方法栈和程序计数器,这三个区域是线程私有的。比如栈帧的生命周期是和线程关联的,即随线程而生,随线程而死。
虚拟机栈其实就是用来描述 Java 方法执行的,所以每个方法执行的时候都会创建一个栈帧,每个栈帧都包含:局部变量、操作数栈、动态链接、方法出口,当方法执行完成之后,对应的栈帧便会出栈。所以它的内存分配是具备确定性的,因此我们并不需要太过关注包括虚拟机栈在内的这几个线程私有区域的内存使用情况。
相反,另外两个线程共享的区域:方法区和堆内存,则是我们需要重点关注的对象。因为这两个区域主要存放对象、数组等不具有确定性的数据,例如创建对象,每个方法运行的过程中创建的对象的数量是不确定的,即占用的内存是不确定的,可能不需要创建对象,也可能会创建很多对象,所以我们需要一套合理的内存管理机制来对这两个区域进行维护,因此,垃圾回收就应运而生了,并且这两个区域也是垃圾回收器进行垃圾回收的最重要的内存区域。
再来对堆内存和方法区进行一下划分,因为 JVM 是采用分代回收的算法,即根据对象的生命周期进行区分并进行分代存储和回收,其主要分为年轻代、老年代、持久代,见下图:
堆内存主要由年轻代和老年代组成,而方法区主要存储持久代的数据。
注意:从 JDK 1.8 开始,永久代已经被移除了,取而代之的是元空间(Meta Space),它和服务器的内存相关联。
GC 的存活标准知道哪些区域的内存需要被回收之后,我们自然而然地想到了,如何去判断一个对象需要被回收呢?对于如何判断对象是否可以回收,有两种比较经典的判断策略。
一个对象被创建之后,系统会给这个对象初始化一个引用计数器,当这个对象被引用了,则计数器 +1,而当该引用失效后,计数器便 -1,直到计数器为 0,意味着该对象不再被使用了,则可以将其进行回收了。
这种算法其实很好用,判定比较简单,效率也很高,但是却有一个很致命的缺点,就是它无法避免循环引用,即两个对象之间循环引用的时候,各自的计数器始终不会变成 0,所以 引用计数算法 只出现在了早期的 JVM 中,现在基本不再使用了。
根搜索算法的中心思想,就是从某一些指定的根对象(GC Roots)出发,一步步遍历找到和这个根对象具有引用关系的对象,然后再从这些对象开始继续寻找,从而形成一个个的引用链(其实就和图论的思想一致),然后不在这些引用链上面的对象便被标识为引用不可达对象,也就是我们说的“垃圾”,这些对象便需要回收掉。这种算法很好地解决了上面 引用计数算法 的循环引用的问题了。
在Java语言中,可作为GC Roots的对象包括下面几种:
Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference
)、软引用(Soft Reference)
、弱引用(Weak Reference)
、虚引用(Phantom Reference)
4种,这4种引用强度依次逐渐减弱。这样子设计的原因主要是为了描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。也就是说,对不同的引用类型,JVM 在进行GC 时会有着不同的执行策略。所以我们也需要去了解一下。
Object obj =new Object();
上述Object这类对象就具有强引用,属于不可回收的资源,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠回收具有强引用的对象,来解决内存不足的问题。
软引用是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象。JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。