zoukankan      html  css  js  c++  java
  • JVM笔记搬迁

     
    JVM
    JVM的内存结构包括五大区域:程序计数器、虚拟机栈、本地方法栈、堆区、方法区。
    堆分区:年轻代(Eden/Survivor)-年老代-永久区
    对象实例都是在堆上创建。一些类信息,常量,静态变量等存储在方法区。堆和方法区都是线程共享的
    从内存回收一个对象之前会调用对象的finalize()方法
     
    GC方式
    Minor GC触发时间:当Eden区满时;
    FULL GC触发时间:
    1⃣️System.gc()
    2⃣️老年代空间不足
    3⃣️方法去空间不足
    4⃣️通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    5⃣️由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
     
     
    回收对象
    程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。
    而Java堆区和方法区则不一样、不一样!(怎么不一样说的朗朗上口),这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。
     
    回收前需要判断对象是否存活的算法
    引用计数算法
    引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。
     
    优缺点
    优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
    缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。
    public class ReferenceFindTest { public static void main(String[] args) { MyObject object1 = new MyObject(); MyObject object2 = new MyObject(); object1.object = object2; object2.object = object1; object1 = null; object2 = null; } }
    这段代码是用来验证引用计数算法不能检测出循环引用。最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,那么垃圾收集器就永远不会回收它们。
     
    可达性分析算法
      可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。
     
    在Java语言中,可作为GC Roots的对象包括下面几种:
      a) 虚拟机栈中引用的对象(栈帧中的本地变量表);
      b) 方法区中类静态属性引用的对象;
      c) 方法区中常量引用的对象;
      d) 本地方法栈中JNI(Native方法)引用的对象。
     
    对象死亡(被回收)前的最后一次挣扎
      即使在可达性分析算法中不可达的对象,也并非是“非死不可”,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程。
      第一次标记:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记;
      第二次标记:第一次标记后接着会进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。在finalize()方法中没有重新与引用链建立关联关系的,将被进行第二次标记。
      第二次标记成功的对象将真的会被回收,如果对象在finalize()方法中重新与引用链建立了关联关系,那么将会逃离本次回收,继续存活。猿们还跟的上吧,嘿嘿。
     
    方法区如何判断是否需要回收?
      方法区主要回收的内容有:废弃常量和无用的类。对于废弃常量也可通过引用的可达性来判断,但是对于无用的类则需要同时满足下面3个条件:
    • 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
    • 加载该类的ClassLoader已经被回收;
    • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
     
    引用类型
    • 强引用
      在程序代码中普遍存在的,类似 Object obj = new Object() 这类引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
    • 软引用
      用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。
    • 弱引用
      也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
    • 虚引用
      也叫幽灵引用或幻影引用(名字真会取,很魔幻的样子),是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。它的作用是能在这个对象被收集器回收时收到一个系统通知。
      
    不要被概念吓到,也别担心,还没跑题,再深入,可就不好说了。
    无论引用计数算法还是可达性分析算法都是基于强引用而言的。
     
    监控命令
    jps主要用来输出JVM中运行的进程状态信息;
    jstack主要用来查看某个Java进程内的线程堆栈信息;
    jmap用来查看堆内存使用状况,一般结合jhat使用;
    jmap -dump:format=b,file=a.bin 13228
    jmap -heap pid查看进程堆内存使用情况,包括使用的GC算法、堆配置参数和各代中堆内存使用情况;
    jstat(JVM统计监测工具)
     
    回收算法
    标记-清除算法
      标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如下图所示。标记-清除算法不需要进行对象的移动,只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。
    复制算法
      复制算法的提出是为了克服句柄的开销和解决内存碎片的问题。它开始时把堆分成 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾 收集就从根集合(GC Roots)中扫描活动对象,并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。
    标记-整理算法
      标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。具体流程见下图:
    分代收集算法
      分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
     
     
     
     
    GC收集器
    1⃣️Serial收集器(新生代,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定。)
    Serial收集器是最基本的收集器,这是一个单线程收集器,它“单线程”的意义不仅仅是说明它只用一个线程去完成垃圾收集工作,更重要的是在它进行垃圾收集工作时,必须暂停其他工作线程,直到它收集完成。Sun将这件事称之为”Stop the world“。
    没有一个收集器能完全不停顿,只是停顿的时间长短。
    虽然Serial收集器的缺点很明显,但是它仍然是JVM在Client模式下的默认新生代收集器。它有着优于其他收集器的地方:简单而高效(与其他收集器的单线程比较),Serial收集器由于没有线程交互的开销,专心只做垃圾收集自然也获得最高的效率。在用户桌面场景下,分配给JVM的内存不会太多,停顿时间完全可以在几十到一百多毫秒之间,只要收集不频繁,这是完全可以接受的。
     
    2⃣️ParNew收集器(新生代 - 并行 复制算法,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数。)
    ParNew是Serial的多线程版本,在回收算法、对象分配原则上都是一致的。ParNew收集器是许多运行在Server模式下的默认新生代垃圾收集器,其主要在于除了Serial收集器,它是多CPU模式下的首选回收器(该回收器在单CPU的环境下回收效率远远低于Serial收集器,所以一定要注意场景哦),也是Server模式下的默认收集器。目前只有ParNew收集器能够与CMS收集器配合工作。
     
    3⃣️Parallel Scavenge收集器(新生代 - 并行 - 复制,是吞吐量优先的收集器)
    Parallel Scavenge收集器是一个新生代垃圾收集器,其使用的算法是复制算法,也是并行的多线程收集器。
    Parallel Scavenge 收集器更关注可控制的吞吐量,吞吐量等于运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)。直观上,只要最大的垃圾收集停顿时间越小,吞吐量是越高的,但是GC停顿时间的缩短是以牺牲吞吐量和新生代空间作为代价的。比如原来10秒收集一次,每次停顿100毫秒,现在变成5秒收集一次,每次停顿70毫秒。停顿时间下降的同时,吞吐量也下降了。
    停顿时间越短就越适合需要与用户交互的程序;而高吞吐量则可以最高效的利用CPU的时间,尽快的完成计算任务,主要适用于后台运算。
     
    4⃣️Serial Old收集器(老年代 标记-整理,老生代Client模式下的默认收集器,单线程执行;也是并发收集器CMS回收失败后的备用收集器)
    Serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,采用“标记-整理算法”进行回收。其运行过程与Serial收集器一样。
     
    5⃣️Parallel Old收集器(老年代 标记-整理 并行,是老生代吞吐量优先的一个收集器)
    Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法进行垃圾回收。其通常与Parallel Scavenge收集器配合使用,“吞吐量优先”收集器是这个组合的特点,在注重吞吐量和CPU资源敏感的场合,都可以使用这个组合。
     
     
    6⃣️CMS 收集器(老年代 标记-清除 并发 最短停顿时间)
    CMS(Concurrent Mark Sweep)收集器是一种以获取最短停顿时间为目标的收集器,CMS收集器采用标记--清除算法,运行在老年代。主要包含以下几个步骤:
    · 初始标记
    · 并发标记
    · 重新标记
    · 并发清除
     
    其中初始标记和重新标记仍然需要“Stop the world”。初始标记仅仅标记GC Root能直接关联的对象,并发标记就是进行GC Root Tracing过程,而重新标记则是为了修正并发标记期间,因用户程序继续运行而导致标记变动的那部分对象的标记记录。
    由于整个过程中最耗时的并发标记和并发清除,收集线程和用户线程一起工作,所以总体上来说,CMS收集器回收过程是与用户线程并发执行的。虽然CMS优点是并发收集、低停顿,很大程度上已经是一个不错的垃圾收集器,但是还是有三个显著的缺点:
     
    · CMS收集器对CPU资源很敏感。在并发阶段,虽然它不会导致用户线程停顿,但是会因为占用一部分线程(CPU资源)而导致应用程序变慢。
    · CMS收集器不能处理浮动垃圾。所谓的“浮动垃圾”,就是在并发标记阶段,由于用户程序在运行,那么自然就会有新的垃圾产生,这部分垃圾被标记过后,CMS无法在当次集中处理它们,只好在下一次GC的时候处理,这部分未处理的垃圾就称为“浮动垃圾”。也是由于在垃圾收集阶段程序还需要运行,即还需要预留足够的内存空间供用户使用,因此CMS收集器不能像其他收集器那样等到老年代几乎填满才进行收集,需要预留一部分空间提供并发收集时程序运作使用。要是CMS预留的内存空间不能满足程序的要求,这是JVM就会启动预备方案:临时启动Serial Old收集器来收集老年代,这样停顿的时间就会很长。
    · 由于CMS使用标记--清除算法,所以在收集之后会产生大量内存碎片。当内存碎片过多时,将会给分配大对象带来困难,这是就会进行Full GC。
     
     
     
    7⃣️G1收集器(标记-整理)
    G1收集器与CMS相比有很大的改进:
    · G1收集器采用标记--整理算法实现。
    · 可以非常精确地控制停顿。
    G1收集器可以实现在基本不牺牲吞吐量的情况下完成低停顿的内存回收,这是由于它极力的避免全区域的回收,G1收集器将Java堆(包括新生代和老年代)划分为多个区域(Region),并在后台维护一个优先列表,每次根据允许的时间,优先回收垃圾最多的区域 。
     
     
    G1优点:
    并行与并发
    能充分利用多CPU、多核环境下的硬件优势;
    可以并行来缩短"Stop The World"停顿时间;
    也可以并发让垃圾收集与用户程序同时进行;
     
    分代收集
    收集范围包括新生代和老年代
    能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配;
    能够采用不同方式处理不同时期的对象;
    虽然保留分代概念,但Java堆的内存布局有很大差别;
    将整个堆划分为多个大小相等的独立区域(Region);
    新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合;

  • 相关阅读:
    微信小程序之遮罩功能实现
    微信小程序之获取点击软键盘搜索按钮(confirm-type="search")之后的值
    python之路——闭包函数
    python之路——装饰器函数
    Python中的单例模式的几种实现方式及优化
    08-函数
    14-定时器
    13-JS中的面向对象
    12-关于DOM操作的相关案例
    17-案例
  • 原文地址:https://www.cnblogs.com/novalist/p/11621346.html
Copyright © 2011-2022 走看看