JVM的分区+查看GC对象是否存活+3种GC算法+7种垃圾收集器+如何减小GC次数

1、JVM的分区:java

 

一、程序计数器(私有)程序员

程序计数器是一块较小的内存分区,你能够把它看作当前线程所执行的字节码的指示器算法

在虚拟机的概念模型里,字节码解释器工做时,就是经过改变计数器的值来选择下一条须要执行的字节码指令。多线程

程序技术器为线程私有,每一个线程都有它们各自的程序计数器,这样再多线程的状况下,线程之间的来回切换,也能正确找到上次切换时执行的位置。并发

若是线程正在执行的是一个Java方法,那么程序计数器记录的是当前线程正在执行的字节码指令的地址;若是线程正在执行的是一个native方法,则计数器值为空。函数

此内存区域是惟一一个Java虚拟机规范中没有规定任何OutOfMemoryError(OOM)状况的区域。布局

二、Java虚拟机栈(私有)spa

虚拟机栈也为线程私有的,它的生命周期与线程相同线程

虚拟机栈能够看作是Java方法执行的内存模型:每一个方法执行的同时都会建立一个栈帧用于存储局部变量表、操做数栈、动态连接、方法出等信息。一个Java方法从调用到执行完的过程,就对应着一个栈帧从虚拟机栈入栈到出栈的过程;方法调用时,会建立栈帧在栈中,调用完是程序自动出栈释放,而不是gc释放。设计

局部变量表中存放了编译期可知的基本数据类型、对象引用、returnAddress类型(指向了一条字节码指令的地址);

在虚拟机栈中可能会出现两种异常:StackOverflowError和OutOfMemory

StackOverflowError:若是线程请求的栈深度大于当前虚拟机所容许的深度,会抛出该异常;

OutOfMemory:若是虚拟机栈能够动态扩展,当扩展时没法申请到足够的内存,会抛出该异常;

三、本地方法栈(私有)

本地方法栈相似与虚拟机栈,它们不一样之处在于,虚拟机栈是为虚拟机执行的Java方法服务,而本地方法栈是为虚拟机使用到的Native方法服务

在HotSpot虚拟机中直接把本地方法栈和虚拟机栈合二为一;

在本地方法栈可能会出现两种异常:StackOverflowError和OutOfMemory

四、Java堆(共享)

Java堆是被全部线程共享的一块区域,它也是Java虚拟机管理的内存中最大的一块,它在虚拟机启动时建立

Java堆惟一的目的就是存放对象实,几乎全部的对象实例的都在这里分配内存;

Java堆是垃圾收集器管理的主要区域,所以不少时候也被称为GC堆

Java堆能够处于物理上不连续的内存空间中,只要逻辑上连续便可,在实现时既能够是固定大小也能够是可扩展的,若是堆中没有内存完成实例分配,而且堆也没法再扩展时,将会抛出OutOfMemory异常;

五、方法区(共享)

方法区也是内存共享的一块区域,它用于存放已被虚拟机加载的类信息常量静态变量、编译器编译后的代码等数据;

在HotSpot虚拟机中,一般把方法区称之为永久代,本质上二者并不相同,只是HotSpot虚拟机的设计团队使用永久代来实现方法区;

方法区中,垃圾收集比较少见,但并非不进行GC,这个区域的回收目标主要是针对常量池的回收和对类型的卸载

方法区相似于Java堆,不要连续的内存和能够选择固定大小或者可扩展。它还能够选择不实现垃圾收集;

当方法区没法知足内存分配需求时,会抛出OutOfMemory异常;

方法区中还存在一个运行时常量池(字符串常量池也在这个里面),常量池用于存放编译期生成的各类字面量和符号引用,它具备动态性,不要求常量必定只有编译期才能产生,运行期间也可能将新的常量放入池中

 

2、堆对象的分配:

