java垃圾回收与c_JAVA 垃圾回收

垃圾回收的意义

随着程序的运行,内存中存在的实例对象、变量等信息占据的内存越来越多,如果不及时进行垃圾回收,必然会带来程序性能的下降,甚至会因为可用内存不足造成一些不必要的系统异常。

哪些垃圾要进行回收?

在Java内存运行时区域的各个部分, 其中程序计数器、JVM栈、本地方法栈3个区域的生命周期是和线程同步的, 他们占用的内存会随着线程销毁而自动释放, 所以这几个区域不需要过多的考虑垃圾回收问题。

而Java堆和方法区则不一样, 一个接口中的多个实现类需要的内存可能不一样, 一个方法中的多个分支需要的内存也可能不一样, 我们只有在程序处于运行期间才能知道会创建哪些对象, 这部分内存的分配和回收是动态的, 所以需要进行GC。

什么时候进行垃圾回收?

垃圾收集器在对Java堆进行回收前, 会先去确定所有的对象实例之中哪些还"存活"着, 哪些已经"死去"(即已经不存在任何引用)。

引用计数算法:给对象中添加一个引用计数器, 每被引用一次,计数器加1; 引用失效时,计数器减1; 当计数器在一段时间内保持为0时,该对象就被认为是可回收的。但是, 这个算法有明显的缺陷: 当两个对象相互引用,但是二者已经没有作用时,按照常规,应该对其进行垃圾回收,但是其相互引用,又不符合垃圾回收的条件,因此无法完美处理这块内存清理。

可达性分析算法:通过一系列的称为"GC Roots"的对象作为起始点, 从这些节点开始向下搜索, 搜索所走过的路径称为引用链, 当一个对象到GC Roots没有任何引用链相连时, 则证明此对象是不可用的

无论是引用计数算法, 还是可达性分析算法, 它们判定对象是否存活都与"引用"有关;在JDK 1.2之后, Java对引用的概念进行了扩充,引入了强、软、弱、虚四种引用, 这4种引用强度依次逐渐减弱

强引用:只要引用存在,垃圾回收器永远不会回收。

Object obj = new Object();

而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。

软引用:非必须引用,内存溢出之前进行回收

Object obj = new Object();

SoftReference sf = new SoftReference(obj);

obj = null;

sf.get();

软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。

弱引用:第二次垃圾回收时回收

Object obj = new Object();

WeakReference wf = new WeakReference(obj);

obj = null;

wf.get();//有时候会返回null

wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾

弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。

弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记

虚引用:垃圾回收时回收,无法通过引用取到对象值

Object obj = new Object();

PhantomReference pf = new PhantomReference(obj);

obj=null;

pf.get();//永远返回null

pf.isEnQueued();//返回是否从内存中已经删除

虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。

虚引用主要用于检测对象是否已经从内存中删除。

finalize()

即使在可达性分析算法中不可达的对象,也并非是"非死不可"的。如果类重写了finalize()方法, 且没有被虚拟机调用过, 那么虚拟机会调用一次finalize()方法, 以完成最后的工作, 在此期间, 如果对象重新与引用链上的任何一个对象建立关联,则该对象可以“重生”; 如果对象这时候还没有逃脱, 那么它就真的被回收了。

垃圾收集器回收方法区

垃圾收集器在对方法区进行回收前, 会先去判定一个类是否是"无用的类", 而类需要同时满足下面3个条件才能算是"无用的类":

该类的所有实例对象都已经被回收。

加载该类的ClassLoader已经被回收。

该类对应的java.lang.Class对象没有在任何地方被引用, 无法在任何地方通过反射访问该类的方法。

如何进行垃圾回收:堆内存分代策略以及意义

Java虚拟机将堆内存划分为新生代、老年代和永久代(jdk1.8之后没了,原本放在永久代的字符串常量池移到方法区,常量池1.8在元空间。而元空间是直接存在内存中,不在java虚拟机中的,因此元空间依赖于内存大小。当然你也可以自定义元空间大小),新生代和老年代是垃圾回收的主要区域,内存大小比例大概为 1:2 。

