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

1、垃圾回收只考虑堆和方法区

回收方法区:回收废弃常量和无用的类

对象存活判定算法:

1、引用计数法,缺点:很难解决循环引用的问题。

2、可达性分析算法

垃圾收集器与内存分配策略_第1张图片

可以作为GCRoots的对象:    

1、虚拟机栈中的引用对象;

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

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

4、本地方法中的JNI引用对象;

引用的类型:

    1、强引用:Object obj=new Object(),这类引用。只要引用存在垃圾回收器就不会回收掉引用的对象

    2、软引用:有用但是非必须的对象,在垃圾回收的时候先回收它们,如果回收完内存还是不够抛出异常。

    3、若引用:有用但是非必须的对象,强队比弱引用弱一些,只能活到下一次GC之前。

    4、虚引用:一个对象是否有虚引用的存在,对对象没有影响,无法通过虚引用获取实例对象。作用:在对象被垃圾回收器回收的时收到一个系统通知。

生存还是死亡:

    即使在可达性算法中不可达的对象,也并非是“非死不可”的,这个时候属于缓刑阶段。至少要经过两次标记过程:如果在进行可达性分析后发现没有与GCRoots想连接的引用链,那么它第一次被标记,并进行第一次筛选,筛选的条件是对象是否有必要执行 finalize()方法,当对象没有覆盖finallize()方法,或者finallize()已经被调用过,这两种情况就会认为没必要执行。

回收方法区:

    永久的主要回收两部分内容:废弃的常量和无用的类。

            废弃的常量:   

               1、 没有任何对象引用常量

           无用的类:

                1、该类所有的实例都被回收,也就是java堆中不存在该类的任何实例,

                2、加载该类的classloader已经被回收

                3、该类的class,无法在任何地方通过反射获得。

垃圾收集算法:

1、标记清除法:两个阶段:标记阶段和清除阶段。清除之后会产生大量的不连续的碎片

2、复制算法:代价是讲内存缩小为原来的一半,另一半用来装存活的对象。默认Eden:form=8:1

3、标记-整理压缩算法:让存活的对象向一端移动。然后直接清理掉边界意外的内存。

4、分带收集算法:新生代每次回收是发现大量的对象死去,就选用复制算法,并且还可以让老年代进行担保。而因为老年代对象存活率比较高,没有额外的空间对它进行担保,就必须使用“标记-清除”或者“标记-整理”算法进行回收。

hotSpot的算法:

    前面介绍了对象存户判定的算法和GC回收算法,在虚拟机上实现这些算法时,必须考虑执行效率,才能保证虚拟机高效运行。

     枚举根节点:

        GCRoots的节点为主要在全局性的引用(常量,静态的变量)与执行上下文中(栈帧中的本地变量表),现在很多系统的方法区就有几百兆,如果挨着检查,必然会消耗很多的时间。可达性分析对执行时间的敏感还体现在GC停顿上,分析期间,整个系统必须停在某个时间点上。

        由于目前主流的java虚拟机使用的都是准确式的GC,所以系统停顿下来不需要一个不漏的检查完所有的执行上下文和全局引用位置。例如在HotSpot的实现中使用一组成为OopMap的数据结构来实现,在类加载完成的时候,HotSpot就把对象内什么偏移量上有什么类型的数据计算出来,在JIT编译过程中,也会在特点的位置记录下栈和寄存器中那些位置是引用。这样,在GC扫描的时候就可以直接得知这些信息了。

    安全点:

        在OopMap协助下,HotSpot可以快速且准确的完成GCRoots枚举。如果OopMap内容变化指令非常多,如果为每一条都生成对应的OopMap,那么将会需要大量的额外空间。这样GC空间成本就会变得很高。

       实际上,HotSpot没有为每条指令都生产OopMap,只有在特定的位置记录了这些信息,这些信息成为安全点(Safepoint)。即程序执行时并非所有地方都能停顿下来开始GC,只有到达安全点才能暂停,安全点不能太多也不能太少,所以,安全点的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定的,因为每条指令执行的时间都非常短暂,程序不太可能因为指令流长度太长这个原因而过长时间运行,“长时间执行”的最明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生Safepoint。

        如何让所有线程跑到安全点停下来?两种方式:抢先式中断和主动式中断。第一种,在GC发生时,首先把所有线程中断,如果发现有线程不在安全点上,让他继续跑到安全点。现在几乎没有虚拟机采用了。第二种:是设置一个标志,让线程来轮询标志,通过标志和线程安全点来进行中断。

    安全区域:

        对于程序不执行就是没有分配CPU时间,典型的例子就是程序处于Sleep状态和Blocked状态,这个时候程序无法响应JVM的中断请求,“走”到安全的地方去中断挂起,JVM也显然不可能等待线程被重新分配CPU时间。

         当程序执行到Safe Region 中的代码时,首先标识自己进入了Safe Region ,那样,当这段时间里JVM要发起GC时,就不用管标识自己为Safe Region 状态的线程了,线程要离开的时候,检查系统是否完成了根节点枚举(或者整个GC过程),如果完成了,那线程继续执行,否则它就必须等待直到收到可以安全离开的信号为止。

垃圾回收器:

1、Serial收集器(新生代、标准复制算法):单线程进行收集,垃圾收集的时候,用户线程要进行等待。

