java中的JVM垃圾处理机制_jvm垃圾清理机制

垃圾回收对象

垃圾回收器主要针对的回收对象为:

新生代和老年代需要被回收的对象是:

GC Root的引用链不可到达该对象,进行第一次标记;

且第一次标记完成时,该对象的finalize()方法已经被调用过或者没办法被调用。

可以用作GC Root的有:全局性的引用,比如常量或者类的静态属性。

方法区永久代需要被回收的对象是:

废弃常量和无用的类。

Minor GC ,Full GC 触发条件

Minor GC触发条件:当Eden区满时,触发Minor GC。

Full GC触发条件:

(1)调用System.gc时,系统建议执行Full GC,但是不必然执行

(2)老年代空间不足

(3)方法区空间不足

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

回收方式

Minor GC:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC

Major GC: 对老年代GC称为Major GC

Full GC: 而Full GC是对整个堆来说的,在最近几个版本的JDK里默认包括了对永生代即方法区的回收(JDK8中无永生代了)

出现Full GC的时候经常伴随至少一次的Minor GC,但非绝对的。

Major GC的速度一般会比Minor GC慢10倍以上。

回收时机

java对象优先分配在Eden区和一个Survivor区(Eden和Survivor的比例可以配置,比如8:1),当Eden区和Survivor区内存不够对象分配时,触发一次MinorGC,将Eden区和Survivor区中存活的对象移入到另一个Survivor区,并将Eden和Survivor进行回收。

如果另一个Survivor区不够装入存活的对象时,使用分配担保策略将这些对象存入老年代。

java大对象直接分配到老年代。长字符串或者大数组这类大对象会在超过设定的阈值后直接存入老年代,避免其在Eden区频繁地引发Minor GC,来回复制。

长期存活的对象进入老年代。每个对象维持一个年龄计数器。在Eden经过一次Minor GC后对象要么因为分配担保直接进入老年代,要么存放在Survivor中,对象在Survivor中每经过一次Minor GC,年龄计数器就加一,直到年龄超过一定阈值时,对象会进入老年代。

如果老年代的剩余连续空间不够一次Minor GC的分配担保,或老年代的内存已经不够Survivor中长期存活的对象移入,则触发一次Full GC。

是否回收判断

判断堆内的对象是否可以回收,要判断这个对象实例是否确实没用,判断算法有两种:引用计数法和根搜索算法。

引用计数法:就是给每个对象加一个计数器,如果有一个地方引用就加1,当引用失效就减1;当计数器为0,则认为对象是无用的。这种算法最大的问题在于不能解决相互引用的对象,如:A.b=B;B.a=A,在没有其他引用的情况下,应该回收;但按照引用计数法来计算,他们的引用都不为0,显然不能回收。

根搜索算法:这个算法的思路是通过一系列名为“GC Roots”的对象作为起点,从这个节点向下搜索,搜索所经过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(图论的不可达)时,则证明该对象不可用。

GC Roots的对象有如下几种:

虚拟机栈(栈帧中的本地变量表)中的引用的对象;

方法区中的类静态属性引用的对象;

方法区中常量引用的对象;

本地方法栈JNI(Native)的引用对象;

95cda28ef647?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱:

强引用

只要强引用还存在,垃圾收集器永远不会收掉被引用的对象

软引用

在系统将要发生内存异常之前,将会把这些对象列进回收范围之中进行第二次回收。

弱引用

被弱引用关联的对象只能生存到下一次垃圾收集发生之前。

虚引用

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象的实例。

finalize()在什么时候被调用?

所有对象被Garbage Collection时自动调用,比如运行System.gc()的时候.

程序退出时为每个对象调用一次finalize方法。

显式的调用finalize方法

注意:

1、当一个对象不可到达时,并不是马上就被回收的。

2、JVM能够保证一个对象在回收以前一定会调用一次它的finalize()方法。这句话中两个陷阱:回收以前一定和一次