新生代

新生成的对象优先存放在新生代中,新生代对象朝生夕死,存活率很低,在新生代中,常规应用进行一次垃圾收集-般可以回收70% ~ 95%的空间,回收效率很高。

老年代

在新生代中经历了多次(具体看虚拟机配置的阀值)GC后仍然存活下来的对象会进入老年代中。老年代中的对象生命周期较长,存活率比较高,在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢。

有了内存分代,新创建的对象会在新生代中分配内存,经过多次回收仍然存活下来的对象存放在老年代中。新生代中的对象存活时间短,只需要在新生代区域中频繁进行GC,老年代中对象生命周期长。分代垃圾收集大大提升了垃圾收集效率,这些都是JVM分代的好处。

8ef5ae9821128b56e63d0d9bf2f960bd.png

如上图所示, 新生代中分为Eden区和Survivor区, 而Survivor区又分为大小相同的两部分:FromSpace 和ToSpace。其中Eden区和一个Survivor区的默认空间比例为8:1, 可以用-XX:SurvivorRatio来设置大小。大多数情况下, 对象在新生代Eden区中分配, 当Eden空间不足时, 虚拟机将发起一次Minor GC把存活的对象转移到Survivor区中。新生代采用复制算法收集内存。

旧生代中用于存放新生代中经过多次垃圾回收仍然存活的对象, 和一些需要大量连续内存空间的大对象。另外在JVM中还有一种动态对象年龄判定: 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半, 年龄大于或等于该年龄的对象就可以直接进入旧生代。旧生代采用标记-整理(压缩)算法收集内存。

垃圾回收算法

复制算法

复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一半的内存。

标记清除算法

是JVM垃圾回收算法中最古老的一个,该算法共分成两个阶段,第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,清除未被标记的对象。该算法的缺点是需要暂停整个应用,并且在回收以后未使用的空间是不连续,即内存碎片,会影响到存储。

标记整理算法

此算法结合了标记-清楚算法和复制算法的优点,也分为两个阶段,第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题,按顺序排放,同时解决了复制算法所需内存空间过大的问题。

分代收集

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

年轻代回收算法(核心其实就是复制算法)

老年代回收算法(回收主要以标记-整理为主)

垃圾收集器

Serial收集器:串行收集器

-XX:+UseSerialGC

ParNew 收集器:并行收集器

-XX:+UseParNewGC

Parallel Scavenge 收集器:新生代并行收集器

-XX:+UseParallelGC: 新生代使用并行回收收集器,旧生代使用串行收集器。

-XX:+UseParallelOldGC: 新生代和旧生代都使用并行回收收集器。

-XX:+UseAdaptiveSizePolicy 可以打开自适应 GC 策略。在这种模式下,新生代的大小、eden 和 survivor 的比例、晋升旧生代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。

Serial Old收集器:旧生代串行收集器

-XX:+UseSerialGC 指定新生代、旧生代都使用串行回收器

Parallel Old收集器:旧生代并行收集器

-XX:+UseParallelOldGC 可以在新生代和旧生代都使用并行回收收集器

-XX:ParallelGCThreads也可以用于设置垃圾回收时的线程数量。

CMS (Concurrent Mark Sweep)收集器

-XX:ParallelCMSThreads 参数手工设定 CMS 的线程数量

-XX:CMSInitiatingOccupancyFraction 来指定回收阈值,默认是 68

-XX:CMSInitiatingOccupancyFraction 进行调优

-XX:+UseCMSCompactAtFullCollection 参数可以使 CMS 在垃圾收集完成后,进行一次内存碎片整理

-XX:CMSFullGCsBeforeCompaction 参数可以用于设定进行多少次 CMS 回收后,进行一次内存压缩。

G1收集器

-XX:+UnlockExperimentalVMOptions –XX:+UseG1GC 来启用 G1 回收器

-XX:MaxGCPauseMills=20,-XX:GCPauseIntervalMills=200 目标停顿时间

查看 jvm 使用的收集器:

java -XX:+PrintCommandLineFlags -version

PS:JVM 默认收集器:

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值