1)大多数状况下,对象在新生代 eden 区中分配,当 Eden 区中没有足够的内存空间进行分配时,虚拟机将发起一次 minor GC {minor gc:发生在新生代的垃圾收集动做,很是频繁,通常回收速度也比较快 full gc:发生在老年代的 gc}。虚拟机给每个对象定义一个对象年龄计数器,若对象在 eden 出生并通过第一次 minor gc 后仍然存活,而且能被 survivor 容纳的话,将被移到 survivor 空间中,而且对象年龄设为1.对象在 survivor 中每熬过一次 minor   gc,年龄就+1,当他年龄达到必定程度(默认为 15), 就会晋升到老年代。

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

3)长期存活的对象将进入老年代

4)若在 survivor 空间中相同年龄全部对象大小的总和>survivor空间的一半,则年龄>=该年龄的对象直接进入老年代,无需等到MaxTeuringThreshold(默认为15)中的要求。

 

当程序运行时,至少会有两个线程开启启动,一个是咱们的主线程,一个时垃圾回收线程, 垃圾回收线程的priority(优先级)较低
垃圾回收器会对咱们使用的对象进行监视,当一个对象长时间不使用时,垃圾回收器会在空闲的时候(不定时)对对象进行回收,释放内存空间,程序员是不能够显示的调用垃圾回收器回收内存的,可是可使用System.gc()方法建议垃圾回收器进行回收,可是垃圾回收器不必定会执行。
Java的 垃圾回收机制能够有效的防止内存溢出问题,可是它并不能彻底保证不会出现内存溢出。例如当程序出现严重的问题时,也可能出现内存溢出问题。
真正宣布一个对象死亡,至少须要经历2次标记过程。见下面的“可达性分析法”。因此进入“DEAD”状态的线程还能够恢复,不必定被回收。
 

3、查看GC对象是否存活的方法:

1)引用计数法

基本思想:给对象中添加要给引用计数器,每当一个地方引用时,计数器值+1,当引用失效时,计数器值-1,任什么时候刻计数器为0的对象就不可能再被使用。

缺点:很难解决对象之间循环引用的问题。

2)可达性分析法

基本思想:经过一系列的称为“GC roots”的对象做为起始点,从这些节点,开始向下搜索,搜索所走过的路径称为引用链,当一个对象到 GC root 没有任何引用链相连(用图论的话来讲,就是从 GC roots 到这个对象不可达),则证实此对象是不可用的。

若对象在进行可达性分析后发现没有与 GC roots 相链接的引用链,那么他将会被第一次标记并进行一次筛选,筛选的条件是该对象是否有必要执行 finalize()方法,当对象没有重写

finalize()方法或者  finalize()方法已经被虚拟机调用过,虚拟机将这两种状况都视为不必执行。

若该对象被断定为有必要执行 finalize 方法,则这个对象会被放在一个 F-Queue 队列,

finalize 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-queue 中的对象进行第二次小规模的标记,若对象要在 finalize 中成功拯救本身—只要从新与引用链上的任何一个对象创建关联便可(便可以重写finalize()方法来实现,好比能够将本身赋值给某个类变量或者对象的成员变量),那么在第二次标记时他们将会被移出“即将回收”集合。

任何一个对象的 finalize()方法都只会被系统调用一次。

 

可做为 GC roots 的对象

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

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

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

4)本地方法栈中 JNI 引用的对象

引用强度:强引用>软引用>弱引用>虚引用

1)强引用:相似Object obj = new Object()的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

2)软引用:用来描述一些还有用但并不是必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常以前,将会把这些对象列进回收范围之中进行第二次回收。若是此次回收尚未足够的内存,才会抛出内存溢出异常。SoftReference类实现软引用。

3)弱引用:用来描述非必须对象,但其强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生以前。当垃圾收集器工做时,不管当前内存是否足够,都会回收掉只被弱引用关联的对象。WeakReference类实现弱引用。

4)虚引用:一个对象是否有虚引用的存在,彻底不会对其生存时间构成影响,也没法经过虚引用来取得一个对象实例。为一个对象设置虚引用关联的惟一目的就是能在这个对象被收集器回收时收到一个系统通知。PhantomReference类实现虚引用。

 