回收算法

垃圾回收器算法包含复制算法、标记清除算法和标记整理算法。

复制算法

复制算法针对新生代,新生代的区域被划分为一块较大的Eden空间和两块较小的Survivor空间。每次使用一块Eden空间和一块Survivor空间。回收时,将Eden和Survivor中还存活的对象一次性复制到另一个Survivor中,然后回收前者。当另一个Survivor空间不够时,需要老年代进行分配担保。

95cda28ef647?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

95cda28ef647?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

标记-清除算法

标记清除算法针对新生代,标记出哪些内存块需要被回收,就地回收,可能存在内存碎片。

95cda28ef647?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

95cda28ef647?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

标记-整理算法

标记整理算法针对老年代,老年代对象存活率高,使用复制算法性能低。因此采用标记整理算法。先对所有老年代的对象进行标记,找到需要被回收的对象;然后让存活的对象向一端移动,清理掉另一端的内存。

95cda28ef647?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

95cda28ef647?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

垃圾回收器简介

按执行机制划分Java有四种类型的垃圾回收器:

(1)串行垃圾回收器(Serial Garbage Collector)

(2)并行垃圾回收器(Parallel Garbage Collector)

(3)并发标记扫描垃圾回收器(CMS Garbage Collector)

(4)G1垃圾回收器(G1 Garbage Collector)

95cda28ef647?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

每种类型都有自己的优势与劣势,在很大程度上有 所不同并且可以为我们提供完全不同的应用程序性能。重要的是,我们编程的时候可以通过向JVM传递参数选择垃圾回收器类型。每种类型理解每种类型的垃圾回收器并且根据应用程序选择进行正确的选择是非常重要的。

1、串行垃圾回收器

串行垃圾回收器通过持有应用程序所有的线程进行工作。它为单线程环境设计,只使用一个单独的线程进行垃圾回收,通过冻结所有应用程序线程进行工作,所以可能不适合服务器环境。它最适合的是简单的命令行程序(单CPU、新生代空间较小及对暂停时间要求不是非常高的应用)。是client级别默认的GC方式。

通过JVM参数-XX:+UseSerialGC可以使用串行垃圾回收器。

2、并行垃圾回收器

并行垃圾回收器也叫做 throughput collector 。它是JVM的默认垃圾回收器。与串行垃圾回收器不同,它使用多线程进行垃圾回收。相似的是,当执行垃圾回收的时候它也会冻结所有的应用程序线程。

适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式。可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数。

3、并发标记扫描垃圾回收器

并发标记垃圾回收使用多线程扫描堆内存,标记需要清理的实例并且清理被标记过的实例。并发标记垃圾回收器只会在下面两种情况持有应用程序所有线程。

(1)当标记的引用对象在Tenured区域;

(2)在进行垃圾回收的时候,堆内存的数据被并发的改变。

相比并行垃圾回收器,并发标记扫描垃圾回收器使用更多的CPU来确保程序的吞吐量。如果我们可以为了更好的程序性能分配更多的CPU,那么并发标记上扫描垃圾回收器是更好的选择相比并发垃圾回收器。

通过JVM参数 XX:+USeParNewGC 打开并发标记扫描垃圾回收器。

以上各种GC机制是需要组合使用的,指定方式由下表所示:

95cda28ef647?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

垃圾回收器JVM配置

运行的垃圾回收器类型:

95cda28ef647?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

GC的优化配置:

95cda28ef647?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

使用JVM GC 参数的例子:

-server -Xms1800m -Xmx1800m -Xmn680m -Xss256k -XX:MetaspaceSize=340m -XX:MaxMetaspaceSize=340m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection -XX:+CMSClassUnloadingEnabled -XX:+DisableExplicitGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Dcom.sun.management.jmxremote.port=9981 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/admin/logs

HotSpot(JDK 7)虚拟机提供的几种垃圾收集器