2、ParNew收集器(新生代、标准复制算法):是Serial的多线程版本,只有它能够与CMS收集器配合工作,

3、Parallel Scavenge 收集器(新生代、标准复制算法、吞吐量优先):使用复制算法,是一个新生代的垃圾回收器,有自适应调节策略(根据系统信息,动态调整参数,提供最合适的停顿时间或者最大吞吐量)。与Parallel最大的区别就是拥有自适应的调节策略。

4、Serial Old 收集器(老年代、标记整理算法):单线程的,采用的是标记整理算法,

5、parallel Old 收集器(老年代,标记整理算法):Parallel Scavenge 收集器的老年代版本,吞吐量优先。

6、CMS收集器(老年代收集器、标记清除算法):以获取最短回收停顿时间为目标的收集器,流程:初始标记,并发标记,重新标记,并发清除。初始标记和重新标记会停止所有其他线程。并发标记和并发清除会与用户线程一起工作。缺点:对用户程序影响很大,无法处理浮动垃圾,采用标记清除算法会有大量的空间碎片 产生,

7、G1回收器:jdk1.9采用,新生代和老年代都代理了。特点;1、并发与并行 2、分代收集 3、空间整合 4、可预测的停顿。

回收动作:

    1、初始标记2、并发标记 3、最终标记 4、筛选回收

垃圾收集器与内存分配策略_第2张图片

jdk 1.7和jdk 1.8 采用的是:Parallel Scavenge 收集器+Parallel Old收集器

 

理解GC日志

垃圾收集器与内存分配策略_第3张图片

1、33.125和100.667,代表了GC发生的时间,从虚拟机启动以来。

2、Full GC 发生了Stop-the-world(暂停其他所有线程)不能用来区分是老年代还是新生代垃圾收集

3、[DefNew(新生带)、[Tenured(老年代)、[Perm(永久代) 表示GC发生的区域,这里显示的区域名称与使用额GC收集器是密切相关的。DefNew(Serial收集器中的新生代名为Default New Generation),如果是ParNew收集器,新生代名称为ParNew。 如果采用Parallel Scavenge收集器,那它配套的新生代名称为PSYoungGen,老年代和永久代同理,名称也是由收集器决定     

4、方括号内部:3324K->152k(3712K):GC前该内存区域已使用的容量->GC后该内存区域已使用容量(该内存区域总容量)。方括号外部:3324K->152k(11904K),表示GC前java堆已使用容量-->GC后java堆已使用容量(java堆容量)。

5、0.0025925secs,该内存区域GC所占用的时间。如:[Times: user=0.01 sys=0.0 real=0.02 secs],这里面的user、sys和real与Linux的time命令所输出的时间含义一致,分别代表,用户消耗CPU时间,内核态消耗的CPU事件和操作从开始到结束所经过的墙钟时间。CPU时间与墙钟时间的区别:墙钟时间包括各种非运算的等待耗时,例如:等待磁盘IO、等待线程阻塞、而CPU时间不包括这些耗时。但是当系统有多CPU或者多核的话,多线程操作会叠加这些CPU时间,所以读者看到user或者sys时间超过real时间是完全正常的。

内存的分配策略和回收策略:

1、对象优先再Eden分配,如果放不开,则引发第一次GC,经过一次GC之后如果form区或者to区存放不开对象,那么就会进入老年代。

2、大对象直接进入老年代

3、长期存活的对象将进入老年代:根据MaxTenuringThreshold的值判断,如果如果为1,代表经历过一次回收之后,再次进入回收那么会进入老年代。代表多大年龄要进入老年代。

4、动态年龄判定:如果在from或to区中相同年龄所有对象大小总和大于from或者to区的一半,大龄大于或等于该年龄的对象就可以直接进入老年代。

5、空间分配担保:Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象空间,GC是安全的。

Minor GC ,Full GC 触发条件是什么

  • 从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC
  • 对老年代GC称为Major GC
  • Full GC是对整个堆来说的

在最近几个版本的JDK里默认包括了对永生带即方法区的回收(JDK8中无永生带了),出现Full GC的时候经常伴随至少一次的Minor GC,但非绝对的。Major GC的速度一般会比Minor GC慢10倍以上。下边看看有那种情况触发JVM进行Full GC及应对策略。

Minor GC触发条件:

当Eden区满时,触发Minor GC。

Full GC触发条件:

(1)System.gc()方法的调用

此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI(Java远程方法调用)调用System.gc。

(2)老年代空间不足

旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误: java.lang.OutOfMemoryError: Java heap space 为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。

(3)方法区空间不足

JVM规范中运行时数据区域中的方法区,在HotSpot虚拟机中又被习惯称为永生代或者永生区,Permanet Generation中存放的为一些class的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下也会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:

java.lang.OutOfMemoryError: PermGen space

为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。

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

如果发现统计数据说之前Minor GC的平均晋升大小比目前old gen剩余的空间大,则不会触发Minor GC而是转为触发full GC

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

CMS收集器过程:

1、初始标记

2、并发标记

3、重新标记

4、并发清除

G1垃圾回收器收集过程:

1、初始标记

2、并发标记

3、最终标记

4、筛选回收

你可能感兴趣的:(垃圾收集器与内存分配策略,JVM垃圾回收,JVM)