欢迎您光临本小站。希望您在这里可以找到自己想要的信息。。。

深入理解java虚拟机(二)

java water 2322℃ 0评论

垃圾收集器与内存分配策略

当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们需要对这些自动化技术内存动态分配与内存回收的技术实施必要的监控和调节

对象已死

垃圾收集器在对堆进行回收之前,第一件事就是确定这些对象有哪些存活哪些死去(不可能再被任何途径使用的对象)

引用计数算法:很难解决对象之间相互循环应用的问题

根搜索算法:主流商用语言都是使用的该算法(javaC#),通过一系列的名为“GC Roots”的对象作为起点,开始向下搜索,搜素所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论来说就是从GC Roots到这个对象不可达)则证明此对象是不可用的。

GC Roots对象包括 1.虚拟机栈(栈帧中的本地变量表)中的应用对象2.方法区中的类静态属性引用的对象3.方法区中的常量引用的对象4.本地方法栈中JNI(即一般说的Native方法)引用的对象

再谈引用:无论通过引用计数、还是通过搜索算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。Jdk1.2之前,java中的应用的定义很传统:如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。这种定义,太狭隘,这种定义只有被引用或者没有被引用两种状态,对于描述一些“食之无味,弃之可惜”的对象无能为力。我们希望描述这样一类对象:内存足够时,则保留,内存在垃圾回收后还紧张,则抛弃。很多系统的缓存功能都符合这种应用场景。

JDK1.2之后,java对引用概念进行扩充,将引用分为(强、弱、软、虚)引用,这四种引用强度依次减弱

强引用:类似Object obj = new Object(),只要强引用存在,永远不会回收引用的对象

软:有用,并非必需的对象。系统发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行二次回收

弱:非必须对象,弱引用的对象只能生存到下一次垃圾收集发生之前

虚:幽灵引用或者幻影引用,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响。无法通过虚引用获取一个对象实例。设置虚引用关联的唯一目的是希望能在对象被回收之前收到一个系统通知

生存还是死亡:根据搜索算法不可达的对象,这时候处于缓刑阶段,宣告死亡,至少经历两次标记过程

回收方法区:永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类

垃圾收集算法

标记清除算法:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。它的缺点:效率问题,效率都不高;另一个空间问题,标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存。

复制搜集算法:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。只是这种算法的代价是将内存缩小为原来的一半。现在的商用虚拟机都踩哦用这种收集算法来回收新生代,IBM的专门研究表明,新生代的对象98%是朝生夕死的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的survivor空间,每次使用Eden和其中的一块Survivor。当回收时,将EdenSurvivor中还存活着的对象一次性拷贝到另外一块Survivor空间上,最后清理掉Eden和刚才使用过的Survivor空间。HotSpot虚拟机默认EdenSurvivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%80%+10%),只有10%的内存是会被浪费的。当然,98%的对象可回收只是一般场景下的数据,我们没办法保证每次回收都只有不多于10%对象存活。当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保。如果另外一块Survivor空间没有满足的空间存放上一次新生代收集下来的存活对象,这些对象将直接分配担保机制进入老年代。

标记整理算法:复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

根据老年代的特点,提出了标记整理算法,标记过程仍然与标记清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。

分代收集算法:当前商业虚拟机的垃圾收集都采用“分代收集”算法,根据对象存活周期的不同将内存划分为几块。一般是把java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法。而老年代中因为对象存活率高,没有额外空间对它进行分配担保,就必须使用“标记清理”或“标记整理”算法进行回收

垃圾收集器

收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。虚拟机规范中对垃圾收集器应该如何实现没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大的差别,并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。

Serial收集器:单线程的收集器,Stop The World,它依然是虚拟机运行在Client模式下的默认新生代收集器。优点:简单而高效。

ParNew收集器:Serial收集器的多线程版本。它是许多运行中Server模式下的虚拟机中首选的新生代收集器。除了Serial收集器外,目前只有它能与CMS收集器配合工作。

Parallel Scavenge收集器:一个新生代收集器,使用复制算法,并行的多线程收集器。关注点不一样,其他收集器是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标是达到一个可控制的吞吐量。吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),停顿时间越短就越适合需要和用户交互的程序,而高吞吐量则可以提高效率地利用cpu时间,尽快完成程序的运算任务,主要适合在后台运算而 不需要太多交互的任务。-XX:+UseAdaptiveSizePolicy打开自适应调节策略。

Serial OldSerial收集器的老年代版本,使用标记整理算法。这个收集器也是在Client模式下的虚拟机使用。如果Server模式下,两大用途,一是在jdk1.5之前的版本与Parallel Scavenge搭配使用,另外是作为CMS收集器的后备预案。

Parallel OldParallel Scavenge收集器的老年代版本。使用多线程和“标记整理”算法。这个收集器是在Jdk1.6中开始使用,可与Parallel Scavenge配合使用

CMS是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务器端上,这类应用尤其重视服务的响应速度。基于“标记清理”算法实现。优点:并发收集、低停顿。

缺点:1CPU资源非常敏感,回收线程数是(cpu数量+3)/42.无法处理浮动垃圾 3.“标记清理”会产生大量空间碎片

G1收集器:最前沿成果,基于“标记整理”,非常精确控制停顿。可以实现在基本不牺牲吞吐量的前提下完成低停顿的内存回收,极力避免全区域的垃圾收集,而G1将整个Java堆(包括新生代、老年代)划分多个大小固定的独立区域,并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据运行的收集时间,优先回收垃圾最多的区域(这就是Garbage First)名称的来由。

内存分配与回收策略

自动化内存管理解决两个问题:给对象分配内存以及回收分配给对象的内存

对象优先在Eden分配   -XX:PrintGCDetails  新生代GCMinor GC,老年代(Major GC/Full GC)。

大对象直接进入老年代  比遇到大对象更加坏的消息是遇到一群“朝生夕死”的短命大对象,经常出现大对象容易导致内存还有不少空间就提前触发垃圾收集以获得足够连续空间安置它们,(新生代采用复制算法收集内存),-XX:PretenureSizeThreshold参数设置,令大于设置值的对象直接在老年代,减少Eden和两个Survivor之间的内存拷贝

长期存活的对象将进入老年代 虚拟机既然采用了分代收集的思想管理内存,那内存回收时必须能识别哪些对象应当放在新生代,哪些放到老年代。为了做到这点,虚拟机给每个对象定义一个对象年龄计数器。经过一次Minor GC对象年龄加一,当年龄大到一定程度(默认15岁),就会晋升到老年代。可以通过-XX:MaxTenuringThreshold设置

动态对象年龄判定:虚拟机并不总是要求对象年龄必须达到阀值才晋升老年代,如果Survivor空间中相同年龄所有对象大小总和大于Survivor空间一半,年龄大于或等于该年龄的对象就直接进入老年代

空间分配担保:在每次发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则直接进行一次Full GC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败,如果允许,那只进行Minor GC; 如果不允许,则改为进行一次FullGC。虽然担保失败时绕的圈子是最大的,但大部分情况下还是会开关打开,避免Full GC过于频繁。

 

总结:内存回收与垃圾收集器在很多时候是影响系统性能、并发能力的主要因素之一。选择最优的收集方式才能获得最好的性能

转载请注明:学时网 » 深入理解java虚拟机(二)

喜欢 (0)or分享 (0)

您必须 登录 才能发表评论!