垃圾收集算法是内存回收的理论基础,而垃圾收集器就是内存回收的具体实现。用户可以根据自己的需求组合出各个年代使用的收集器。

1.Serial(SerialMSC)(Copying算法)

Serial收集器是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法。

2.Serial Old (标记—整理算法)

Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。

2.ParNew (Copying算法)

ParNew收集器是新生代收集器,Serial收集器的多线程版本。使用多个线程进行垃圾收集,在多核CPU环境下有着比Serial更好的表现。

3.Parallel Scavenge (Copying算法)

Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。追求高吞吐量,高效利用CPU。吞吐量一般为99%。 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。

4.Parallel Old(ParallelMSC)(标记—整理算法)

Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。吞吐量优先。

5.CMS (标记—整理算法)

CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法。高并发、低停顿,追求最短GC回收停顿时间,CPU占用比较高。响应时间快,停顿时间短,多核CPU 追求高响应时间的选择。

6.G1

G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。

G1垃圾回收器适用于堆内存很大的情况,他将堆内存分割成不同的区域,并且并发的对其进行垃圾回收。G1也可以在回收内存之后对剩余的堆内存空间进行压缩。并发扫描标记垃圾回收器在STW情况下压缩内存。G1垃圾回收会优先选择第一块垃圾最多的区域。

通过JVM参数 –XX:+UseG1GC 使用G1垃圾回收器。

总结

新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge。

老年代收集器使用的收集器:Serial Old、Parallel Old、CMS。

95cda28ef647?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

常见问题

1、内存溢出

就是你要求分配的java虚拟机内存超出了系统能给你的,系统不能满足需求,于是产生溢出。

2、内存泄漏

是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问,该块已分配出来的内存也无法再使用,随着服务器内存的不断消耗,而无法使用的内存越来越多,系统也不能再次将它分配给需要的程序,产生泄露。一直下去,程序也逐渐无内存使用,就会溢出。

性能调优

对于GC的性能主要有2个方面的指标:

吞吐量throughput(工作时间不算gc的时间占总的时间比)

暂停pause(gc发生时app对外显示的无法响应)。

我们可以通过给 Java 虚拟机分配超大堆(前提是物理机的内存足够大)来提升服务器的响应速度,但分配超大堆的前提是有把握把应用程序的 Full GC 频率控制得足够低,因为一次 Full GC 的时间造成比较长时间的停顿。控制 Full GC 频率的关键是保证应用中绝大多数对象的生存周期不应太长,尤其不能产生批量的、生命周期长的大对象,这样才能保证老年代的稳定。

Direct Memory 在堆内存外分配,而且二者均受限于物理机内存,且成负相关关系,因此分配超大堆时,如果用到了 NIO 机制分配使用了很多的 Direct Memory,则有可能导致 Direct Memory 的 OutOfMemoryError 异常,这时可以通过 -XX:MaxDirectMemorySize 参数调整 Direct Memory 的大小。

除了 Java 堆和永久代以及直接内存外,还要注意下面这些区域也会占用较多的内存,这些内存的总和会受到操作系统进程最大内存的限制:

线程堆栈:可通过 -Xss 调整大小,内存不足时抛出 StackOverflowError(纵向无法分配,即无法分配新的栈帧)或 OutOfMemoryError(横向无法分配,即无法建立新的线程)。

Socket 缓冲区:每个 Socket 连接都有 Receive 和 Send 两个缓冲区,分别占用大约 37KB 和 25KB 的内存。如果无法分配,可能会抛出 IOException:Too many open files 异常。关于 Socket 缓冲区的详细介绍参见我的 Java 网络编程系列中深入剖析 Socket 的几篇文章。

JNI 代码:如果代码中使用了JNI调用本地库,那本地库使用的内存也不在堆中。

虚拟机和 GC:虚拟机和 GC 的代码执行也要消耗一定的内存。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值