java 垃圾回收机制

前言
本文章主要了解一下java 垃圾回收机制的基本概念,垃圾回收的种类,以及垃圾回收的工作流程,了解这些都是为了我们能对垃圾回收做一些优化。

1.自动垃圾回收机制
自动垃圾回收机制是指查看堆内存、区分在使用的对象和未使用的对象、删除未使用对象的一个过程。对于使用对象和引用对象,是指在程序中有一处对于这个的引用。对于未使用对象和未引用对象,是指在程序中没有该对象的引用。所以,在程序中没有一处引用的对象可以释放内存,以便其他对象重新使用。
对于内存泄漏,是指那些对象的生命周期超过程序需要这个对象的时间,导致内存没有回收。可能是对象的引用一直存在之类导致。
当需要更多的内存来存放新的对象,同时存在未引用的对象,那么就会删除未引用对象。
2.垃圾回收过程

  • 标记
    首先jvm会扫描一遍所有的对象,区分出还在使用的对象和未使用的对象,分别打上标记。
  • 删除未使用内存
    正常删除未使用的内存,也可以压缩删除,压缩删除是指删除后将在使用的对象重新压缩到一起,方便重新管理、分配新的内存。

3.垃圾回收的分代
java 中堆的存放对象的空间被分为三个代,年轻代,老年代,持久代。

  • 年轻代
    刚创建的对象是在年轻代中分配内存的,对象在年轻代中逐渐变老。当年轻代中被填满的时候就会触发一个小收集,这是由于内存普遍存活的时间短,所以才设计年轻代和老年代,并分别在两个年代中进行一次回收。年轻代中的特点是死亡的对象比较多。幸存下来的对象到达阈值,就移到老年代中。
  • 全局暂停事件
    当发生小收集的时候,所有的应用线程都会暂停,直到小收集完成。
  • 老年代
    老年代是指当年轻代中的对象存活了一定时间之后,就移到老年代中。随着时间的推移,老年代也会被填满,这时候会触发大收集事件。
    同样的,大收集也会触发全局暂停事件。
  • 持久代
    持久代中存放的是jvm运行的类所需要的对象。
    在这里插入图片描述

年轻代(Young Generation)

1.所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。

2.新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。

3.当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收

4.新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)

年老代(Old Generation)

1.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

2.内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。

持久代(Permanent Generation)

用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。
4.垃圾回收的执行机制
由于对垃圾回收进行了分代处理,所以垃圾回收的区域以及时间也不一样。垃圾回收分为Scanenge gc 和 Full gc.

  • Scanenge gc

一般情况下,对象都是在Eden堆内存中开始的,当新对象生成,并且申请内存失败的时候,就会触发 scanenge gc。清理Eden区未引用的对象,将存活的对象放到survivor区。Eden去分配的不会很大,所以gc会频繁的进行。需要的算法也是要快速、高效进行垃圾回收。

  • Full gc

对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:

1)年老代(Tenured)被写满
2)持久代(Perm)被写满
3)System.gc()被显示调用
4)上一次GC之后Heap的各域分配策略动态变化

5.垃圾回收的相关配置命令

参数描述
-XmsJVM启动的时候设置初始堆的大小
-Xmx设置最大堆的大小
-Xmn设置年轻代的大小
-XX:PermSize设置持久代的初始的大小
-XX:MaxPermSize设置持久代的最大值

1、串行收集器:
在Java SE 5和6中,串行收集器是客户端环境(client-style machines)机器的默认设置。在这种情况下,小垃圾收集和大垃圾收集都是串行进行的(使用单个的虚拟CPU)。

使用的算法说明:

串行收集器在年轻代使用的是拷贝算法,这个算法比较简单,在这里不做详述。而年老代和持久代使用标记-清扫-压缩(mark-sweep-compact)算法。标记阶段,收集器识别哪些对象仍然活着。清扫阶段“扫荡”整个代,识别垃圾。之后,收集器执行平移压缩(sliding compaction),将存活的对象平移到代的前端(持久代类似),相应的在尾部留下一整块连续的空闲空间。压缩后,以后的分配就可以在年老代和持久代使用空闲指针(bump-the-pointer)技术。这种压缩算法能够在堆上迅速分配内存块。

示例:大多数客户端式(client-style machines)机器上运行的应用程序通常都是选择串行收集器,这些应用对短暂停没有要求。它之所以叫这个名字,是因为它能充分利用单个虚拟处理器进行垃圾回收的工作。在今天的硬件上,串行收集器可以有效的管理许多拥有几百M堆内存的重要应用程序,并且拥有相对短的最坏暂停(Full GC仅有几秒左右)。

在有大量JVM运行在同一个机器上(在某些情况下,JVM的个数比可以用的处理器的个数多)的应用环境下,串行垃圾收集器也被广泛使用。在这种环境下,要进行垃圾回收的JVM最好使用一个处理器,虽然这样会使垃圾回收的时间变得更长,但可以降低与其他JVM的冲突。这时,使用串行垃圾回收器能够获得很好的权衡。最后,如果在较小的内存和较少的CPU核心上对硬件进行稍加扩充,将能获得更好的性能。

命令行参数:

使用串行垃圾回收器 -XX:+UseSerialGC

给事例应用使用串行垃圾回收器的命令行如下:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar

c:\javademos\demo\jfc\Java2D\Java2demo.jar
2、并行收集器
并行垃圾收集器在年轻代使用多线程进行垃圾回收。默认情况下,在N个CPU的主机上,并行垃圾收集器使用N个垃圾收集器线程进行垃圾回收。垃圾收集器线程的个数可以在命令行进行设置:-XX:ParallelGCThreads=<期望的数值>

在单核的CPU上,尽管我们请求设置的是并行垃圾收集器,但JVM还是使用默认的垃圾收集器。在两个CPU的主机上,并行垃圾收集器与默认的串行垃圾收集器所表现出来的性能相当,年轻代的垃圾收集器暂停时间与两个以上CPU的主机相比也有所减少。并行垃圾收集器有两种使用方式。

使用的算法说明:

年轻代:与串行垃圾收集器年轻代相同的拷贝算法,只不过是该算法的并行版本,使用多个CPU并行的运行,减少了垃圾收集的开销,因此增加了吞吐量。

年老代:与串行垃圾收集器老年代想听的标记-清扫-压缩(mark-sweepcompact)算法,只不过是该算法的并行版本。

示例:并行收集器也叫做吞吐量收集器,因为其可以使用多个CPU来增大应用程序的吞吐量。当应用程序需要处理大量的工作同事可以接受较长的暂停时,可以使用并行垃圾收集器。例如,想打印报告或者账单这样的批处理,或者进行大量的数据库查询。

-XX:+UseParallelGC

使用这个命令行参数,就会将年轻代设置为多线程的收集器,老年代使用单线程的收集器。该选项,还会在老年代进行单线程的压缩工作。

启动示例应用程序Java2Demo的命令行如下:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelGC -jar
c:\javademos\demo\jfc\Java2D\Java2demo.jar

-XX:+UseParallelOldGC
使用该参数,年轻代和老年代都会使用多线程的收集器,同时,也使用多线程的压缩收集器。HotSpot仅仅在老年代进行整理,在年轻代是一个复制收集器,因此没必要进行整理。

压缩描述的是这样一种行为,移动对象使得个对象之间没有空闲位置。再一次垃圾收集的清理之后,存活对象在内存中的存储位置之间可能存在空闲区。整理移动对象,使得对象的存储都是顺序的,彼此之间没有空闲区。垃圾收集器可能也是一个不带压缩的收集器。所以,并行收集器和并行压缩收集器之间的区别就是后者在垃圾收集清理操作之后,对内存空间进行一次整理。

启动示例应用程序Java2Demo的命令行如下:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelOldGC -jar
c:\javademos\demo\jfc\Java2D\Java2demo.jar

3、并发标记清理收集器
并发标记清理收集器(CMS,又叫作并发低暂停收集器)在老年代进行收集。由于垃圾收集能使用应用线程的并发进行大多数的垃圾收集工作,所以它降低了应用程序的暂停时间。

正常说来,并发低暂停的收集器对存活对象不进行复制和压缩的工作。这种情况下,垃圾收集器没有移动任何存活对象。如果因此而带来了内存的碎片问题,那就为其分配一个更大的堆。

注意:CMS收集器在年轻代使用和并行收集器一样的算法。

示例:CMS收集器常常应用于需要低暂停及可以与垃圾收集器共享资源的场景。例如:桌面UI应用程序对事件的响应,Web服务器对请求的响应,以及数据库对查询请求的响应。

命令行参数:

如果要使用CMS收集器,使用 -XX:+UseConcMarkSweepGC ,同时,可以设置并发的线程数目 -XX:ParallelCMSThreads= 。

启动示例应用程序Java2Demo的命令行如下:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseConcMarkSweepGC -XX:ParallelCMSThreads=2 -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar

6.有了垃圾回收仍然有内存泄漏问题
1.静态集合类像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着。

Static Vector v = new Vector(); 
for (int i = 1; i<100; i++) 
{ 
    Object o = new Object(); 
    v.add(o); 
    o = null; 
}

在这个例子中,代码栈中存在Vector 对象的引用 v 和 Object 对象的引用 o 。在 For 循环中,我们不断的生成新的对象,然后将其添加到 Vector 对象中,之后将 o 引用置空。问题是当 o 引用被置空后,如果发生 GC,我们创建的 Object 对象是否能够被 GC 回收呢?答案是否定的。因为, GC 在跟踪代码栈中的引用时,会发现 v 引用,而继续往下跟踪,就会发现 v 引用指向的内存空间中又存在指向 Object 对象的引用。也就是说尽管o 引用已经被置空,但是 Object 对象仍然存在其他的引用,是可以被访问到的,所以 GC 无法将其释放掉。如果在此循环之后, Object 对象对程序已经没有任何作用,那么我们就认为此 Java 程序发生了内存泄漏。
2.各种连接,数据库连接,网络连接,IO连接等没有显示调用close关闭,不被GC回收导致内存泄露。
3.监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。

https://www.cnblogs.com/shudonghe/p/3457990.html
https://www.cnblogs.com/andy-zcx/p/5522836.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值