内存分配规则:

1.对象优先分配在Eden区,若是Eden区没有足够的空间,执行一次Minor GC.

2.大对象直接进入老年代。

3.长期存活的对象进入老年代。虚拟机为每一个对象定义了一个年龄计数器,若是对象通过了1次Minor GC那么对象会进入Survivor区,以后每通过一次Minor GC年龄+1,达到16,进入老年代。

4.动态判断对象的年龄。若是Survivor区中相同年龄的全部对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象能够直接进入老年代。

5.空间分配担保。

 

4、3种GC算法:

标记-清除算法(老年代):分为“标记”和“清除”两个阶段:首先标记出全部须要回收的对象,在标记完成后统一回收全部被标记对象。

缺点:1)产生大量不连续的内存碎片 2)标记和清除效率都不高

复制算法(新生代):它将可用内存按照容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,则就将还存活的对象复制到另外一块上面,而后再把已经使用过的内存空间一次清理掉。使得每次都是对整个半区进行内存回收。

优势:1)不会出现内存碎片。2)只需移动堆顶指针,按顺序分配内存便可,实现简单,运行高效。

缺点:1)将内存缩小为原来的一半。2)在对象存活率较高时会进行较多复制操做,效率较低。

商业虚拟机的分配担保机制:将内存分为一块较大的 eden 空间和两块较小的 survivor 空间,默认比例是 8:1:1,即每次新生代中可用内存空间为整个新生代容量的 90%,每次使用 eden 和其中一个 survivour。当回收时,将 eden 和 survivor 中还存活的对象一次性复制到另一块 survivor 上,最后清理掉 eden 和刚才用过的 survivor,若另一块 survivor 空间没有足够内存空间存放上次新生代收集下来的存活对象时,这些对象将直接经过分配担保机制进入老年代。

标记-清理算法(老年代): 标记过程和“标记-清除”算法同样,但后续步骤不是直接对可回收对象进行清除,而是让全部存活对象都向一端移动,而后直接清理掉端边界之外的内存。

 

5、7种垃圾收集器,前 3 个是新生代,后 3 个是老年代:

并行收集和并发收集的区别:

(1)并行(Parallel)

       指多个垃圾收集线程并行工做,但此时用户线程仍然处于等待状态;

       如ParNew、Parallel Scavenge、Parallel Old;

(2)并发(Concurrent)

       指用户线程与垃圾收集线程同时执行(但不必定是并行的,可能会交替执行);用户程序在继续运行,而垃圾收集程序线程运行于另外一个CPU上;    

       如CMS、G1(也有并行);

(1)  serial (复制算法):单线程(单线程的意义不只仅说明它会使用一个 cpu或一条垃圾收集线程去完成垃圾收集工做,更重要的是在它进行垃圾收集的时候,必须暂停其余全部工做线程,直到他收集结束)。

应用场景:

对于运行在 client 模式下的虚拟机来讲是个很好的选择。

优势:

1)简单高效(与其余收集器的单线程相比);

2)对于限定单个CPU的环境来讲,Serial收集器没有线程交互(切换)开销,能够得到最高的单线程收集效率;

3)在用户的桌面应用场景中,可用内存通常不大(几十M至一两百M),能够在较短期内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是能够接受的。

 

(2)  parNew (复制算法):serial 收集器的多线程版本,是许多运行在 server 模式下的虚拟机首选的新生代收集器。

 应用场景:

Server模式下,ParNew收集器是一个很是重要的收集器,由于除Serial外,目前只有它能与CMS收集器配合工做;但在单个CPU环境中,不会比Serail收集器有更好的效果,由于存在线程交互开销。

 

(3)  parallel scaverge(复制算法):其余与ParNew相似,特别之处在于:CMS等收集器的关注点是尽量地缩短垃圾收集时用户线程的停顿时间;而其目标是达到一个可控制的吞吐量,适合在后台运算,没有太多的交互。

应用场景:

高吞吐量为目标,即减小垃圾收集时间,让用户代码得到更长的运行时间;当应用程序运行在具备多个CPU上,对暂停时间没有特别高的要求时,即程序主要在后台进行计算,而不须要与用户进行太多交互;例如,那些执行批量处理、订单处理、工资支付、科学计算的应用程序。

 

(4)  serial old(标记-清理):serial 的老年代版本,单线程,

应用场景:

主要用于Client模式;而在Server模式有两大用途:

(A)、在JDK1.5及以前,与Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配);

(B)、做为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用(后面详解);

 

(5)  parallel old(标记-清理):parallel scaverge 老年代的版本,多线程 

应用场景:

JDK1.6及以后用来代替老年代的Serial Old收集器;特别是在Server模式,多CPU的状况下;这样在注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge加Parallel Old收集器的"给力"应用组合。

 

(6)  cms(标记-清除) :一种以获取最短回收停顿时间为目标的收集器 “标记-清除”,有 4 个过程:

1)初始标记(仅标记一下GC Roots能直接关联到的对象;速度很快;但须要"Stop The World";)

2)并发标记(进行GC Roots Tracing的过程;刚才产生的集合中标记出存活对象;应用程序也在运行;并不能保证能够标记出全部的存活对象;)

3)从新标记(为了修正并发标记期间因用户程序继续运做而致使标记变更的那一部分对象的标记记录;须要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;采用多线程并行执行来提高效率;)

4)并发清除 (回收全部的垃圾对象)。

优势:并发收集,低停顿;

缺点:

1)不能处理浮动垃圾,因为 cms 并发清除阶段,用户线程还在继续执行,伴随程序进行,还有新的垃圾产生,这一部分垃圾发生在标记以后,cms 没法在当次收集时处理他们,只能留到下一次gc。可能出现"Concurrent Mode Failure"失败。这时JVM启用后备预案:临时启用Serail Old收集器,而致使另外一次Full GC的产生。

2)对 cpu 资源敏感。并发收集虽然不会暂停用户线程,但由于占用一部分CPU资源,仍是会致使应用程序变慢,总吞吐量下降。cms 默认启动的回收线程数是(cpu 数量+3)/4。当CPU数量多于4个,收集线程占用的CPU资源多于25%,对用户程序影响可能较大;不足4个时,影响更大,可能没法接受。

3)产生大量内存碎片 ,大对象分配困难,须要提早触发另外一次Full GC动做。

  

(7)G1:是一款面向服务端应用的商用垃圾收集器。具有四个特色:

1)并行与并发:能充分利用多CPU、多核环境下的硬件优点;可使用多个CPU并行来缩短"Stop The World"停顿时间;也能够并发让垃圾收集与用户程序同时进行。

2)分代收集:能独立管理整个GC堆(新生代和老年代),而不须要与其余收集器搭配;可以采用不一样方式处理不一样时期的对象;虽然保留分代概念,但Java堆的内存布局有很大差异;将整个堆划分为多个大小相等的独立区域(Region);新生代和老年代再也不是物理隔离,它们都是一部分Region(不须要连续)的集合。

3)空间整合,不产生碎片:从总体看,是基于标记-整理算法;从局部(两个Region间)看,是基于复制算法;都不会产生内存碎片,有利于长时间运行,不会提早触发一次GC。

4)可预测的停顿:低停顿的同时实现高吞吐量;G1除了追求低停顿处,还能创建可预测的停顿时间模型;能够明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒。

 

6、如何减小GC出现的次数:

1.对象不用时显示置null。

2.少用System.gc()。

3.尽可能少用静态变量。

4.尽可能使用StringBuffer,而不用String累加字符串。

5.分散对象建立或删除的时间。

6.少用finalize函数。

7.若是须要使用常常用到的图片,可使用软引用类型,它能够尽量将图片保存在内存中,供程序调用,而不引发OOM。

8.能用基本类型就不用基本类型封装类。

9.增大-Xmx的值。

相关文章
相关标签/搜索