jvm垃圾收集器理解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33127597/article/details/80024550
  • 1、java运行时区域

    运行时区域分为 两类:线程私有和线程共享。

    • 线程私有

      程序计数器:
              当前线程所执行的行号指令器。Java虚拟机的多线程是通过线程轮流切换并配置处理执行时间的方式来实现的,在同一个时刻,cpu只能执行一个线程,为了线程切换后能恢复到正确的
              执行位置,每个线程都需要有一个独立的程序计数器。
              若正在执行的是java方法,则计数器记录的是正在执行的字节码指令地址
              若正在执行的是native方法,则计数器为0
      java虚拟机栈
              java虚拟机也是私有的,它的生命周期与线程相同,虚拟机栈描述的是java方法执行的内存模型,每个方法执行的同时都会创建一个栈帧用于存放局部变量表、操作数栈,动态链接,方法出口等信息。
              局部变量表存放了编译期可知的基本数据类型,引用数据类型,和returnAddress类型(指向一条字节码指令地址),局部变量表的内存空间在编译器确定,在运行期不变。
              可导致两种异常:线程请求的栈深度大于虚拟机允许的深度-StarkOverflowError。虚拟机无法申请到足够的内存---OutOfMemoryError  
      本地方法栈
              和虚拟机栈类似,本地方法栈为虚拟机使用的native方法服务。本地方法也会抛出StackOverflowError 和OutOfMemoryError异常
      
    • 线程共享

      java堆
              java堆是所有线程共享的一块区域。在虚拟机启动时创建,主要是存放对象实例和数组。
              堆是垃圾回收器主要管理的区域:主要分为新生代和老年代
              从内存分配角度看,堆可划分出多个线程私有的分配缓冲区
              可以通过-Xmx  -Xms 分配堆内存
      方法区
              主要用于存放已经被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据
              Gc主要回收常量池和类型的卸载。
              运行时常量池是方法区的一部分
      运行时常量池
              Class文件的常量池用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放在运行时常量池中
              还把翻译出来的直接引用也放在运行时常量池中,运行时产生的常量也放在里面
      
  • 2、垃圾回收机制

    首先判断对象是否活着

    引用计数法
            给对象设置引用计数器,每引用该对象一次,计数器就加1,引用失效时,计数器就减1,当计数器为0时,则该对象就回收
            java并没有用到引用计数器,很难解决循环引用。
    
    可达性分析算法
            通过GC Roots对象作为起始点,从这些节点开始乡下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,证明此对象不可用。
            作为GC Roots对象包括:
                虚拟机栈(栈帧中的本地变量表)中引用的对象。
                方法区中静态属性引用的对象
                方法区中常量引用的对象
                本地方法栈中JNI(native方法)引用的对象
    

    在可达性分析过程中,对象引用类型会对对象的生命周期产生影响

    java几种类型的引用
        强引用:只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
        软引用:在系统将要发生内存溢出之前,将会把这些对象列进回收范围进行二次回收。SoftReference类来实现引用
        弱引用:非必须对象,只能生存到下一次GC收集之前,当垃圾收集工作时,无论当前内存是否足够,都会回收掉弱引用关联的对象。WeakReference类来实现引用
        虚引用:无法通过虚引用获得对象实例,也不会对对象的生存时间产生影响、唯一目的:当该对象被Gc收集时,收到一个系统通知。用PhantomReference类实现
    
    一个对象死亡,至少要经历两次标记过程  
        首先进行可达性分析,找出没有与GC Roots相连接的引用链,第一次标记。
        标记后进行筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法,或者finalize()已经被调用过了,就视为没有必要执行。
        若有必要执行,会放置在一个叫做F-Queue队列中,由jvm自动建立的低优先级的Finalizer线程去执行它。(但不会承诺会等待它运行结束)
        finalize()方法是对象最后一次自救机会,只要重新与引用链上的任何一个对象建立关联即可,则它会被移出要回收的对象的集合。其他对象则会被第二次标记,进行回收。
    
    回收方法区
        永久代中主要回收两部分内容:废弃常量和无用的类
        废弃常量回收
            举例:字符串 a 进入常量池,没有任何对象引用这个常量,如果发生内存回收,a常量就会被系统清理出常量池
        无用的类需满足3个条件
            该类的所有实例对象已被回收
            加载该类的ClassLoader已被回收
            该类的Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
    
  • JAVA中的垃圾回收算法有

    标记-清除算法(Mark-Sweep)
        两个阶段:标记、清除
        标记阶段:首先通过根节点,标记所有从根节点开始的可达对象。未被标记的对象就是未被引用的垃圾对象。
        清除阶段:清除所有未被标记的对象。
        缺点:两个阶段效率都不高,容易产生大量内存碎片。
    复制算法(Copying)
        把内存分为大小相同的两块,当一块的内存用完了,就把可用的对象复制到另一块上,将使用过的一块一次性都清除掉。
        缺点:浪费了一般内存
    标记-整理
        适用于存活对象较多的场合。如老年代。
        首先需要从根节点开始,对所有可达的对象做一次标记,将所有存活的对象压缩到内存的一端,之后,清理边界外所有的空间。
    分代收集
        依据对象的存活对象进行分类,短命对象为新生代,长命对象为老年代。
        少量对象存活,采用复制算法
        大量对象存活,采用标记清除或标记整理
    
  • GC内存分配规则

    堆分为新生代和老年代
        将新生代内存分为一块大的Eden区和两块小的Survivor,每次使用Eden和一个Survivor,回收时将EdenSurvivor存活的对象复制到另外一个Survivor(HotSpot的必烈为
Eden:Survivor:Survivor=8:1:1)
    对象优先在Eden区分配
        当Eden区没有足够的空间就会发起一次Minor GC
    大对象直接进入老年代
        典型的大对象是很长的字符串和数组
    长期存活的对象将进入老年代
        每个对象有年龄计数器,每经过一次GC,计数器值加一,当到达一定程度时(默认15),就会进入老年代年龄的阈值可通过参数 -XX:MaxTenuringThreshold设置
    动态对象年龄判断
        Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄对象就可以直接进入老年代,无须等到MaxTenuringThreshold要求的年龄
    空间分配担保
        发生Minor GC前,jvm会检查老年代最大可用的连续空间是否大于新生代所有对象总空间,若大于,则Minor GC是安全的
        若不大于,jvm会查看HandlePromotionFailure是否允许担保失败,若不允许,则改为一次Full GC
        若允许担保失败,则检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,若大于,则尝试进行Minor GC;若小于,则要改为Full GC

  • 垃圾收集器
