gc 订阅
气相色谱(gas chromatography 简称GC)是二十世纪五十年代出现的一项重大科学技术成就。这是一种新的分离、分析技术,它在工业、农业、国防、建设、科学研究中都得到了广泛应用。气相色谱可分为气固色谱和气液色谱。 展开全文
气相色谱(gas chromatography 简称GC)是二十世纪五十年代出现的一项重大科学技术成就。这是一种新的分离、分析技术,它在工业、农业、国防、建设、科学研究中都得到了广泛应用。气相色谱可分为气固色谱和气液色谱。
信息
简    称
GC
类    别
科学技术成就
分    类
气固色谱和气液色谱
中文名
气相色谱
出现时间
二十世纪五十年代
外文名
gas chromatography
GC分类
气相色谱可分为气固色谱和气液色谱。气固色谱指流动相是气体,固定相是固体物质的色谱分离方法。例如活性炭、硅胶等作固定相。气液色谱指流动相是气体,固定相是液体的色谱分离方法。例如在惰性材料硅藻土涂上一层角鲨烷,可以分离、测定纯乙烯中的微量甲烷、乙炔、丙烯、丙烷等杂质。
收起全文
精华内容
参与话题
问答
  • GC是什么,为什么要有GC

    千次阅读 2019-03-05 07:54:34
    GC就是垃圾回收,java这种语言是动态分配内存大小的,并且依靠垃圾回收机制来完成对分配内存空间的回收,从而来避免内存溢出的问题,也在一定程度上降低了程序员工作的复杂度。 jvm中的GC采用了generation(分代回收...

    GC就是垃圾回收,java这种语言是动态分配内存大小的,并且依靠垃圾回收机制来完成对分配内存空间的回收,从而来避免内存溢出的问题,也在一定程度上降低了程序员工作的复杂度。

    jvm中的GC采用了generation(分代回收)算法,因为大多数的对象存活的时间比较短,而少部分的对象才能够长时间存活。因此,jvm将堆内存划分为年轻代(young generation)和年老代(old generation)。年轻代中的对象通常建立时间不久,且大部分生命周期也很短;年老代中的对象则已经创建比较久了,其声明周期也相对年轻代比较长。按照上面的划分,jvm在做GC时也进行了区别对待,对年轻代GC会相对比较频繁,且采用了copying(复制)算法;年老代的GC相对比较少,且采用的是tracing算法的一种,是标记-清除-压缩。

    展开全文
  • GC原理解析

    千次阅读 2018-08-30 15:52:30
    这是因为Java在JVM虚拟机上增加了垃圾回收(GC)机制,用以在合适的时间触发垃圾回收,将不需要的内存空间回收释放,避免无限制的内存增长导致的OOM。作为一个合格的Java程序员,有必要了解Java GC相关知识。掌握GC...

    众所周知,Java程序不用像C++程序在程序中自行处理内存的回收释放。这是因为Java在JVM虚拟机上增加了垃圾回收(GC)机制,用以在合适的时间触发垃圾回收,将不需要的内存空间回收释放,避免无限制的内存增长导致的OOM。作为一个合格的Java程序员,有必要了解Java GC相关知识。掌握GC知识一方面可以帮助我们快速排查因JVM导致的线上问题,另一方面也可以帮助我们在Java应用发布之前合理地对JVM进行调优,提高应用的执行效率、可靠性和健壮性。

    这篇文章将从以下几点展开介绍:

    • Java堆内存结构
    • 分代回收算法
    • 垃圾收集器
    • GC日志
    • JVM参数使用

    本文所讨论的虚拟机为默认JDK所使用的HotSpot虚拟机。

    1. Java堆内存结构

    为了后面章节知识点的理解,我们应该先对Java堆内存结构划分有一定的了解。Java将堆内存分为3大部分:新生代、老年代和永久代,其中新生代又进一步划分为Eden、S0、S1(Survivor)三个区。结构如下图所示:

    +---------------------------+-------------------------------+-------------------+
    |          |       |        |                               |                   |
    |   Eden   |   S0  |   S1   |       Old generation          |      Perm         |
    |          |       |        |                               |                   |
    +---------------------------+-------------------------------+-------------------+
    |<----Young Gen Space------>|

    我们在程序中new出来的对象一般情况下都会在新生代里的Eden区里面分配空间,如果存活时间足够长将会进入Survivor区,进而如果存活时间再长,还会被提升分配到老年代里面。持久代里面存放的是Class类元数据、方法描述等。

    1. S0和S1是两个大小相等的区域,分配内存空间只会在其中某一个进行,另外一个空间是用来辅助进行新生代进行垃圾回收的,因为新生代的垃圾回收策略基于复制算法,其思想是将Eden区及两个Survivor中的某个区,如S0区里面需要存活的对象复制到另外一个空的Survivor区,如S1区,然后就可以回收Eden和S0区域里面的死亡对象。下一次回收就对调S0和S1两个区的角色,S1用来存放存活对象而S0用来辅助回收垃圾,如此循环利用。
       
    2. 有些文章并不将永久代纳入Java堆内存。其实永久代就是我们所说的方法区,而方法区经常被称为Non-Heap(非堆)。仅仅在HotSpot虚拟机的实现中才将GC分代收集扩展至方法区,或者说使用永久代来实现方法区,对于其他的虚拟机是不存在永久代这个概念的。
       
    3. 并非所有的对象创建都会在Eden区中分配内存空间。对于Serial和ParNew垃圾收集器,通过指定-XX:PretenureSizeThreshold={size}来设置超过这个阈值大小的对象直接进入老年代。

    2. 分代回收算法

    我们一般讨论的垃圾回收主要针对Java堆内存中的新生代和老年代,也正因为新生代和老年代结构上的不同,所以产生了分代回收算法,即新生代的垃圾回收和老年代的垃圾回收采用的是不同的回收算法。针对新生代,主要采用复制算法,而针对老年代,通常采用标记-清除算法或者标记-整理算法来进行回收。

    2.1 复制算法

    复制算法的思想是将内存分成大小相等的两块区域,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块区域上,然后对该块进行内存回收。示例图如下所示:

    复制算法图解

    这个算法实现简单,并且也相对高效,但是代价就是需要将牺牲一半的内存空间用于进行复制。有研究表明,新生代中的对象98%存活期很短,所以并不需要以1:1的比例来划分整个新生代,通常的做法是将新生代内存空间划分成一块较大的Eden区和两块较小的Survivor区,两块Survivor区域的大小保持一致。每次使用Eden和其中一块Survivor区,当回收的时候,将还存活的对象复制到另外一块Survivor空间上,最后清除Eden区和一开始使用的Survivor区。假如用于复制的Survivor区放不下存活的对象,那么会将对象存到老年代。

    HotSpot虚拟机默认Eden和Survivor的大小比例是8:1:1,也就是说新生代中牺牲掉10%的空间而不是一半的空间。

    2.2 标记-清除算法

    标记-清除(Mark-Sweep)算法分为两个阶段:

    • 标记
    • 清除

    标记阶段将标记出需要回收的对象空间,然后在下一个阶段清除阶段里面,将这些标记出来的对象空间回收掉。这种算法有两个主要问题:一个是标记和清除的效率不高,另一个问题是在清理之后会产生大量不连续的内存碎片,这样会导致在分配大对象时候无法找到足够的连续内存而触发另一次垃圾收集动作。标记-清除算法示例图如下所示:

    标记-清除算法图解

    2.3 标记-整理算法

    标记-整理(Mark-Compact)算法有效预防了标记-清除算法中可能产生过多内存碎片的问题。在标记需要回收的对象以后,它会将所有存活的对象空间挪到一起,然后再执行清理。示例图如下所示:

    标记-整理算法图解

    标记-整理通常会在标记-清除算法里面作为备选方案,为了防止标记-清除后产生大量内存碎片而无法为大对象分配足够内存的情况,如后面所讲的Serial Old收集器(基于标记-整理算法实现)将会作为CMS收集器(基于标记-清除算法实现)的备选方案。

    3. 垃圾收集器

    因为新生代和老年代采用回收算法的不同,垃圾收集器相应地也分为新生代收集器和老年代收集器。其中新生代收集器主要有Serial收集器、ParNew收集器和Parallel Scavenge收集器。老年代收集器主要有Serial Old收集器、Parallel Old收集器和CMS收集器。当然还包括了一款全新的、新生代老年代通用的G1收集器。各款收集器的搭配使用如下图所示,其中有连线的代表收集器可以搭配使用,没有连线的收集器表示不能搭配使用。

    收集器搭配使用图解

    其中显示为?号的收集器叫做G1收集器,是目前最新的收集器。这篇文章不会涉及到它。

    3.1 新生代收集器

    3.1.1 Serial收集器

    Serial收集器作用于新生代,是一个单线程收集器,基于复制算法实现。在进行垃圾回收的时候仅使用单条线程并且在回收的过程中会挂起所有的用户线程(Stop The World)。Serial收集器是JVM client模式下默认的新生代收集器。

    Serial收集器图解

    特别注意,Stop-The-World会挂起应用线程,造成应用的停顿。

    3.1.2 ParNew收集器

    ParNew收集器作用于新生代,是一个多线程收集器,基于复制算法实现。相对于Serial收集器而言,在垃圾回收的时候会同时使用多条线程进行回收,但是它跟Serial收集器一样,在回收过程中也是会挂起所有的用户线程,从而造成应用的停顿。

    ParNew收集器图解

    3.1.3 Parallel Scavenge收集器

    Parallel Scavenge收集器同样作用于新生代,并且也是采用多线程和复制算法来进行垃圾回收。Parallel Scavenge收集器关注的是吞吐量,即使得应用能够充分使用CPU。它与ParNew收集器一样,在回收过程会挂起所有的用户线程,造成应用停顿。

    所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值。即吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。如果虚拟机运行了100分钟,其中垃圾收集花了1分钟,那么吞吐量就是99%。

    3.2 老年代收集器

    3.2.1 Serial Old收集器

    Serial Old收集器作用于老年代,采用单线程和标记-整理算法来实现垃圾回收。在回收垃圾的时候同样会挂起所有用户线程,造成应用的停顿。一般来说,老年代的容量都比新生代要大,所以当发生老年代的垃圾回收时,STW经历的时间会比新生代所用的时间长得多。该收集器是JVM client模式下默认的老年代收集器。

    Serial Old收集器还有一个重要的用途是作为CMS收集器的后备方案,在并发收集发生Concurrent Mode Failure的时候使用,进行内存碎片的整理。

    3.2.2 Parallel Old收集器

    Parallel Old收集器是Parallel Scavenge收集器的老年代版本,采用多线程和标记-整理算法来实现老年代的垃圾回收。这个收集器主要是为了配合Parallel Scavenge收集器的使用,即当新生代选择了Parallel Scavenge收集器的情况下,老年代可以选择Parallel Old收集器。

    在JDK1.6以前并没有提供Parallel Scavenge收集器,所以在1.6版本以前,Parallel Scavenge收集器只能与Serial Old收集器搭配使用。

    3.2.3 CMS收集器

    CMS(Concurrent Mark Sweep)收集器是一款真正实现了并发收集的老年代收集器。CMS收集器以获取最短回收停顿时间为目标,采用多线程并发以及标记-清除算法来实现垃圾回收。CMS只在初始化标记重新标记阶段需要挂起用户线程,造成一定的应用停顿(STW),而其他阶段收集线程都可以与用户线程并发交替进行,不必挂起用户线程,所以并不会造成应用的停顿。CMS收集器可以最大程度地减少因垃圾回收而造成应用停顿的时间。

    CMS收集器图解

    CMS垃圾收集分为以下几个阶段:

    (1) 初始化标记 (inital mark)

    这个阶段仅仅是标记了GC Roots能够直接关联到的对象,速度很快,所以基本上感受不到STW带来的停顿。

    (2) 并发标记 (concurrent mark)

    并发标记阶段完成的任务是从第一阶段收集到的对象引用开始,遍历所有其他的对象引用,并标记所有需要回收的对象。这个阶段,收集线程与用户线程并发交替执行,不必挂起用户线程,所以并不会造成应用停顿。

    (3) 并发预清除 (concurrent-pre-clean)

    并发预清除阶段是为了下一个阶段做准备,为的是尽量减少应用停顿的时间。

    (4) 重新标记 (remark)

    这个阶段将会修正并发标记期间因为用户程序继续运作而导致标记产生变动的那部分对象的标记记录(有可能对象重新被引用或者新对象可以被回收)。这个阶段的停顿时间比初始标记阶段要长一些,但是远比并发标记的时间短。

    (5) 并发清除 (concurrent sweep)

    这个阶段将真正执行垃圾回收,将那些不被使用的对象内存回收掉。

    (6) 并发重置 (concurrent reset)

    收集器做一些收尾的工作,以便下一次GC周期能有一个干净的状态。

    使用CMS要注意以下两个关键词:

    • concurrent mode failure
    • promotion failed

    对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。promotion failed是在进行Minor GC时,新生代的survivor区放不下,对象只能放入老年代,而此时老年代也放不下造成的。concurrent mode failure是在执行CMS GC的过程中同时有对象要放入老年代(满足一定年龄的对象或者大对象),而此时老年代空间不足造成的。

    1. 通常我们把发生在新生代的垃圾回收称为Minor GC,而把发生在老年代的垃圾回收称为Major GC,而FullGC是指整个堆内存的垃圾回收,包括对新生代、老年代和持久代的回收。一般情况下应用程序发生Minor GC的次数要远远大于Major GC和Full GC的次数。
       
    2. 在讲解GC的时候会涉及到并行并发两个概念。在这里,并行指的是多个GC收集线程之间并行进行垃圾回收。而并发指的是多个GC收集线程与所有的用户线程能够交替执行。

    4. GC日志

    了解GC日志可以帮助我们更好地排查一些线上问题,如OOM、应用停顿时间过长等等。GC日志对我们进行JVM调优也是很有帮助的。采用不同的GC收集器所产生的GC日志的格式会稍微不同,但虚拟机设计者为了方便用户阅读,将各个收集器的日志都维持一定的共性。

    具有一定共性的的GC日志格式大致如下所示:

    <datestamp>:[GC[<collector>:<start occupancy1>-><end occupancy1>(total size1),<pause time1> secs]<start occupancy2>-><end occupancy2>(total size2),<pause time2> secs] [Times:<user time> <system time>, <real time>]
    • 1
    • datestamp : 表示GC日志产生的时间点,如果指定的jvm参数是-XX:+PrintGCTimeStamps,那么输出的是相对于虚拟机启动时间的时间戳,如果指定的是-XX:+PrintGCDateStamps,那么输出的是具体的时间格式,可读性更高

    • GC : 表示发生GC的类型,有GC(代表MinorGC)和FullGC两种情况

    • collector : 表示GC收集器类型,取值可能是DefNew、ParNew、PSYoungGen、Tenured、ParOldGen、PSPermGen等等

    • start occupancy1 : 表示发生回收之前占用的内存空间

    • end occupancy1 : 表示发生回收以后还占用的内存空间

    • total size1 : 该堆区域所拥有的总内存空间

    • pause time1 : 发生垃圾收集的时间

    • start occupancy2 : 表示回收前Java堆内存总占用空间

    • end occupancy2 : 表示回收后Java堆内存还占用的总空间

    • total size2 : 表示Java堆内存总空间

    • pause time2 : 表示整个堆回收消耗时间

    • [Times:\

    -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/myapp-gc.log
    • 1
    • -XX:+PrintGCDetails : 表示输出GC的详细情况
    • -XX:+PrintGCDateStamps : 指定输出GC时的时间格式,比-XX:+PrintGCTimeStamps可读性更高
    • -Xloggc : 指定gc日志的存放位置

    下面我们将具体解读几种GC收集器组合下产生的GC日志。

    辅助讲解的例子程序:

    package com.test.gc;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class TestGC {
    
        private static class BigObject {
    
            byte[] bigBytes = new byte[1024 * 1024];
    
        }
    
        public static void main(String[] args) {
    
            keepInMemory();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.gc();
    
        }
    
        private static void keepInMemory() {
    
            List<BigObject> memList = new ArrayList<BigObject>();
    
            for(int i = 0; i < 55;i ++) {
                memList.add(new BigObject());
            }
        }
    
    }

    (1) Serial + Serial Old

    -XX:+UseSerialGC
    -Xms64m -Xmx64m
    -Xmn32m
    -XX:SurvivorRatio=8
    -XX:+PrintGCDetails -XX:+PrintGCDateStamps
    
    [GC log]
    2016-04-22T15:00:00.647+0800: [GC2016-04-22T15:00:00.648+0800: [DefNew: 26152K->2513K(29504K), 0.0131820 secs] 26152K->25042K(62272K), 0.0132320 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    2016-04-22T15:00:00.663+0800: [GC2016-04-22T15:00:00.663+0800: [DefNew: 28155K->28155K(29504K), 0.0000125 secs]2016-04-22T15:00:00.663+0800: [Tenured: 22528K->31745K(32768K), 0.0114797 secs] 50684K->49617K(62272K), [Perm : 2753K->2753K(21248K)], 0.0115361 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    2016-04-22T15:00:01.676+0800: [Full GC2016-04-22T15:00:01.676+0800: [Tenured: 31745K->463K(32768K), 0.0039332 secs] 56964K->463K(62272K), [Perm : 2753K->2753K(21248K)], 0.0039788 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

    以GC日志第一行为例子,发生的是Minor GC。最开始的2016-04-22T15:00:00.647+0800表示发生GC的时间;DefNew表示新生代采用的是Serial收集器。26152K->2513K(29504K), 0.0131820 secs表示回收前新生代内存占用是26152K(约26M),回收后内存占用是2513K(约2.5M),括号里面的数字表示新生代的可用总容量为29504K(约29M),一般来说这个数字约等于Eden区加上一个Survivor区的容量大小。最后的0.0131820 secs代表Minor GC消耗的时间。

    从GC日志第二行可以看出,新生代已经容纳不下新生的对象,需要把存活的对象不断的挪到老年,Tenured表示老年代采用的是Serial Old收集器。[Tenured: 22528K->31745K(32768K), 0.0114797 secs]表明老年代的容量不断被占用,并且已经接近满了,如果这个时候还不断有新对象生成并存活,那么将会发生OOM异常。

    最后发生的FullGC是由于程序调用了System.gc()造成的,这个时候keepInMemory()函数退出,局部变量将可以被回收,所以调用gc方法可以尽快通知虚拟机进行垃圾回收,所以我们可以看到堆内存的占用情况很快地降了下来。

    特别注意,如果把System.gc()方法写在keepInMemory()方法的最后,那么堆内存将不会被回收,因为方法没有退出,局部引用对象将一直存在于内存。

    (2) Parallel Scavenge + Parallel Old

    -XX:+UseParallelGC -XX:+UseParallelOldGC
    -Xms64m -Xmx64m
    -Xmn32m
    -XX:SurvivorRatio=8
    -XX:+PrintGCDetails -XX:+PrintGCDateStamps
    
    [GC log]
    2016-04-22T14:09:28.990+0800: [GC [PSYoungGen: 26174K->2688K(29696K)] 26174K->25216K(62464K), 0.0090217 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    2016-04-22T14:09:28.999+0800: [Full GC [PSYoungGen: 2688K->0K(29696K)] [ParOldGen: 22528K->25040K(32768K)] 25216K->25040K(62464K) [PSPermGen: 2748K->2747K(21504K)], 0.0084858 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
    2016-04-22T14:09:29.011+0800: [Full GC [PSYoungGen: 25657K->17408K(29696K)] [ParOldGen: 25040K->32208K(32768K)] 50698K->49616K(62464K) [PSPermGen: 2750K->2750K(21504K)], 0.0100313 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
    2016-04-22T14:09:29.022+0800: [Full GC [PSYoungGen: 24758K->0K(29696K)] [ParOldGen: 32208K->462K(32768K)] 56966K->462K(62464K) [PSPermGen: 2750K->2750K(21504K)], 0.0057976 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 

    以日志第一行为例,发生的是Minor GC。最开始的2016-04-22T14:09:28.990+0800表示发生GC的时间;PSYoungGen表示新生代采用的是Parallel Scavenge收集器,后面的26174K->2688K(29696K)表示新生代回收前使用了26174K(约26M)的空间而回收后还占用了2688K(约2.6M)的空间,括号里面的数值表示新生代的可用总容量为29696K(约29M),而后面的0.0090217 secs表示此次Minor GC耗费的时间。

    以日志第二行为例,发生的是Full GC。其中包括了新生代、老年代和持久代的垃圾回收。从数据可以看出,在老年代发生GC以后占有的内存比回收前还要多[ParOldGen: 22528K->25040K(32768K)],证明此时程序应该有大量的对象存活并且由于新生代已经存放不下去了,只能由老年代来存放。持久代的垃圾回收内存占用基本保持不变。

    而在日志最后一行发生的Full GC是因为程序在结束的时候调用了System.gc()。所以堆内存的占用情况降了下来。

    (3) ParNew + CMS

    -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
    -Xms64m -Xmx64m
    -Xmn32m
    -XX:SurvivorRatio=8
    -XX:+PrintGCDetails -XX:+PrintGCDateStamps
    
    [GC log]
    2016-04-22T14:34:06.134+0800: [GC2016-04-22T14:34:06.134+0800: [ParNew: 26152K->2532K(29504K), 0.0076385 secs] 26152K->25069K(62272K), 0.0076904 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    2016-04-22T14:34:06.144+0800: [GC2016-04-22T14:34:06.145+0800: [ParNew: 28174K->28174K(29504K), 0.0000311 secs]2016-04-22T14:34:06.145+0800: [CMS: 22536K->31745K(32768K), 0.0170668 secs] 50711K->49617K(62272K), [CMS Perm : 2754K->2753K(21248K)], 0.0171470 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
    2016-04-22T14:34:06.162+0800: [GC [1 CMS-initial-mark: 31745K(32768K)] 50641K(62272K), 0.0002688 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2016-04-22T14:34:06.164+0800: [CMS-concurrent-mark: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2016-04-22T14:34:06.165+0800: [CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2016-04-22T14:34:06.165+0800: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2016-04-22T14:34:06.165+0800: [GC[YG occupancy: 25219 K (29504 K)]2016-04-22T14:34:06.165+0800: [Rescan (parallel) , 0.0002540 secs]2016-04-22T14:34:06.165+0800: [weak refs processing, 0.0000090 secs]2016-04-22T14:34:06.165+0800: [scrub string table, 0.0001010 secs] [1 CMS-remark: 31745K(32768K)] 56965K(62272K), 0.0004079 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2016-04-22T14:34:06.165+0800: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2016-04-22T14:34:06.166+0800: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2016-04-22T14:34:07.163+0800: [Full GC2016-04-22T14:34:07.163+0800: [CMS: 31745K->465K(32768K), 0.0066109 secs] 56965K->465K(62272K), [CMS Perm : 2753K->2753K(21248K)], 0.0066795 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 

    以GC日志第一行为例,ParNew表示使用的新生代收集器为Parallel收集器。从第二行可以看出这个时候新生代已经容纳不下新对象了,对象往老年代放,造成老年代的容量不断增加。
    [CMS: 22536K->31745K(32768K), 0.0170668 secs] 50711K->49617K(62272K),表示老年代使用的是CMS收集器。并且回收后的容量比回收前的容量还要高。

    注意这里的回收是由新生代引起的

    而后显示的是老年代CMS收集器发生GC的日志,从日志中可以看出CMS收集的所有阶段。

    5. JVM参数使用

    5.1 堆内存相关

    • -Xms 与 -Xmx

    -Xms用于指定Java应用使用的最小堆内存,如-Xms1024m表示将Java应用最小堆设置为1024M。-Xmx用于指定Java应用使用的最大堆内存,如-Xmx1024m表示将Java应用最大堆设置为1024m。过小的堆内存可能会造成程序抛出OOM异常,所以正常发布的应用应该明确指定这两个参数。并且,一般会选择将-Xms-Xmx设置成一样大小,防止JVM动态调整堆内存容量对程序造成性能影响。

    • -Xmn

    通过-Xmn可以设置堆内存中新生代的容量,以此达到间接控制老年代容量的作用,因为没有JVM参数可以直接控制老年代的容量。如-Xmn256m表示将新生代容量设置为256M。假如这个时候额外指定了-Xms1024m -Xmx1024m,那么老年代的容量为768M(1024-256=768)。

    • -XX:PermSize 与 -XX:MaxPermSize

    -XX:PermSize-XX:MaxPermSize分别用于设置永久代的容量和最大容量。如-XX:PermSize=64m -XX:MaxPermSize=128m表示将永久代的初始容量设置为64M,最大容量设置为128M。

    • -XX:SurvivorRatio

    这个参数用于设置新生代中Eden区和Survivor(S0、S1)的容量比值。默认设置为-XX:SurvivorRatio=8表示Eden区与Survivor的容量比例为8:1:1。假设-Xmn256m -XX:Survivor=8,那么Eden区容量为204.8M(256M / 10 * 8),S0和S1区的容量大小均为25.6M(256M / 10 * 1)。

    5.2 GC收集器相关

    • -XX:+UseSerialGC

    虚拟机运行在client模式下的默认值,使用这个参数表示虚拟机将使用Serial + Serial Old收集器组合进行垃圾回收。

    -XX:+UseSerialGC表示使用这个设置,而-XX:-UseSerialGC表示禁用这个设置。

    • -XX:+UseParNewGC

    使用这个设置以后,虚拟机将使用ParNew + Serial Old收集器组合进行垃圾回收。

    • -XX:+UseConcMarkSweepGC

    使用这个设置以后,虚拟机将使用ParNew + CMS + Serial Old的收集器组合进行垃圾回收。注意Serial Old收集器将作为CMS收集器出现Concurrent Mode Failure失败后的后备收集器来进行回收(将会整理内存碎片)。

    • -XX:+UseParallelGC

    虚拟机运行在server模式下的默认值。使用这个设置,虚拟机将使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器组合进行垃圾回收。

    • -XX:+UseParallelOldGC

    使用这个设置以后,虚拟机将使用Parallel Scavengen + Parallel Old的收集器组合进行垃圾回收。

    • -XX:PretenureSizeThreshold

    设置直接晋升到老年代的对象大小,大于这个参数的对象将直接在老年代分配,而不是在新生代分配。注意这个值只能设置为字节,如-XX:PretenureSizeThreshold=3145728表示超过3M的对象将直接在老年代分配。

    • -XX:MaxTenuringThreshold

    设置晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC之后,年龄就会加1,当超过这个值时就进入老年代。默认设置为-XX:MaxTenuringThreshold=15

    • -XX:ParellelGCThreads

    设置并行GC时进行内存回收的线程数。只有当采用的垃圾回收器是采用多线程模式,包括ParNew、Parallel Scavenge、Parallel Old、CMS,这个参数的设置才会有效。

    • -XX:CMSInitiatingOccupancyFraction

    设置CMS收集器在老年代空间被使用多少(百分比)后触发垃圾收集。默认设置-XX:CMSInitiatingOccupancyFraction=68表示老年代空间使用比例达到68%时触发CMS垃圾收集。仅当老年代收集器设置为CMS时候这个参数才有效。

    • -XX:+UseCMSCompactAtFullCollection

    设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片整理。仅当老年代收集器设置为CMS时候这个参数才有效。

    • -XX:CMSFullGCsBeforeCompaction

    设置CMS收集器在进行多少次垃圾收集后再进行一次内存碎片整理。如设置-XX:CMSFullGCsBeforeCompaction=2表示CMS收集器进行了2次垃圾收集之后,进行一次内存碎片整理。仅当老年代收集器设置为CMS时候这个参数才有效。

    5.3 GC日志相关

    • -XX:+PrintGCDetails

    表示输出GC的详细情况。

    • -XX:+PrintGCDateStamps

    指定输出GC时的时间格式,比指定-XX:+PrintGCTimeStamps可读性更高。

    • -Xloggc

    指定gc日志的存放位置。如-Xloggc:/var/log/myapp-gc.log表示将gc日志保存在磁盘/var/log/目录,文件名为myapp-gc.log

    展开全文
  • 深入理解Java-GC机制

    万次阅读 多人点赞 2018-04-13 15:28:14
    今天我们来谈谈Java主流虚拟机-HotSpot的GC实现机制,本篇文章默认使用HotSpot虚拟机进行介绍,如果没有特殊说明,其都为HotSpot虚拟机中的特性。 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围城的“高墙...

    Hello,大家好,我是Steafan,在经历了惊心动魄的阿里电面之后,我又回来了。

    今天我们来谈谈Java主流虚拟机-HotSpot的GC实现机制,本篇文章默认使用HotSpot虚拟机进行介绍,如果没有特殊说明,其都为HotSpot虚拟机中的特性。

      Java与C++之间有一堵由内存动态分配和垃圾收集技术所围城的“高墙”,墙外面的人想进去,墙里面的人却想出来。

    说起垃圾收集,大部分人都把这项技术当做Java语言的伴生产物。事实上,GC的历史比Java久远,1960年诞生与MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。关于Garbage Collection的历史这里就不多说了,因为这是一篇技术博客而不是来将历史的,如果对GC的发展历史感兴趣可以自行百度。

    一、GC实现机制-我们为什么要去了解GC和内存分配?

          说道这个问题,我有一个简单的回答:在真实工作中的项目中,时不时的会发生内存溢出、内存泄露的问题,这也是不可避免的Bug,这些潜在的Bug在某些时候会影响到项目的正常运行,如果你的项目没有合理的进行业务内存分配,将会直接影响到的项目的并发处理,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节,而了解了GC实现机制则是我们一切监控和调节的前提。

    二、GC实现机制-Java虚拟机将会在什么地方进行垃圾回收?

         说起垃圾回收的场所,了解过JVM(Java Virtual Machine Model)内存模型的朋友应该会很清楚,堆是Java虚拟机进行垃圾回收的主要场所,其次要场所是方法区。

    三、GC实现机制-Java虚拟机具体实现流程


          我们都知道在Java虚拟机中进行垃圾回收的场所有两个,一个是堆,一个是方法区。在堆中存储了Java程序运行时的所有对象信息,而垃圾回收其实就是对那些“死亡的”对象进行其所侵占的内存的释放,让后续对象再能分配到内存,从而完成程序运行的需要。关于何种对象为死亡对象,在下一部分将做详细介绍。Java虚拟机将堆内存进行了“分块处理”,从广义上讲,在堆中进行垃圾回收分为新生代(Young Generation)和老生代(Old Generation);从细微之处来看,为了提高Java虚拟机进行垃圾回收的效率,又将新生代分成了三个独立的区域(这里的独立区域只是一个相对的概念,并不是说分成三个区域以后就不再互相联合工作了),分别为:Eden区(Eden Region)、From Survivor区(Form Survivor Region)以及To Survivor(To Survivor Region),而Eden区分配的内存较大,其他两个区较小,每次使用Eden和其中一块Survivor。Java虚拟机在进行垃圾回收时,将Eden和Survivor中还存活着的对象进行一次性地复制到另一块Survivor空间上,直到其两个区域中对象被回收完成,当Survivor空间不够用时,需要依赖其他老年代的内存进行分配担保。当另外一块Survivor中没有足够的空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老生代,在老生代中不仅存放着这一种类型的对象,还存放着大对象(需要很多连续的内存的对象),当Java程序运行时,如果遇到大对象将会被直接存放到老生代中,长期存活的对象也会直接进入老年代。如果老生代的空间也被占满,当来自新生代的对象再次请求进入老生代时就会报OutOfMemory异常。新生代中的垃圾回收频率高,且回收的速度也较快。就GC回收机制而言,JVM内存模型中的方法区更被人们倾向的称为永久代(Perm Generation),保存在永久代中的对象一般不会被回收。其永久代进行垃圾回收的频率就较低,速度也较慢。永久代的垃圾收集主要回收废弃常量和无用类。以String常量abc为例,当我们声明了此常量,那么它就会被放到运行时常量池中,如果在常量池中没有任何对象对abc进行引用,那么abc这个常量就算是废弃常量而被回收;判断一个类是否“无用”,则需同时满足三个条件:

              (1)、该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;

              (2)、加载该类的ClassLoader已经被回收

              (3)、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

    虚拟机可以对满足上述3个条件的无用类进行回收,这里说的是可以回收而不是必然回收。

        大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC;同理,当老年代中没有足够的内存空间来存放对象时,虚拟机会发起一次Major GC/Full GC。只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full CG。

        虚拟机通过一个对象年龄计数器来判定哪些对象放在新生代,哪些对象应该放在老生代。如果对象在Eden出生并经过一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将该对象的年龄设为1。对象每在Survivor中熬过一次Minor GC,年龄就增加1岁,当他的年龄增加到最大值15时,就将会被晋升到老年代中。虚拟机并不是永远地要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中所有相同年龄的对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。

    四、GC实现机制-Java虚拟机如何实现垃圾回收机制

       (1)、引用计数算法(Reference Counting)

                 给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的,这就是引用计数算法的核心。客观来讲,引用计数算法实现简单,判定效率也很高,在大部分情况下都是一个不错的算法。但是Java虚拟机并没有采用这个算法来判断何种对象为死亡对象,因为它很难解决对象之间相互循环引用的问题。

    public class ReferenceCountingGC{
      public Object object = null;
      
      private static final int OenM = 1024 * 1024;
      private byte[] bigSize = new byte[2 * OneM];
    
      public static void testCG(){
         ReferenceCountingGC objA = new ReferenceCountingGC(); 
          ReferenceCountingGC objB = new ReferenceCountingGC(); 
          
          objA.object = null;
          objB.object = null;
    
         System.gc();
    }
    }
        在上述代码段中,objA与objB互相循环引用,没有结束循环的判断条件,运行结果显示Full GC,就说明当Java虚拟机并不是使用引用计数算法来判断对象是否存活的。

        (2)、可达性分析算法(Reachability Analysis)

                这是Java虚拟机采用的判定对象是否存活的算法。通过一系列的称为“GC Roots"的对象作为起始点,从这些结点开始向下搜索,搜索所走过的路径称为饮用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。可作为GC Roots的对象包括:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象。本地方法栈JNI引用的对象。


    在上图可以看到GC Roots左边的对象都有引用链相关联,所以他们不是死亡对象,而在GCRoots右边有几个零散的对象没有引用链相关联,所以他们就会别Java虚拟机判定为死亡对象而被回收。

    五、GC实现机制-何为死亡对象?

          Java虚拟机在进行死亡对象判定时,会经历两个过程。如果对象在进行可达性分析后没有与GC Roots相关联的引用链,则该对象会被JVM进行第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,如果当前对象没有覆盖该方法,或者finalize方法已经被JVM调用过都会被虚拟机判定为“没有必要执行”。如果该对象被判定为没有必要执行,那么该对象将会被放置在一个叫做F-Queue的队列当中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它,在执行过程中JVM可能不会等待该线程执行完毕,因为如果一个对象在finalize方法中执行缓慢,或者发生死循环,将很有可能导致F-Queue队列中其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。如果在finalize方法中该对象重新与引用链上的任何一个对象建立了关联,即该对象连上了任何一个对象的引用链,例如this关键字,那么该对象就会逃脱垃圾回收系统;如果该对象在finalize方法中没有与任何一个对象进行关联操作,那么该对象会被虚拟机进行第二次标记,该对象就会被垃圾回收系统回收。值得注意的是finaliza方法JVM系统只会自动调用一次,如果对象面临下一次回收,它的finalize方法不会被再次执行。

    六、再探GC实现机制-垃圾收集算法


          (1)、标记-清楚算法(Mark-Sweep)

                    用在老生代中, 先对对象进行标记,然后清楚。标记过程就是第五部分提到的标记过程。值得注意的是,使用该算法清楚过后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

    .      (2)、复制算法(Copying)

                     用在新生代中,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另外一块上,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。

    七、空间分配担保策略-GC过程中的内存担保机制

          当出现大量对象在Minor GC后仍然存活的情况,就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代。与生活中的银行贷款类似,老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会存活下来在实际完后才能内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。如果出现担保失败,就只好重新发起一次Full GC来进行内存的分配。

    展开全文
  • JAVA GC垃圾回收机制详解

    万次阅读 2019-12-02 12:57:50
    既然提到了JVM那么我们必须得谈一谈GC垃圾回收机制了,在分析垃圾回收机制之前呢,我们还是需要来内存结构中堆(Heap)的详细结构。 图中展示了堆中三个区域:Eden、From Survivor、To Survivor。从图中可以也可以...

    引言

    在上一篇博客中我们详细分析了,JVM的内存结构。既然提到了JVM那么我们必须得谈一谈GC垃圾回收机制了,在分析垃圾回收机制之前呢,我们还是需要来内存结构中堆(Heap)的详细结构。image

     

    图中展示了堆中三个区域:Eden、From Survivor、To Survivor。从图中可以也可以看到它们的大小比例,准确来说是:8:1:1。为什么要这样设计呢,本篇文章后续会给出解答,还是根据垃圾回收的具体情况来设计的。

    还记得在设置JVM时,常用的类似-Xms和-Xmx等参数吗?对的它们就是用来说设置堆中各区域的大小的。

     image

    控制参数详解:

    • -Xms设置堆的最小空间大小。
    • -Xmx设置堆的最大空间大小。
    • -Xmn堆中新生代初始及最大大小(NewSize和MaxNewSize为其细化)。
    • -XX:NewSize设置新生代最小空间大小。
    • -XX:MaxNewSize设置新生代最大空间大小。
    • -XX:PermSize设置永久代最小空间大小。
    • -XX:MaxPermSize设置永久代最大空间大小。
    • -Xss设置每个线程的堆栈大小。

    对照上面两个图,再来看这些参数是不是没有之前那么枯燥了,它们在图中都有了对应的位置。

    有没有发现没有直接设置老年代空间大小的参数?我们通过简单的计算获得。

    1. 老年代空间大小=堆空间大小-年轻代大空间大小

    对上面参数立即了,但记忆有困难?那么,以下几个助记词可能更好的帮你记忆和理解参数的含义。

    Xmx(memory maximum), Xms(memory startup), Xmn(memory nursery/new), Xss(stack size)。

    对于参数的格式可以这样理解:

    • -: 标准VM选项,VM规范的选项。
    • -X: 非标准VM选项,不保证所有VM支持。
    • -XX: 高级选项,高级特性,但属于不稳定的选项

    GC概述

    垃圾收集(Garbage Collection)通常被称为“GC”,由虚拟机“自动化”完成垃圾回收工作。

    思考一个问题,既然GC会自动回收,开发人员为什么要学习GC和内存分配呢?为了能够配置上面的参数配置?参数配置又是为了什么?

    当需要排查各种内存溢出,内存泄露问题时,当垃圾成为系统达到更高并发量的瓶颈时,我们就需要对GC的自动回收实施必要的监控和调节。

    JVM中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生随线程而灭。栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理。它们的内存分配和回收都具有确定性。

    因此,GC垃圾回收主要集中在堆和方法区,在程序运行期间,这部分内存的分配和使用都是动态的。

     GC回收流程

                                 

    (1)Eden区域是用来存放使用new或者newInstance等方式创建的对象,默认都是存放在Eden区,除非这个对象太大,或者超过了设定的阈值-XX:PretenureSizeThresold,这样的对象会被直接分配到Old区域。

    (2)2个Survivor(幸存)区,一般称S0,S1,理论上他们是一样大的,解释一下,他们是如何工作的:

    在不断创建对象的过程中,Eden区会满,这时候会开始做Young G也叫Minor GC,而Young空间的第一次GC就是找出Eden区中,幸存活着的对象,然后将这些对象,放到S0,或S1区中的其中一个, 假设第一次选择了S0,它会逐步将活着的对象拷贝到S0区域,但是如果S0区域满了,剩下活着的对象只能放old区域了,接下来要做的是,将Eden区域 清空,此时时候S1区域也是空的。

    当第二次Eden区域满的时候,就将Eden区域中活着的对象+S0区域中活着的对象,迁移到S1中,如果S1放不下,就会将剩下的部门,放到Old区域中,只是这次对象来源区域增加了S0,最后会将Eden区+S0区域,清空

    第三次和第四次依次类推,始终保证S0和S1有一个是空的,用来存储临时对象,用于交换空间的目的,反反复复多次没有被淘汰的对象,将会放入old区域中,默认是15次。具体的交换过程就和上图中的信息相似。

     

    如何判断对象存活

    判断对象常规有两种方法:引用计数算法和可达性分析算法(Reachability Analysis)。

    引用计数算法:给对象添加一个引用计数器,每当有一个地方引用它时计数器加1,引用释放时计数减1,当计数器为0时可以回收。

    引用计数算法实现简单,判断高效,在微软COM和Python语言等被广泛使用,但在主流的Java虚拟机中没有使用该方法,主要是因为无法解决对象相互循环引用的问题。

    可达性分析算法:基本思想是通过一系列称为“GC Root”的对象(如系统类加载器、栈中的对象、处于激活状态的线程等)作为起点,基于对象引用关系,开始向下搜索,所走过的路径称为引用链,当一个对象到GC Root没有任何引用链相连,证明对象是不可用的。

    image

    上图中中绿色部分为存活对象,灰色部分为可回收对象。虽然灰色部分内部依旧有关联,但它们到GC Root是不可达的。 

    面试问题

    面试官,说说Java GC都用了哪些算法?分别应用在什么地方?

    答:复制算法、标记清除、标记整理……

    标记清除算法

    标记清除(Mark-Sweep)算法,包含“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。

    标记清除算法是最基础的收集算法,后续的收集算法都是基于该思路并对其缺点进行改进而得到的。

                         image 

    主要缺点:一个是效率问题,标记和清除过程的效率都不高;另外是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

    复制算法

    复制(Copying)算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当一块内存用完了,就将还存活着的对象复制到另外一块上,然后清理掉前一块。

    image

    每次对半区内存回收时、内存分配时就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

    缺点:将内存缩小为一半,性价比低,持续复制长生存期的对象则导致效率低下。

    JVM堆中新生代便采用复制算法。回到最初推分配结构图。

    image

     

    在GC回收过程中,当Eden区满时,还存活的对象会被复制到其中一个Survivor区;当回收时,会将Eden和使用的Survivor区还存活的对象,复制到另外一个Survivor区,然后对Eden和用过的Survivor区进行清理。

    如果另外一个Survivor区没有足够的内存存储时,则会进入老年代。

    这里针对哪些对象会进入老年代有这样的机制:对象每经历一次复制,年龄加1,达到晋升年龄阈值后,转移到老年代。

    在这整个过程中,由于Eden中的对象属于像浮萍一样“瞬生瞬灭”的对象,所以并不需要1:1的比例来分配内存,而是采用了8:1:1的比例来分配。

    而针对那些像“水熊虫”一样,历经多次清理依旧存活的对象,则会进入老年代,而老年的清理算法则采用下面要讲到的“标记整理算法”。

    标记整理算法

    标记整理(Mark-Compact)算法:标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

    image

    这种算法不既不用浪费50%的内存,也解决了复制算法在对象存活率较高时的效率低下问题。

    分代收集算法

    分代收集算法,基本思路:将Java的堆内存逻辑上分成两块,新生代和老年代,针对不同存活周期、不同大小的对象采取不同的垃圾回收策略。

    而在新生代中大多数对象都是瞬间对象,只有少量对象存活,复制较少对象即可完成清理,因此采用复制算法。而针对老年代中的对象,存活率较高,又没有额外的担保内存,因此采用标记整理算法。

    其实,回头看,分代收集算法就是对新生代和老年代算法从策略维度的规划而已。

    常见问题锦集

    1、对象进入Old区域有什么坏处?

    old区域一般称为老年代,老年代与新生代不一样,年轻代,我们可以认为存活下来的对象很少,而老年代则相反,存活下来的对象很多,所以JVM的 堆内存,才是我们通常关注的主战场,因为这里面活着的对象非常多,所以发生一次FULL GC,来找出来所有存活的对象是非常耗时的,因此,我们应该尽量避免FULL GC的发生。

    2、S0和S1一般多大,靠什么参数来控制,有什么变化?

    一般来说很小,我们大概知道它与Young差不多相差一倍的比例,设置的的参数主要有两个:

    -XX:SurvivorRatio=8

    -XX:InitialSurvivorRatio=8

    第一个参数是Eden和Survivor区域比重,注意是一个Survivor的的大小,如果将其设置为8,则说明Eden区是一个Survivor区的8倍,换句话说S0或S1空间是整个Young空间的1/10,剩余的80%由Eden区域来使用。

    第二个参数是Young/S0的比值,当其设置为8时,表示s0或s1占整个Young空间的12.5%。

    3、一个对象每次Minor Gc时,活着的对象都会在s0和s1区域转移,经过经过Minor GC多少次后,会进入Old区域呢?

    默认是15次,参数设置-XX:MaxTenuringThreshold=15,计数器会在对象的头部记录它交换的次数

    4、为什么发生FULL GC会带来很大的危害?

    在发生FULL GC的时候,意味着JVM会安全的暂停所有正在执行的线程(Stop The World),来回收内存空间,在这个时间段内,所有除了回收垃圾的线程外,其他有关JAVA的程序,代码都会静止,反映到系统上,就会出现系统响应大幅度变慢,卡机等状态。

    举个通俗易懂点的例子,就是在一个房间里,如果有一个人,不停的扔垃圾,然后有一个清洁工不停扫垃圾,这时候,我们的系统是OK的,因为基本不会 出现垃圾堆满房间的情景,而且因为清洁工可以对付过来,假设现在有10个人不停扔垃圾,那么就房间就会很快被堆满,这时候清洁工,由于工作不过来了,大声 吼一声,你们都暂停3分钟,别再扔了,我先把这个房间打扫完,你们才可以扔。

    在这个场景中,一个人扔,一个人扫,就类似于Minor GC,这时候,并不会影响扔垃圾的人,然后一旦10个人同时仍,而且很快就没地方仍了,这时候,就会触发Full GC,然后JVM下令,你们暂时都别仍了,等我什么时候回收完垃圾了,你们在仍,现在大家清楚了吧,所谓的10个人,就是类似我们成千上百的java类, 在不停的执行任务,所谓的清洁工,就是我们的GC机制,所以,大家在平时编码的时候,一定注意尽量少造点垃圾对象,这样触发FULL GC的几率,才会变小。

     

    展开全文
  • 什么是GC

    千次阅读 2018-11-04 14:08:53
    GC就是垃圾回收,它的主要作用就是回收程序中不再使用的内存. 是否可以主动通知jvm进行垃圾回收? 不能实时调用垃圾回收器对某个对象或者所有对象进行垃圾回收.但是可以通过System.gc()方法来通知垃圾回收器运行,...
  • 浅谈Java的System.gc()实现

    万次阅读 2019-04-20 09:35:35
    我们都知道System.gc()用于调用垃圾收集器。很久之前我一直认为执行System.gc()之后,虚拟机会立刻垃圾回收。 抱歉,我理解错了。 直到看完System.gc()的源码之后才搞清楚,执行System.gc()函数的作用只是提醒或...
  • JVM系列(四)之GC调优

    万次阅读 2018-08-13 17:52:29
    说说Minor GC 、Major GC、Full GC 总结 限制JVM内存的大小 减少新生代对象转移到老年代的数量 选择合适的垃圾收集器 垃圾收集器的调优分析 Serial收集器 ParNew收集器 GC日志分析 Parallel收集器 ...
  • 详解GC

    千次阅读 2018-03-07 17:22:24
    详解GC收集算法更多干货分布式实战(干货)spring cloud 实战(干货)mybatis 实战(干货)spring boot 实战(干货)React 入门实战(干货)构建中小型互联网企业架构(干货)一、引用计数算法给对象中添加一个引用...
  • Java GC日志查看,GC日志时间分析

    万次阅读 2019-03-09 17:00:49
    Java中的GC有哪几种类型? 参数 描述 UseSerialGC 虚拟机运行在Client模式的默认值,打开此开关参数后, 使用Serial+Serial Old收集器组合进行垃圾收集。 ...
  • GC是什么?为什么要有GC

    千次阅读 2016-05-12 19:59:29
    GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方, 忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的, Java语言...
  • GC是什么? 为什么要有GC?

    万次阅读 2017-07-06 10:58:34
    1.JVM的gc概述 gc(garbage collection):即垃圾收集,是指JVM用于释放那些不再使用的对象所占用的内存。java语言并不要求JVM有gc,也没有规定gc如何工作。不过常用的JVM都有gc,而且大多数gc都使用类似的算法管理...
  • 说说你对GC理解,什么是GC

    千次阅读 2018-11-22 23:13:42
    GC(GarbageCollection)是垃圾回收机制(垃圾回收器),GC是JVM对内存(实际上就是对象)进行管理的方式。 Java是由C++发展来的。它摈弃了C++中一些繁琐容易出错的东西。其中有一条就是这个GC。 写C/C++程序,...
  • JVM架构和GC垃圾回收机制(JVM面试不用愁)

    万次阅读 多人点赞 2017-06-10 16:49:15
    JVM架构和GC垃圾回收机制详解 JVM架构图分析 下图:参考网络+书籍,如有侵权请见谅 (想了解Hadoop内存溢出请看: Hadoop内存溢出(OOM)分类、参数调优化) JVM被分为三个主要的子系统 (1)类加载器子系统(2...
  • 深入理解 JVM 垃圾回收机制及其实现原理

    万次阅读 多人点赞 2018-05-31 13:47:17
    对于 JVM 来说,我们都不陌生,其是 Java Virtual Machine(Java 虚拟机)的缩写,它也是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM 有自己完善的硬件架构,如处理器、堆栈...
  • jstat是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存...[options] : 操作参数,一般使用 -gcutil 查看gc情况 VMID : 本地虚拟机进程ID,即当前运行的java进程号(PID) [interval] : ...
  • JVM进阶(五)——JAVA GC之标记

    万次阅读 多人点赞 2017-01-15 09:28:21
    JVM进阶(五)——GC之标记前言  堆分为年轻代和年老代。永久代是非堆内存,它又叫做方法区(一般的说法),主要存储已被加载的类信息、常量、静态变量。而该区域在java8已被删除,取而代之的是元空间,我会在后面的...
  • JVM内存管理及GC机制

    万次阅读 多人点赞 2015-09-08 16:28:18
    一、概述Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样...
  • 目前主流的JVM(HotSpot)采用的是分代收集算法。作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题。与C++不同的是,Java采用的是类似于树形结构的可达性分析法来判断对象是否还...
  • JVM结构、GC工作机制详解

    万次阅读 多人点赞 2016-11-04 12:26:51
    关于JVMGC,我相信学Java的绝大部分人都听过,很多公司的面试官都爱问,一开始我也很头痛,问这么底层干什么,所以我每次面试也只是看看答案敷衍了事。最近面完阿里感觉真不能这样,知识不仅要知其然,还要知其...
  • JVM GC策略

    千次阅读 2018-04-17 20:16:38
    静态内存   在Java中静态内存分配是指在Java被编译时就已经能够确定需要的内存空间,当程序被加载时系统把内存一次性分配给它。这些内存不会在程序执行时发生变化,直到程序执行结束时内存才会被回收。...
  • JVM GC日志分析

    千次阅读 2018-09-07 16:17:43
    分析gc日志后,经常需要调整jvm内存相关参数,常用参数如下 -Xms:初始堆大小,默认为物理内存的1/64(<1GB);默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制 -Xmx:...
  • JVM原理和机制 GC调优

    千次阅读 2017-02-19 11:02:39
    前言Java的JVM,可能学Java的都知道这个名字,博客或者百度也有一大堆,因为面试原因,大致也能说得上一些东西,今天重新梳理一下,一方面复习,一方面加深理解JVM的原理JVM可以理解成一个虚构出来的计算机,一个...
  • GC(Allocation Failure)引发的一些JVM知识点梳理

    万次阅读 多人点赞 2018-10-12 17:08:51
    日前查看某个程序的日志,发现一直在报GC相关的信息,不确定这样的信息是代表正确还是不正确,所以正好借此机会再复习下GC相关的内容: 以其中一行为例来解读下日志信息: [GC (Allocation Failure) [ParNew: ...
  • 面试题:JVMGC垃圾回收机制

    万次阅读 多人点赞 2018-04-17 13:04:50
    GC(垃圾收集),那收集回收的是什么呢?是内存,所以在了解垃圾回收机制之前,要对Java内存有一个了解。 一,Java内存 GC主要就是在Java堆中进行的。 Java堆:Java虚拟机管理的内存中最大的一块,...
  • jvmGC日志分析

    万次阅读 2015-06-22 16:37:38
    JVMGC日志的主要参数包括如下几个: -XX:+PrintGC 输出GC日志 -XX:+PrintGCDetails 输出GC的详细日志 -XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式) -XX:+PrintGCDateStamps 输出GC的时间戳(以...
  • 图文理解 JVM GC(Garbage Collection) 垃圾回收

    千次阅读 多人点赞 2019-05-28 19:26:28
    目录 前言 JVM如何判断一个对象是不是垃圾? 引用计数算法 可达性分析法(主流判断方法) ...哪些元素可以为根节点?...JVM堆的几种垃圾回收算法: ...JVM如何进行垃圾回收?...垃圾收集器(GC) 串行垃圾收集器(...
  • JVM GC垃圾回收算法与调优参数

    千次阅读 2019-03-24 21:31:37
    在Java当中,为了简化内存管理,JVM提供了自动垃圾回收机制,统一由JVM来进行垃圾对象回收,在Java应用程序当中只需要按需创建对象即可,不需要在应用程序中手动删除对象。 由之前的文章分析可知,在JVM的运行时数据...
  • jvm GC日志分析详解

    千次阅读 2018-05-18 11:36:59
    JVMGC调优,总结下CMS的一些特点和要点,让我们先简单的看下整个堆年轻代和年老代的垃圾收集器组合(以下配合java8完美支持,其他版本可能稍有不同),其中标红线的则是我们今天要着重讲的内容:垃圾回收器的可用...
  • JVM内存模型和GC算法分析

    万次阅读 2019-05-09 12:32:19
    JVM运行时数据区 JVM在运行过程中会把它所管理的内存划分成若干不同的数据区域。 线程私有:程序计数器、虚拟机栈、本地方法栈 (主要存放指令) 线程共享:堆、方法区 (主要存放数据) 一、程序计数器 程序...
  • 谈谈JVM GC 收集器

    千次阅读 2018-08-30 20:20:24
    本文基于收集器的发展路线,从前到后汇总和简单分析一下JVM垃圾收集器的roadmap。本文暂且从对内存区管理和回收特色方面分为分代收集和非分代两个part。   Part I、分代收集阶段 一、新生代收集器 1.Serial New...

空空如也

1 2 3 4 5 ... 20
收藏数 1,021,813
精华内容 408,725
关键字:

gc