JVM内存管理和垃圾回收

1.  浅谈Java的GC

Java的优势是,可以通过自己的JVM自动分配和回收内存空间

GC是后台一个低优先级的守护进程。在内存中低到一定限度时才会自动运行,因此垃圾回收的时间是不确定的

为何要这样设计:因为GC也要消耗CPU等资源,如果GC执行过于频繁会对Java的程序的执行产生较大的影响,因此实行不定期的GC

GC有关的是:JVM运行时数据区中的堆(对象实例会存储在这里)和 gabagecollector方法。

垃圾回收GC只能回收通过new关键字申请的内存(在堆上),但是堆上的内存并不完全是通过new申请分配的。还有一些本地方法,这些内存如果不手动释放,就会导致内存泄露,所以需要在finalize中用本地方法(nativemethod)free操作等,再使用gc方法

2.  何为垃圾,对象的消亡。

Java中那些不可达的对象就会变成垃圾。对象之间的引用可以抽象成树形结构,通过树根(GC Roots)作为起点,从这些树根往下搜索,搜索走过的链称为引用链。(可达性分析)

当一个对象到GC Roots没有任何引用链相连时,则证明这个对象为可回收的对象。

3.  四种引用类型

3.1  强引用

Object obj = new Object();

这里的obj引用便是一个强引用,强引用不会被GC回收。即使抛出OutOfMemoryError错误,使程序异常终止。

 

3.2  软引用

如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存软引用可用来实现内存敏感的高速缓存

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

 

3.3  弱引用

弱引用与软引用的区别在于垃圾回收器一旦发现了弱引用的对象,不管当前内存空间足够与否都会回收它的内存。不过由于垃圾回收器是一个优先级很低的线程因此不一定会很快发现那些弱引用的对象。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

 

3.4     虚引用

虚引用必须和引用队列ReferenceQueue)联合使用当垃圾回收器发现一个对象有虚引用时,首先执行所引用对象的finalize()方法,在回收内存之前,把这个虚引用对象加入到引用队列中你可以通过判断引用队列中是否有该虚引用对象,来了解这个对象是否将要被垃圾回收。然后就可以利用虚引用机制完成对象回收前的一些工作

这里特别需要注意:当JVM将虚引用插入到引用队列的时候,虚引用执行的对象内存还是存在的。但是PhantomReference并没有暴露API返回对象。所以如果我想做清理工作,需要继承PhantomReference类,以便访问它指向的对象。

4.  典型的垃圾回收算法

在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收

下面讨论几种常见的垃圾收集算法

4.1     Mark-Sweep(标记-清除)算法

标记-清除算法分为两个阶段:标记阶段和清除阶段。

标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。

标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发GC

4.2    Copying(复制)算法

Copying算法将可用内存按容量划分为大小相等的两块每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把第一块内存上的空间一次清理掉,这样就不容易出现内存碎片的问题,并且运行高效

但是该算法导致能够使用的内存缩减到原来的一半。而且,该算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。(这也是为什么后面提到的新生代采用Copying算法)

4.2    Mark-Compact(标记-整理)算法

为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。

该算法标记阶段标记出所有需要被回收的对象,但是在完成标记之后不是直接清理可回收对象,而是将存活的对象都移向一端,然后清理掉端边界以外的所有内存(只留下存活对象)

4.4    Generational Collection(分代收集)算法

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。

它的核心思想是将堆区划分为老年代Tenured Generation)和新生代Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以在不同代的采取不同的最适合的收集算法

目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,该算法效率在新生代也较高。但是实际中并不是按照11的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间两块较小的Survivor空间(比例8:1:1)每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将还存活的对象复制到另一块Survivor空间中,然后清理掉EdenA空间。在进行了第一次GC之后,使用的便是Eden spaceB空间了,下次GC时会将存活对象复制到A空间,如此反复循环。

当对象在Survivor躲过一次GC的话,其对象年龄便会加1,默认情况下,对象年龄达到15时,就会移动到老年代中。一般来说,大对象会被直接分配到老年代,所谓的大对象是指需要大量连续存储空间的对象,最常见的一种大对象就是大数组,比如:byte[] data = newbyte[4*1024*1024]

当然分配的规则并不是百分之百固定的,这要取决于当前使用的是哪种垃圾收集器组合和JVM的相关参数。这些搬运工作都是GC完成的GC不仅负责在Heap中搬运实例,同时负责回收存储空间。

最后,因为每次回收都只回收少量对象,所以老年代一般使用的是标记整理算法。

注意,在方法区中有一个永久代Permanet Generation),它用来存储class文件、静态对象、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类

5.  典型的垃圾回收器

垃圾收集算法是内存回收的理论基础,而垃圾收集器就是内存回收的具体实现。

下面介绍一下HotSpotJDK 7)虚拟机提供的几种垃圾收集器,用户可以根据自己的需求组合出各个年代使用的收集器。

1.Serial&Serial Old

Serial和Serial Old收集器是最基本最古老的收集器,是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。

2.ParNew

ParNew收集器是Serial收集器的多线程版本,使用多个线程进行垃圾收集。

3.Parallel Scavenge

Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器)其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量

4.Parallel Old

Parallel OldParallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法

5.CMS

CMSCurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法

6.G1

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值