serial(串行收集器)
    特点:单线程  采用复制算法  Stop The World(进行垃圾收集时,必须暂停其他所有的工作线程)
    应用:jvm在client模式下的默认新生代收集器
    优点:简单高效 (单线程收集器)

ParNew
    特点:Serial的多线程版本,采用复制算法
    应用:运行在Server模式下的虚拟机首选的新生代收集器。与CMS配合工作
    优点:在多CPU下,优势高于serial

Parallel Scavenge
    特点:并行多线程收集器,采用复制算法。吞吐量优先,自适应调节策略
    应用:吞吐量大的时候

Serial Old      
    特点: Servial收集器的老年代版本,单线程收集器,采用标记-整理算法
    应用:Client模式下使用。

Parallel Old
    特点:是Parallel Scavenge收集器的老年代版本。使用多线程和标记-整理算法
    应用:注重高吞吐场合,优先考虑Parallel Scavenge加Parallel Old收集器

CMS(Concurrent Mark Sweep)
     运作过程:
        初始标记:Stop The World,标记GC Roots能直接关联到的对象。
        并发标记:进行GC Roots Tracing(追踪)过程。
        重新标记:Stop The World,修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。
        并发清除:清除对象。
     特点:并发收集,低停顿,使用标记-清除算法 对CPU资源敏感
     缺点:无法处理浮动垃圾(并发清除 时,用户线程仍在运行,此时产生的垃圾为浮动垃圾)。产生大量空间碎片
     -XX:UseCMSCompactAtFullCollection: 用于顶不住进行Full GC时开启内存碎片合并整理过程。
     -XX:CMSFullGCsBeforeCompaction:用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的。

GC(Garbage-First)
     运作过程:  
        初始标记:stop the world 标记GC Roots能直接关联到的对象。并且修改NTMS(next top make start)的值,让下阶段用户程序并发运行时,能在正确可用的Region中创建对象。
        并发标记:可达性分析,找出存活的对象。
        最终标记:修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录
        筛选回收:筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划
    特点: 
        并行与并发:使用多个cpu缩短Stop The World停顿时间,部分其他收集器原本需要停顿java线程执行的GC操作,G1收集器仍然可以通过并发的方式让java程序继续执行。
        分代收集
        空间整合:从整体看是基于“标记-整理”的,从局部(两个region之间)看是基于“复制”的。
        可预测的停顿:使用者可明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
        面向服务端应用,将整个堆划分为大小相同的region

猜你喜欢

转载自blog.csdn.net/qq_33127597/article/details/80024550