jvm内存模型与垃圾回收

*JVM历史
  JIT编译(just-in-time compilation)狭义来说是当某段代码即将第一次被执行时进行编译,因而叫“即时编译” 
      互联网-js
      人工智能-python
      微服务-go
  虚拟机:就是一台虚拟的计算机,他是一款软件,用来执行一系列计算机指令,大体上,虚拟机可以分为【系统虚拟机】和【程序虚拟机】。
       • 大名鼎鼎的Visual Box,VMware就属于系统虚拟机,它们完全是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台。
       • 程序虚拟机的典型代表就是Java虚拟机,它专门为执行单个计算机程序而设计,在Java虚拟机中执行的指令我们称为Java字节码指             令。
        无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。
  JVM特点:
       1.一次编译到处运行
       2.自动内存管理
       3.自动垃圾回收功能
       
   方法区跟堆是多线程共享,其他的为【java栈,本地方法栈,pc计数器】独有
   执行引擎:包括 解释器【逐行对字节码指令解释执行】,即时编译器【编译期后端,将源文件编译成字节码文件为编译期的前端,对热点代码直            接编译成机器指令并缓存】,垃圾回收器(操作系统只能识别机器指令,字节码文件不是机器指令,借助执行引擎将高级语言翻译为            机器语言),操作局部变量表和操作数栈
   
   HotSpot VM是目前市面上高性能虚拟机的代表作之一,它采用解释器与即时编译器并存的架构,只有一个程序计数器,基于栈的架构
   JVM架构模型:java编译器输入的指令流基本上是一种基于栈的指令集架构,另一种指令集架构则是基于寄存器的指令集架构
          基于栈式架构特点:
              1.设计和实现更简单,适用于资源受限的系统【机顶盒,打印机】
              2.避开了寄存器的分配难题;使用零地址指令方式分配
              3.不需要硬件支持,可移植性好,更好实现跨平台,指令集更小编译期更容易实现,单字节8为
          基于寄存器架构的特点:
              1.X86,Android,Davlik虚拟机
              2.完全依赖硬件,可移植性差
              3.性能优秀更高效,依赖cpu
              4.花费更少的指令去完成操作,双字节16位
              
   指令格式分为两部分:
          1.操作码OP:指令的操作特性与功能
          2.地址码A:参与操作的操作数地址
             零地址:不需要地址码,停机指令
             一地址:只有一个地址码  累加器
             二地址:分别指明参与操作的两个数在内存中或运算器中通用寄存器的地址,其中地址A1兼做存放操作结果的地址,
                    从操作的物理地址归结为三种类型
                         1.SS存储器-存储器:涉及内存,操作数都放到内存,需要多次访问内存
                         2.RR寄存器-寄存器:需多个通用寄存器或个别专用寄存器,执行较快不用访问内存
                         3.RS寄存器-存储器:既要访问寄存器,又要访问内存
             三地址:A1被操作数地址,A2为终点操作数地址,A3为存放操作结果的地址
    javap -v xxxx.class反编译  
    JVM生命周期:
          启动:通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)完成,这个类是由虚拟机的具体实现指                定的
          执行:一个运行中的java虚拟机有着一个清晰的任务:执行java程序
               程序开始执行他才运行程序结束时它就停止
               执行一个所谓的java程序时,真正执行的是一个叫做java虚拟机的进程
          退出:程序正常结束
               执行过程异常终止
               操作系统出错导致java虚拟机进程终止
               某线程调用Runtime类或者System类的exit或Runtime类的halt方法
               JNI(Java Native Interface)规范描述了用JNI Invocation API来加载或卸载Java虚拟机,java虚拟机的退出情况
   虚拟机:Classic VM、Exact VM、【HotSpot VM【方法区】、JRockit世界上最快的JVM,J9简称IT4J】、Azul VM、Liquid VM、            Apache Harmony、Microsoft JVM、Taobao JVM、Graal VM
*类加载子系统简单介绍
   类加载子系统:
        类加载子系统作用:加载阶段(引导类加载器,扩展类加载器,系统类加载器)->链接阶段(验证,准备,解析)->初始化阶段(初始化)	
        类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识(cafebabe,在链接阶段验证)
        ClassLoader只负责class文件的加载,至于他是否可以运行,则由ExcutionEngine决定
        加载的类信息存放在一块称为方法区的内存空间【jdk8方法区叫元空间是在本地内存】,除了类信息外,方法区中还会存放运行常量池信         息,可能还包括字符串字面量和数字常量(这部分常量信息是class文件中常量池部分的内存映射)
     类的加载过程一Loading:
             1.通过一个类的全限定名获取定义此类的二进制字节流
             2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
             3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
     类的加载过程二Linking:
            1.验证Verify:确保Class文件的字节流中包含信息符合当前虚拟机要求(CA FE BA BE)
            2.准备Prepare:为类的变量分配内存并且设置该类变量的默认初始值,即零值;不包含final修饰的static,因为final在编译              的时候就会分配了,准备阶段会显示初始化,这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对              象一起分配到java堆中
            3.解析Resolve:将常量池内的符号引用转换为直接引用的过程,直接引用就是直接指向目标的指针,相对偏移量或一个间接定位              到目标的句柄、
     类的加载过程三Initialization初始化:
             1.初始化阶段就是执行类构造器方法<clinit>()的过程,此方法不需要定义,是javac编译器自动收集类中的所有静态变量的赋                值动作和静态代码块中的语句合并而来【如果没有就不会有clinit这个方法了】,init方法时构造器方法
             2.<clinit>()不同于类的构造器
             3.若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕	
             4.虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁 
     类加载器的分类:
            引导类加载器(Bootstrap ClassLoader)
            自定义加载器(User-Defined ClassLoader){扩展类和系统类也属于自定义加载器}
            我们自己定义的类xxx.class.getClassLoader()是通过系统类加载器加载,String类是引导类加载器加载-->java核心类库             都是使用引导类加载器进行加载的
            引导类加载器:
               这个类使用C/C++语言实现的,嵌套在JVM内部
               他用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resource.jar或sun.boot.class.path路径下的内容)
               没有父类加载器
               加载扩展类和应用类加载器,并指定为他们的父类加载器	
               加载包名为java,javax,sun开头的类
            扩展类加载器:
               java语言编写,由sun.misc.Launcher$ExtClassLoader实现
               派生于ClassLoader
            系统类加载器:
               java编写
               派生于ClassLoader
               父类为扩展类加载器
               负责加载classpath或系统属性java.class.path指定路径下的类库
               该类加载是程序默认的类加载器,一般,Java应用的类都是它来完成加载的
            用户自定义类加载器:
               为什么要自定义类加载器
                 1.隔离加载类(当使用不同的框架在一个项目中,他们的jar包可能包含相同名称的类需要加载时使用)
                 2.修改类加载的方式
                 3.扩展加载源
                 4.防止源码泄露(容易被反编译)
               实现步骤:
                 1.继承java.lang.ClassLoader类的方式
                 2.把自定义的类加载逻辑写在findClass()方法中
                 3.也可以直接继承URLClassLoader类,避免自己写findClass()方法以及获取字节码流的方式
                 
   ClassLoader类:是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)
            获取当前类的ClassLoader:xxxx.getClassLoader()
            获取当前线程上下文的ClassLoader:Thread.currentThread().getContextClassLoader()
            获取系统的ClassLoader:ClassLoader.getSystemClassLoader().getParent()
            获取调用者的ClassLoader:DriverManager.getCallerClassLoader()
   
  *双亲委派机制
         java虚拟机对class文件是按需加载,加载某个类的class文件时,java虚拟机采用的就是双亲委派机制,即把请求交给父类处理,他          是一种任务委派模式。     
         工作原理:
           1.如果一个类加载器收到了类加载请求,它并不会自己先去加载而是把这个请求委托给父类加载器去执行
           2.如果父类加载器还存在父类加载器,则进一步向上委托,依次递归,请求最终将要到达顶层的启动类加载器;
           3.如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载
          优势:
            1.避免类的重复加载
            2.保护程序安全,防止核心API被随意篡改
      沙箱安全机制:
          自定义String类,但是在加载自定义String类的时候会率先使用引导类加载器,而引导类加载器在加载的过程中会先加载jdk自带的           文件(rt.jar包中java\lang\String.class),报错信息说没有main方法就是因为加载的是rt.jar包中的String类、这样可以保           证对java源代码的保护,这就是沙箱安全机制
      其他:
         在JVM中表示两个class对象是否为同一个类存在两个必要条件
                 1.类的完整类名必须一致,包括包名
                 2.加载这个类的ClassLoader(指ClassLoader实例)必须相同
          如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类信息的一部分保存在方法区中(就是在方法区中                    保存了类加载器信息)。当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的
          java程序对类的使用方式分为:主动使用和被动使用,被动使用都不会导致类的初始化
*内存与垃圾回收篇【运行时数据区】
      进程对应的区:【线程共享】
            1.方法区【jdk1.8元空间,在本地内存一般不会溢出】
            2.堆
      线程对应的区:
            1.程序计数器
            2.本地方法栈
            3.虚拟机栈
      垃圾回收95%是堆区的回收,5%是方法区回收
      栈是运行时的单位,而堆是存储的单位
      线程:线程是一个程序执行单元,JVM允许一个应用有多个线程并行执行,在Hotspot JVM里,每个线程都与操作系统的本地线程直接映             射,当一个java线程准备好执行以后,此时一个操作系统的本地线程也同时创建,Java线程执行终止后,本地线程也会回收;操作             系统负责所有线程的安排调度到任何一个可用的CPU上,一旦本地线程初始化成功,他就会调用Java线程中的run()方法
          1.守护线程:程序中只剩下守护线程JVM就可以退出了
          2.普通线程
      Hotspot JVM中的后台系统线程
          1.虚拟机线程 :JVM到达安全点才会出现
          2.周期任务线程:一般用于周期性操作的调度执行(中断)
          3.GC线程 :垃圾回收
          4.编译线程 :将字节码编译成本地代码
          5.信号调度线程:接收信号发给JVM
 
  *运行时数据区
   一、PC Register:
           JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟,PC寄存器是用来存储指向下一条指令的地址,也即将要执行的指令代                  码,由执行引擎读取下一条指令;它是一块很小的内存空间,几乎可以忽略不计,也是运行速度最快的存储区域,JVM中每个                  线程都有它自己的程序计数器,是线程私有的,生命周期与线程生命周期保持一致,任何时间一个线程都只有一个方法 也就                  是当前方法;计数器会存储当前线程正在执行的Java方法的JVM指令;如果是在执行native方法,则是未指(ndefined)
           它是程序控制流的指示器,分支,循环、跳转、异常处理、线程恢复等基础功能都需要计数器
           字节码解释器工作时就是通过改变计数器的值来选取下一条需要执行的字节码指令
           它是唯一一个在Java虚拟机规范中没有规定任何OutpOfemoryError情况的区域,也没有GC
                 
      两个常见问题:
          1.使用PC寄存器存储字节码指令地址有什么用呢?为什么使用PC寄存器记录当前线程的执行地址呢?
            因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行,JVM的字节码解释器就需要通过改变PC             寄存器的值来明确下一条应该执行什么样的字节码指令。
          2.PC寄存器为什么会被设定为线程私有?
            为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样各个线             程之间进行独立计算,从而不会出现相互干扰的情况、
  二、虚拟机栈:
        概念:每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧,对应着一次次的Java方法调用(一个栈帧对应一个方法);              线程私有生命周期和线程一致;
            作用:主管java程序的运行,它保存方法的局部变量(8种基本数据类型,对象的引用地址)、部分结果,并参与方法的调用和返回
            优点:栈是一种有效的分配存储方式,访问速度仅次于程序计数器,不存在GC
            
        虚拟机栈常见异常:jva虚拟机允许java栈的大小是动态的或者固定不变的
            OOM(OutofMemoryError):Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时                                    没有足够的内存去创建对应的虚拟机栈,那Java虚拟机就会抛OOM异常
            
            SOF(StackoverflowError):采用固定大小的java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选                                     定,如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个SOF                                     异常
            设置栈内存大小:-Xss选项设置线程的最大栈空间
          
        栈的存储单位:每个线程都有栈,以栈帧的格式存在,栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息
                    不同线程所包含的栈帧是不允许存在相互引用的
       栈帧内部结构:
            1.局部变量表(Loacal variables):
                 也被称为局部变量数组或本地变量表;
                 定义为一个数值类型数组(用数值型存储),主要用于存储方法参数和定义在方法体内的局部变量;
                 局部变量表所需要的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项                    中,在运行期间是不会改变大小的;当方法调用结束后,随着方法栈帧的销毁,局部变量也会随之销毁
               Slot的理解(槽):
                  局部变量表最基本的单元是Slot(变量槽),在局部变量表里,32位以内的类型只占用一个slot(包括returnAddress类                   型),64位的类型(long和double)占用两个slot;JVM会为局部变量表中的每一个Slot都分配一个索引,通过索引即可                   成功访问到局部变量表中指定的局部变量,访问变量表中64bit的局部变量值时需要索引前一个就可以;如果当前帧是由构                   造方法或者实例方法(非静态)创建的,那么该对象引用this将会存放在index为0的slot处,其余按参数表顺序继续排                     序;栈帧中的局部变量表中的槽位是可以重用的,局部变量过了它的作用域,新的局部变量就很可能会重复用
           2.操作数栈(表达式栈)Operand Stack
              栈:可以使用数组或者链表来实现,这里使用数组来实现的
                  每一个独立的栈帧中除了包含局部变量表以外,还包含一个后进先出(LIFO)的操作数栈,也称为表达式栈(Experssion)
                  操作数栈,在方法执行过程中,根据字节码指令,忘栈中写入数据或提取数据,即入栈/出栈,主要用于保存计算过程的中                   间结果,同时作为计算过程中变量临时的存储空间
                  操作数栈就是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会被创建,这个方法的操作数栈                   是空的,会确定深度max_stack的值。
                  操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈和出栈操作完成访问
                  如果别调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈、
              栈顶缓存技术:
                  因为JVM是基于栈式架构使用零地址指令,但完成一次操作需要更多的出栈入栈,这同时也就意味着将需要更多的指令分派                   次数和内存读/写次数,由于操作数是存储在内存中的因此频繁地执行内存读/写操作必然会影响执行速度,所以将栈顶元素                   全部缓存在物理CPU的寄存器中,降低对内存的读写次数,提升执行引擎的执行效率
            3.动态链接(指向运行时常量池的方法引用)
                  每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。包含这个引用就是为了支持当前方法的代码能够实                   现动态链接(Dynamic Linking),java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用                        (Symbolic Reference)保存在class文件的常量池里
              补充:
              方法的调用:在JVM中将【符号引用】转换为调用方法的【直接引用】与方法的绑定机制相关(#7这种)
                    1.静态链接
                        当一个字节码被装载进JVM内部时,如果被调用的目标方法【在编译期可知】,且运行期保持不变时,这种情况下将						调用方法的符号引用转换为直接引用的过程称之为静态链接
                    2.动态链接
                        如果被调用的方法【在编译期无法被确定下来】,只能在程序【运行期】将调用方法的符号引用转换为直接引用,						  由于这种引用转换过程具备动态性,因此称为动态链接
                 对应方法的绑定机制:早期绑定和晚期绑定,绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,仅发生一次
                    早期绑定:被调用的方法在编译期可知,且运行期保持不变(子类调用父类的方法)
                    晚期绑定:编译期无法被确定,只能在程序运行期根据实际的类型绑定相关方法(没指定实例)
               虚方法和非虚方法:
                    非虚方法:编译期确定,运行时不变称为非虚方法,静态方法、私有方法、final方法、实例构造器、父类方法都是
                    虚方法:其他方法
                    子类对象的多态性:1.类的继承关系2.方法的重写   
              JVM中方法调用指令:
                 普通调用指令:
                   1.invokestatic:调用静态方法,解析阶段确定唯一方法版本
                   2.invokespecial:调用<init>方法,私有方法,父类方法,解析阶段确定唯一方法版本
                   3.invokevirtual:调用所有虚方法
                   4.invokeinterfce:调用接口方法
                 动态调用指令
                   invokedynamic:动态解析出需要调用的方法,然后执行
                   动态类型语言和静态类型语言:
                       动态类型语言和静态类型语言区别是对类型的检查是在编译期还是在运行期,前者为静态,后者为动态;静态类型语						 言是判断变量自身的类型信息,动态类型语言是判断变量值的类型信息
                       Java:String info = "atguigu"
                       JS:var name="ssss" name = 10 动态改变
                 invokestatic和invokespecial方法调用的是非虚方法	final被invokevirtual调用
                 JVM为了提高性能在类的方法区建立一个虚方法表来实现。虚方法表在链接阶段的解析过程中创建并初始化
            4.方法返回地址(或方法正常退出或者异常退出的定义)
                 存放调用该方法的pc寄存器的值(为了返回定位到原来的方法)
                 方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址;而通过异常退出时,返				 回地址是要通过异常表来确定,栈帧中一般不会保存着部分信息。
                  一个方法在正常调用完成之后究竟需要使用哪一个返回指令还需要根据方法返回值的实际数据类型而定,返回指令包括                       ireturn(返回值是boolean byte char short int)、lreturn、freturn、dreturn以及areturn(引用类型)还				   有一个return指令声明为void的方法、实例方法、类和接口的初始化方法使用
            5.一些附加信息
                栈帧中还允许携带与java虚拟机实现相关的一些附加信息,例如,对程序调试提供支持的信息
                  
     补充:在栈帧中,与性能调优关系最为密切的部分就是前面提到的局部变量表,在方法执行时,虚拟机使用局部变量表完成方法的传递。
          局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。
       变量的分类:
          按照数据类型分:1.基本类型  2.引用数据类型
          按照在类中声明的位置分:
                     1.成员变量:在使用前都经历过默认初始化赋值
                         类变量(static) :linking的prepare阶段:给类变量默认赋值 --->initial阶段:给类变量显示赋值即静                                          态代码块赋值
                         实例变量:随着对象的创建会在堆空间中分配实例变量空间,并进行默认赋值
                     2.局部变量:使用前必须要显示赋值,否则不会编译通过 
    
       虚拟机栈的面试题:
           1.举例栈溢出的情况(StackOverflowError)
              通过-Xss设置栈的大小;内存空间不够会OOM
           2.调整栈的大小,就能保证不出现溢出吗?
              不能保证
           3.分配的栈内存越大越好吗
              不是,会占用别的空间
           4.垃圾回收是否会涉及到虚拟机栈?
              不会的!
           5.方法中定义的局部变量是否线程安全?
               具体问题具体分析(对象内部产生内部消亡是安全的)
 *本地方法接口
     一个Native Method就是一个Java调用非Java代码的接口。一个Native Method是这样一个Java方法:该方法的实现由非Java语言实		 现,比如C。这个特征并非Java特有,C++中可以用extern "C"告知C++编译器调用一个C函数。在定义一个native method时,并不提供      实现体,因为其实现体由非java语言在外面实现的。
     
    为什么要使用Native Method? 对效率有要求时使用或有些层次任务用java实现起来不容易
        与java环境外交互
        与操作系统交互
        Sun's Java:Sun的解释器是由C实现
       
 三、本地方法栈
        Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。线程私有,允许被实现成固定或者可动态扩展的内存         大小;如果线程请求分配的栈容量超过本地方法栈允许的最大容量(固定),Java虚拟机将会抛出一个StackOverflowError异常;
        动态扩展无法得到申请的内存或者在创建新线程时没有足够的内存去创建对应的本地方法栈,那么Java虚拟机将会抛出一个         		 OutOfMemoryError异常。
        具体做法:
            Native Method Stack中登记native方法,在Execution Engine执行时加载本地方法库
        当某个线程调用一个本地方法时,他就进入了一个全新的并且不再受虚拟机限制的世界,它和虚拟机拥有同样的权限,它可以直接使用本         地处理器中的寄存器,直接从本地内存的堆中分配任意数量的内存。
        在HotSpot JVM中,直接将本地方法栈和虚拟机栈合二为一
        
 四、堆
     1.概述:
        一个JVM实例只存在一个堆内存,堆也是java内存管理的核心区域、Java堆区在JVM启动的时候即被创建,其空间大小也就确定了。是		 JVM管理的最大一块内存空间,堆内存的大小是可调节的,所有线程共享java堆,在这里可以划分线程私有的缓冲区(Thread Local           Allocation Buffer,TLAB);所有对象实例以及数组都应当在运行时分配在堆上(几乎所有 逃逸分析有可能在栈上分配),方法结束后         堆中的对象不会马上被移除,仅仅在垃圾收集才会被移除,堆是GC(Garbage Collection)执行垃圾回收的终点区域
        
        现代垃圾收集器大部分基于分代收集理论设计,堆空间细分为
         java7及之前堆内存逻辑上分为三部分:新生区(Eden+servivor0+servivor1)[servivor0和1选一个]+养老区+永久区(方法区)
         java8及之后堆内存逻辑上分为三部分:新生区+养老区+元空间(方法区)
     2.设置堆内存大小与OOM:
         -Xms堆空间起始内存 等价于-XX:InitialHeapSize(年轻代+老年代)
              -X 是JVM的运行参数
              ms 是memory start
         -Xmx则用于表示堆空间最大内存,等于-XX:MaxHeapSize(年轻代+老年代)
         默认情况下,初始内存:物理内存/64
                   最大内存:物理内存/4
         开发中建议将初始堆内存和最大堆内存设置成相同的值
         查看设置的参数:方式一:jps ->jstat -gc 进程id
                      方式二:-XX:+PrintGCDetails
         OOM:OutOfMemory
     3.年轻代和老年代:
         存储在JVM中的java对象可以分为两类:
             1.一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速
             2.另一类对象的生命周期非常长,在某些极端的情况下还能够与JVM的生命周期保持一致
          -XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3  【默认是这个】
          可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5【命令行指令jinfo -flag NewRatio 进程id】
          在HotSpot中Eden空间和两个Survivor空间缺省所占比例是8:1:1,可以通过"-XX:SurvivorRatio=8"调整这个空间占比
          -XX:-UseAdaptiveSizePolicy:关闭自适应内存分配策略
          几乎所有的java对象都是在Eden区被new出来的
          绝大部分java对象的销毁都在新生代进行了
          使用"-Xmn"设置新生代最大内存大小
     4.图解对象分配过程:
          去老年代参数:-XX:MaxTenuringThreshold=<N>
          Eden区满触发YGC survivor区满不会触发(但不代表不会进行垃圾回收)
          survivor谁空谁是to
          老年代垃圾回收 FullGC MajorGC
     5.Minor GC、Major GC、Full GC:
        部分收集:
          新生代收集:Minor GC /Young GC
          老年代收集:Major GC/Old Gc
              目前,只有CMS GC会有单独收集老年代的行为
              注意,很多时候Major GC会和Full GC混淆使用,需要具体分辨是老年代回收还是整堆回收
          混合收集:Mixed GC收集整个新生代以及部分老年代的垃圾回收机制 目前只有G1 GC会有这种行为
        整堆收集:Full GC:收集整个java堆和方法区的垃圾收集	
        Minor GC:年轻代空间不足触发MinorGC,这里的年轻代满指的是Eden代满,Survivor满不会引发GC ,会引发STW
        Major GC:出现了MajorGC经常会伴随至少一次的MinorGC(但非绝对,在Parallel Scavenge收集器的收集策略里就有直接进行                     Major Gc的策略选择过程)
                  MajorGC的速度一般会比MinorGC慢10倍以上,STW的时间更长
                  Major GC后内存还不足,就报OOM了
        Full GC:
             1.调用System.gc()时,系统建议执行Full GC,但是不必然执行          
             2.老年代空间不足
             3.方法区空间不足
             4.Minor GC后进入老年代的平均大小大于老年代的可用内存
             5.由Eden区,s0向s1复制时对象大小大于to可用内存,则把该对象转到老年代,且老年代的可用内存小于该对象大小
             说明:开发中尽量避免Full Gc
     6.堆空间分代思想:
          分代是为了优化GC性能
     7.内存分配策略:
          优先分配到Eden
          大对象直接分配到老年代
          长期存活的对象分配到老年代
          动态对象年龄判断
               如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老                年代,无需等到MaxTenuringThreshold中要求的年龄
          空间分配担保:
               -XX:HandlePromotionFailure
     8.为对象分配内存:TLAB(Thread Local Allocation Buffer)
        什么是TLAB?
           堆线程共享,任何线程都可以访问堆区中的共享数据
           由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全
           为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度、
           从内存模型而不是垃圾收集的角度,对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内
           避免线程安全问题,同时能提升内存分配的吞吐量,这种分配方式称为快速分配策略
           尽管不是所有对象实例都能够在TLAB中成功分配内存,但JVM确实是将TLAB作为内存分配的首选
           -XX:UseTLAB设置是否开启TLAB(默认开启)
           仅占Eden1%
           -XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间百分比
           一旦对象在TLAB空间分配内存失败时,JVM就会尝试通过使用加锁机制确保数据操作的原子性,从而直接从Eden空间中分配
          
     9.小结堆空间的参数设置:
        常用参数:
           -XX:+PrintFlagsInitial 查看所有参数的默认初始值
           -XX:+PrintFlagsFinal   查看所有参数的最终值(可能会存在修改,不再是初始值)
                   具体查看某个参数的指令:jps 查看当前运行的进程
                                       jinfo -flag SurvivorRatio 进程id
           -Xms:初始堆空间内存(默认为物理内存的1/64)
           -Xmn:设置新生代的大小(初始值及最大值)
           -Xmx:最大堆空间内存(默认为物理内存1/4)
           -XX:NewRatio:配置新生代和老年代在堆结构中的占比
           -XX:SurvivorRation:设置新生代中Eden和s0/s1空间的比例
           -XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄
           -XX:+PrintGCDetails:输出详细的GC处理日志
           打印gc简要信息:1.-XX:+PrintGC 2.-verbose:gc
           -XX:HandlePromotionFailure:是否设置空间分配担保(JDK7之后失效)
     10.堆是分配对象的唯一选择吗?
           随着JIT编译期的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆            上也渐渐变得不那么"绝对"了;
           有一种特殊情况,如果经过逃逸分析(Escape Analysis)后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配
           TaoBaoVM创新的GCIH技术实现off-heap,将生命周期较长的java对象从heap中移到heap外
        逃逸分析概述:
           这是一种有效减少java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法,通过逃逸分析,java HotSpot编译器能够		   分析出一个新的对象引用的使用范围从而决定是否要将这个对象分配到堆上;
           逃逸分析的基本行为就是分析对象动态作用域:
              当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸【方法内部对象放到栈上】
              当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。例如作为调用参数传递到其他地方中
            jdk6之后默认开启了
        逃逸分析代码优化;
            1.栈上分配:将堆分配转化为栈分配
            2.同步省略:如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步
            3.分离对象或标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储               在内存(堆),而是存储在cpu寄存器中(java是栈)。
              标量(Scalar):是指一个无法再分解成更小的数据的数据,java中的原始数据类型就是标量。相对的可以分解的数据叫做聚合               量(Aggregate),java中对象就是聚合量,因为它可以分解成其他聚合量和标量-XX:+EliminateAllocations【默认开启】
 五、方法区(在本地内存就是非java虚拟机内存)
     字符串常量池从永久代(方法区)移到了堆中,运行时常量池在方法区中
     1.栈、堆、方法区的交互关系
          Person person = new Person();
          方法区    栈        java堆
     2.方法区的理解
         方法区启动的时候被创建,可以确定也可以动态扩展,方法区内存不需要物理连续
         java虚拟机规范中说方法区逻辑上属于堆的一部分,但其实是要分开的,方法区看作是一块独立于java堆的内存空间
         方法区和堆一样是线程共享的内存区域
         方法区的大小决定了系统可以保存多少个类,如果定义了太多的类,导致方法区溢出java.lang.OutOfMemoryError:PermGen                space 或者Metaspace,加载第三方jar包或Tomcat部署的工程太多 大量动态的反射类
         关闭JVM就会释放这个区域内存
       
       ·方法区的演进 
          jdk7之前称为永久代(落地实现),jdk8开始,使用元空间取代了永久代  
          在hotspot中方法区和永久代等价但是在别的虚拟机就不一定了
          元空间不在虚拟机设置的内存中,而是使用本地内存
     3.设置方法区大小与OOM
         jdk7之前 -XX:PermSize来设置永久代初始分配空间 默认值20.75M -XX:MaxPermSize 最大可分配空间 32位64M 64位82M
         jdk8以后 -XX:MetaspaceSize 默认21M -XX:MaxMetaspaceSize是-1就是没有限制
           到达这个21M后就会触发Full GC 尽量设置高一点避免频繁出现Full GC
         
         如何解决这些OOM?
           1.解决OOM或heap space异常,用工具对dump出来的堆转储快照进行分析,分清到底是出现了内存泄露还是内存溢出
           2.如果是内存泄露,查看泄露对象到GC Roots的引用链,于是就能找到泄露对象是通过怎样的路径与GC Roots相关联并导致垃圾              收集器无法自动回收他们的。
           3.如果不是内存泄露,调整物理内存
     4.方法区的内部结构
         方法区存储什么?
             它用于存储以被虚拟机加载的类型信息(接口枚举类注解)、常量、静态变量(7以后在堆)、即时编译器编译后的代码缓存
             类信息、域信息(属性)、方法信息
             non-final的类变量
                静态变量和类变量关联在一起,随着类的加载而加载,它们成为类数据在逻辑上的一部分
                类变量被类的所有实例共享,即使没有类实例时你也可以访问它
                被声明为final的类变量的处理方法则不同(static final),每个全局常量在编译的时候就会被分配了(在liniking的                   prepare阶段,而普通static变量是在prepare赋0在initialization的<clinit>阶段赋真实值)
         运行时常量池vs常量池
           常量池:一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述信息外,还包含一项信息那就是常量池表                      (ConstantPool Table),包括各种字面量(就是在""内部的一些数据或者是变量的数值)和对类型、域和方法的符号                       引用。
                     常量池,可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。
           运行时常量池:是方法区的一部分,常量池表是Class文件的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将       				  在类加载后存放到方法区的运行时常量池中,JVM为每个已加载的类型(类或接口)都维护一个常量池。池中的数据项像					  数组项一样,是通过索引访问的。(#索引)此时不再是常量池中的符号地址了,这里换为真实地址。相对于Class文件                       常量池它具备动态性(也就是存的常量可能比class文件内容多,String.intern())
     5.方法区使用举例
         
     6.方法区的演进细节演示
          1.首先明确:只有Hotspot才有永久代
          2.HotSpot中方法区的变化
             jdk1.6及之前:有永久代(permanent generation),静态变量存放在永久代上
             jdk1.7 有永久代,但已经逐步"去永久代",字符串常量池、静态变量移除,保存到堆中
             jdk1.8及以后 无永久代,类信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆
        永久代为什么要被元空间替代?
          1.永久代设置空间大小是很难确定的
          2.对永久代进行调优是很困难的 full GC 方法区主要回收常量池中废弃的常量和不再使用的类型
        StringTable为什么要调整?
          jdk7中放到了堆中,因为永久代的回收效率很低,在full GC的时候才会触发,而full gc是老年代的空间不足、永久代不足时才会           触发。这就导致StringTable回收效率不高。而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆             里,能及时回收内存
        静态变量存放在哪?
           静态引用对应的对象实体始终放在堆空间(new 的东西)
           非静态成员变量放在堆中
           静态成员变量在堆中
     7.方法区的垃圾回收
         java虚拟机规范对方法区的约束很宽松,提到过可以不要求虚拟机在方法区中进行垃圾回收,一般来说这个区域的回收效果令人很难满          意,尤其是类型的卸载,条件相当苛刻,但是有时也确实有必要;方法区的垃圾回收主要两部分:常量池中废弃的常量和不再使用的类          型;
         方法区存放的两大类常量:字面量和符号引用
                 字面量比较接近java语言层次的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理的概					 念,包括下面三类常量:1.类和接口的全限定名2.字段的名称和描述符3.方法的名称和描述符
            
     8.总结
        
 *对象
   一、对象实例化的几种方式
         1.对象的实例化
             a.属性的默认初始化- b.显示初始化/c.代码块中初始化  d.构造器中初始化
         2.对象的内存布局
             指针碰撞
             空闲列表
         3.对象的访问定位
   二、对象的内存布局
         1.对象头
             a.运行时元数据:哈希值、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
             b.类型指针:指向类元数据InstanceKlass,确定该对象所属的类型
             如果是数组,还需要记录数组的长度
         2.实例数据
            说明:他是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)
            规则:相同宽度的字段总会被分配在一起,父类中定义的变量会出现在子类之前、如果CompactFields参数为true(默认为					 true):子类的窄变量可能插入到父类变量的空隙
         3.对齐填充
   三、对象的访问定位
         对象访问两种方式:
            1.句柄访问:reference->java堆中的句柄池(包含到对象实例数据的指针和到对象类型数据的指针)->对象实例数据
                好处:对象地址改变后不用改变栈中引用的地址只需要改变句柄池就可以
            2.直接指针(Hotspot):reference->java堆对象实例数据->方法区对象类元数据
            
 *直接内存(元空间使用本地内存)
    不是虚拟机运行时数据区的一部分,直接内存在java堆外、直接向系统申请的内存空间;来源于NIO通过存在堆中的DirectByteBuffer操作     Native内存;通常情况下访问直接内存的速度会优于java堆读写性能高;也可能OOM:Direct buffer memory异常;分配回收成本较高,     不受jvm管理;直接内存大小可以通过MaxDirectMemorySize设置,如果不指定默认大小与堆大小一致
    
    IO                 NIO(New IO/Non-Blocking IO)
    byte[]/char[]      Buffer
    strean             Channel
*执行引擎
   1.概述
      “虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行的能力,其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集       和操作系统层面上的,而虚拟机的执行引擎则是由软件自行实现的,因此可以不受物理条件约束地定制指令集与执行引擎的结构体系,能够       执行那些不被硬件直接支持的指令集格式;java虚拟机将负责装载字节码文件到虚拟机中,然后执行引擎将字节码指令解释编译为对应操作       系统上的本地机器指令(后端编译);执行引擎依赖于PC寄存器
   2.java代码编译和执行过程
       	程序源码->词法分析->语法分析->抽象语法树【前端编译完成.java->.class】
       	中间代码生成器->目标代码【java虚拟机执行引擎编译过程】
       	解释器->解释执行【java虚拟机执行引擎解释过程】效率低下
   3.机器码、指令、汇编语言
        机器码:二进制编码的指令
        指令:把机器码中特定的0和1序列简化对应的指令(如mov,inc) 平台有关
        汇编:用助记符代替机器指令的操作码,汇编也要翻译成机器指令才能执行
        高级语言->汇编语言(对java来说就是前端编译为字节码的过程)->机器指令
   4.解释器(解释器->解释执行)
      java虚拟机启动根据规范对字节码采用逐行解释的方式执行,翻译为本地机器指令执行
      
   5.JIT编译器(中间代码生成器->目标代码)
      虚拟机将源码直接编译成和本地机器平台相关的机器语言 
   
解释器与编译器并存?
   解释器响应速度快,程序启动就可以发挥作用;编译器要先把代码翻译成本地代码(机器指令)需要一定的执行时间
      
      前端编译器:.java->.class  sun的javac、
     后端编译器(JIT)  .class->机器代码 HotSpot VM的C1(client模式下),C2编译器(server模式下)
     静态提前编译器(AOT ahead of Time Compiler)在程序运行之前,便将字节码转换为机器码过程
   6.热点代码探测
      一个被多次调用的方法,或者是一个方法体内部循环次数较多的循环体都可以被称为"热点代码",由于这种编译方式发生在方法的执行过程       中,因此被称之为栈上替换,或称为OSR(On Stack Replacement)编译
      目前采用基于计数器的热点探测:
           方法调用计数器:默认阈值client模式1500 在server模式下10000次 超过这个阈值就会出发JIT编译
                  热度衰减:一段时间之内方法被调用的次数, 半衰周期
           回边计数器:统计方法中循环体代码执行的次数
      -client:c1编译器会对字节码进行简单和可靠的优化,耗时短,以达到更快的编译速度
           方法内联
           去虚拟化
           冗余消除
      -server:c2进行耗时较长的优化,以及激进优化,但优化以后的代码执行效率高
           标量替换
           栈上分配
           同步消除
*StringTable
   1.String的基本特征
      “”引起来,声明final不可被继承,实现了serializable,char[]行数组(jdk1.8),byte[]数组(jdk1.9),不可变性
      字符串常量池是不会存储相同内容的字符串的,String的String Pool是一个固定大小的Hashtable,使用-XX:StringTableSize可	   以设置大小
      
   2.String的内存分配
      直接使用双引号声明出来的String对象会直接存储在常量池中
      如果不是双引号声明的String对象,可以使用String提供的intern()方法
      StringTable为什么要调整?
         permsize默认比较小、永久代垃圾回收评率低
   3.String的基本操作
      前面有了字面量 后面相同的就不用重新创建了直接引用
   4.字符串拼接操作
      1.常量与常量的拼接结果在常量池,原理是编译优化(常量引用final修饰的变量也会编译优化)
      2.常量池中不会存在相同内容的常量
      3.只要其中一个是变量,结果就在堆中(相当于new String())、变量拼接的原理就是StringBuilder【会创建两个对象】
            String s4 = s1 + s2细节?
              a.StirngBuilder s = new StringBuilder();
              b.s.append(s1)
              c.s.append(s2)
              d.s.toString()  约等于new String()
            补充:在jdk5.0之后使用StringBuilder 之前使用的是StringBuffer
      4.如果拼接的结果要用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址
   5.intern()的使用
       data.intern:如果字符串常量池中没有对应的data的字符串的话,则在常量池中生成常量,如果存在返回这个字符串的地址
       如果不是用双引号声明的String对象,可以使用String提供的intern方法:intern方法会从字符串常量池中查询当前字符串是否存	   在,若不存在就会将字符串放入常量池中 例如:String s = new String("abc").intern();
       ("a"+"b"+"c").intern() == "abc"
       如何保证变量s指向的是字符串常量池中的数据呢?
          方式1:String s = "avbc"
          方式2:String s = new String("dfdas").intern()如果没有调用intern常量池中应该也创建了常量但是引用的是堆中的地                 址
                String s = new StringBuilder("sdfasf").toString().intern();
      面试题:
        1.new String("ab")会创建几个对象?
            2个,一个是new关键字在堆空间创建的,另一个对象是字符串常量池中的对象
        2.new String("a")+new String("b")呢?
            对象1:new StringBuilder()
            对象2:new String("a")
            对象3:常量池中的"a"
            对象4:new String("b")
            对象5:常量池中的"b"
            
            深入剖析:StringBuilder的toString()[toString使用字节数组创建String,String str=new String("ab")字面量]
              对象6:new String("ab")
              强调:toString()的强调,在字符串常量池中,没有生成"ab"
      【个人理解】
           String s=new String("ab")时 在堆中开辟空间,然后"ab"会先在常量池中创建常量对象 									   再把常量放到堆中对象中但是返回给 s的还是堆的地址并不是常量"ab"的地址;String s3=new String("a")+new        			 String("b")相当于new String("ab")但是不会在常量池中创建"ab"常量对象,s3.intern之后就会(jdk1.6是在常量池中创            建对象,但是1.7之后为了节省内存直接引用了堆中的对象中的常量的地址)
   6.StringTable的垃圾回收
           -Xms:15m -Xmx15m -XX:PrintStringTableStatics -XX:+PrintGCDetails
   7.G1中的String去重操作
           去底层new的string对象char[]数组对象,不是常量池去重
*垃圾回收
   一、垃圾回收概述
       1.什么是垃圾
         垃圾是指在运行程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾,如果不及时处理垃圾会保持到程序结束,会导致内		 存溢出
           哪些内存需要回收?
           什么时候回收?
           如何回收?
       2.为什么需要GC
         不回收内存会满
       3.早期垃圾回收
          c/C++ 手动回收,new申请内存,delete内存释放
       4.java垃圾回收机制
          自动内存管理,无需开发人员手动参与内存的分配与回收,这样降低内存泄露和内存溢出的风险,没有垃圾回收器,java也会和cpp一		  样,各种悬垂指针,野指针,泄露问题让你头疼不已;如果过度依赖“自动”会弱化java开发人员在程序出现内存溢出时定位问题和解			  决问题的能力。要实施必要的监控和调节
   二、垃圾回收算法
        垃圾标记阶段:对象存活判断,在GC执行垃圾回收之前,首先需要区分内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为	                 已经死亡的对象,GC才会在执行垃圾回收时,释放掉其所占用的内存空间,因此这个过程我们可以称为垃圾标记阶段
                     当一个对象不在被任何的存活的对象继续引用时,就可以宣判已经死亡
             判断对象存活方式:引用计数算法和可达性分析算法
      1.标记阶段:引用计数算法
         对每个对象保存一个整型的引用计数器属性。用于记录对象被引用的情况。值为0就可以回收了
         优点:实现简单,垃圾对象便于辨识,判定效率高,回收没有延迟性
         缺点:需要单独的存储计数器,存储空间开销
              每次赋值都要更新计数器,时间开销
              【无法处理循环引用】的情况,致命缺陷,导致在java的垃圾回收器中没有使用这类算法
         java没有使用引用计数算法,python使用了引用计数算法【手动解除循环引用,使用弱引用weakref】
        
      2.标记阶段:可达性分析算法(根搜索算法、追踪性垃圾收集)
          解决了在引用技术算法中循环引用的问题,防止内存泄漏的发生
          1.以根对象集合(GC Roots)【一组活跃的引用】为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达
          2.使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链(Referen chain)
          3.目标没有任何引用链相连,则是不可达的,意味着该对象以死亡,可以标记为垃圾对象
          4.只有能够被根对象集合直接或者间接连接的对象才是存活对象
         java中GC Roots包括以下几类元素:
            虚拟机栈中引用的对象:比如各个线程被调用的方法中使用到的参数、局部变量等
            本地方法内JNI(通常说的本地方法)引用的对象
            方法区中类静态属性引用的对象(后面在堆空间) 比如java类的引用类型静态变量
            方法区中常量引用的对象(后面也在堆) 比如字符串常量池里的引用
            所有被同步锁synchronized持有的对象
            java虚拟机内部的引用 比如;基本数据类型对应的Class对象,一些常驻的异常对象,系统类加载器
            反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
          补充:除了固定的GC Roots之外,根据用户所选用的垃圾收集器以及回收的内存区域不同,还可以有其他对象"临时性"地加入,共同				构成完整的GC Roots 比如要回收新生代的时候老年代就要加入GC Roots集合      
          
      3.对象的finalization机制
         当垃圾回收器发现没有引用指向一个对象,即:垃圾回收此对象之前,总会先调用这个对象的finalize()方法(只会调用一次)
         finalize()方法允许在子类中被重写,用于在对象被回收时进行资源释放比如关闭文件、套接字和数据库连接等
         永远不要主动调用某个对象的finalize()方法,应该交给垃圾回收机制调用,
              在finalize()时可能会导致对象复活
              finalize()方法的执行时间是没有保障的,它完全由GC线程决定极端情况下若不发生GC,则finalize()方法将没有机会执行
              一个糟糕的finalize会严重影响GC的性能
         由于finalize()方法的存在,虚拟机中的对象可能存在三种状态
              可触及的:从根结点开始,可以达到这个对象
              可复活的:对象的引用都被释放,但是对象可能在finalize()中复活
              不可触及的:对象的finalize()被调用,并且没有复活那么就会进入不可触及状态
      4.MAT与JProfiler的GC Roots 溯源  
          
      5.清楚阶段:标记-清除算法(Mark-Sweep)
           执行过程:第一项是标记,第二项是清除
             标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象
             清除:Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收
           缺点:
             效率不算高
             在进行gc时需要停止整个应用程序,导致用户体验差
             清理出来的空闲内存是不连续的,产生内存碎片、需要维护一个空闲列表
           何为清除:
             这里所谓的清除不是置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新的对象需要加载时,判断垃圾的位置空			   间是否够,如果够就存放
          
      6.清除阶段:复制算法(解决标记清除算法收集效率方面的缺陷)
           将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后			清除正在使用的内存块中的所有对象,交换两个内存角色,最后完成垃圾回收
          优点:
            没有标记和清除阶段,实现简单,运行高效
            复制过去以后保证空间的连续性,不会出现 碎片问题  可以用指针碰撞
          缺点:
            需要两倍的内存空间
            对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间			 开销也不小
            垃圾太多就不好用了,复制次数太多了
      7.清除阶段:标记-压缩算法(Mark-Compact)	
           复制算法在存活对象少,垃圾多前提下使用,这种情况在新生代经常发生,但在老年代,更常见的情况是大部分对象都是存活对象              如果依然使用复制算法,成本就会很高,因此需要使用其他算法
           执行过程:
             1.和标记清除算法一样,从根结点开始标记所有被引用对象
             2.将所有的存活对象压缩到内存的一端,按顺序排放
             3.清理边界外所有的空间
            优点:
              消除了标记清除内存区域分散的缺点
              给新对象分配内存时,jvm只需要持有一个内存起始地址即可
              消除了复制算法中内存减半的高额代价
            缺点:
              效率上说,标记-整理算法要低于复制算法
              移动对象时,如果对象被其他对象引用,则需要调整地址
              移动过程中,需要全程暂停用户应用程序stw
              
      8.小结
      9.分代收集算法
           不同生命周期的对象可以采用不同的收集方式,以便提高回收效率
           年轻代:区域相对老年代较小,对象生命周期短、存活率低,回收频繁。用复制算法比较好
           老年代:区域大,对象生命周期长,复制算法明显变得不合适,一般是由标记-清除或者标记-压缩混合实现
                 HotSpot中的CMS回收器为例,CMS是基于Mark-Sweep实现的,效率高,对于碎片问题CMS采用Mark-Compact算法的					 Serial Old回收器作为补偿措施;当内存回收不佳,将采用Serial Old执行FUll GC以达到对老年代内存的整理
 
      10.增量收集算法、分区算法
         增量收集算法:
             上述现有的算法在垃圾回收时会stw,如果垃圾回收长,应用程序会被挂起很久,将严重影响用户体验或者系统稳定性,增量收集				 算法就是解决这个问题
           基本思想:
             如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次垃圾 				收集线程只收集一小片区域的内存空间,接着切换到应用程序线程,依次反复,直到垃圾收集完成;总的来说,增量收集算法的基			  础仍是传统的标记-清除和复制算法。增量收集算法通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、    			 清理或复制工作
           缺点:
             因为线程切换和上下文转换的消耗会使得垃圾回收的总体成本上升,造成系统吞吐量的下降
             
         分区算法:
             一般来说,在相同条件下,堆空间越大,一次GC时所需要的时间就越长,有关GC产生的停顿也越长。为了更好地控制GC产生的停    		  顿时间,将一块大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间,从而减				少一次GC所产生的停顿;
             分代算法将按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同小区间
             每一个小区间都独立使用,独立回收

  三、垃圾回收的相关概念 
      1.System.gc()的理解
          通过System.gc()或者Runtime.getRuntime().gc()的调用,会显式触发Full GC,同时对老年代和新生代进行回收
          然而System.gc()调用附带一个免责声明,无法保证对垃圾收集器的调用(提醒jvm希望进行gc,不确定是否马上进行)
          无须手动触发,自动进行
          当调用System.runFinalization()后悔强制调用使用的引用的对象的finalize()方法
          
      2.内存溢出与内存泄露
          OOM:没有空闲内存,并且垃圾收集器也无法提供更多内存
             1.java虚拟机的堆内存设置不够
             2.代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(cun存在引用)
            当然,也不是任何情况下垃圾收集器都会被触发:比如分配一个超大对象超过堆的最大值直接OOM
         内存泄露(Memory Leak):只有对象不会再被程序用到了,但是GC又不能回收它们的情况,才叫内存泄露;但实际情况很多时候一些								不太好的实践会导致对象的生命周期变得很长很长甚至导致OOM,也可以叫做宽泛意义上的“内存泄漏”
           举例:
              1.单例模式
                 单例的生命周期和应用程序是一样长的,所以单例程序中,如果持有对外部对象的引用的话,那么这个外部对象是不能被回收				 的,则会导致内存泄露的存在。(单例中创建的对象引用了外部对象忘记断开就会一直存在引用)
              2.一些提供close的资源未关闭导致内存泄露
                 数据库连接(dataSource.getConnection()),网络连接(socket)和io连接必须手动close,否则是不能被回收的
            
      3.Stop The World
          GC事件发生过程中,会产生应用程序停顿
          可达性分析算法中枚举根节点(GC Roots)会导致所有java执行线程停顿。
             分析工作必须在一个确保一致性的快照中进行
             如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证
          STW事件和采用哪款GC无关,所有GC都有这个事件
          后台自动发起和自动完成的
          开发中不要用System.gc()
          
      4.垃圾回收的并行与并发
          并发(Concurrent):是指一个时间段中有几个程序都处于已启动运行到运行完毕之间且这几个程序都是在同一个处理器上运行
          并行(Parallel)当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资		   源,可以同时进行。
           垃圾回收方面:
               并行(parallel):指多条垃圾线程并行工作,但此时用户线程处于等待状态
               串行(Serial):单线程执行
               并发(Concurrent):用户线程与垃圾收集线程同时执行 如:CMS、G1
      5.安全点与安全区域
      安全点:  
          程序并非在所有地方都能停下来GC,只有特定位置才可以GC,这些位置叫“安全点”
          如果安全点太少导致GC等待时间太长,如果太多可能导致运行时的性能问题,通常会根据“是否具有让程序长时间执行的特征”为标准
          比如选择一些执行时间较长的指令作为safe point,如方法调用、循环跳转和异常跳转等
          
          如何在GC发生时,检查所有线程都跑到安全点停顿了下来?
             抢断式中断:(目前没有虚拟机采用了)
                  首先中断所有线程。如果还有线程不在安全点,就恢复线程,让线程跑到安全点
             主动式中断:
                  设置一个中断标志,各个线程运行到safe point的时候主动轮询这个标志,如果中断标志为真,则将自己进行中断挂起
       安全区域:
          线程处于sleep状态或blocked状态,这时候线程无法响应JVM中断请求,“走”到安全点去中断挂起,JVM也不太可能等待线程被唤		  醒,这种情况就需要安全区域(safe Region)来解决。安全区域是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域			中的任何位置开始GC都是安全的
      6.强、软、弱、虚、终结器引用
         强(StrongReference):Object Obj=new Object()无论任何情况下只要强引用关系还在,垃圾收集器就永远不会回收
         					 强引用可能导致内存泄露
         					 可以直接访问对象
         软(SoftReference):在系统要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收。回收后还没有足够的内存,才			 				  会抛出内存溢出异常(内存不足即回收)
           				   高速缓存就有用到软引用,如果还有空闲内存就可以暂时保留缓存当内存不足时清理掉保证使用缓存不耗尽内存 
         				   当内存足够时不会回收软引用对象,不够时才回收
         弱(WeakReference):被弱引用关联的对象只能生存到下一次垃圾收集之前。当垃圾收集器工作时,无论内存空间是否足够,都会回收							  掉被弱引用关联的对象(发现即回收)
          				   弱引用和软引用一样,在构造弱引用时,也可以指定一个引用队列,当弱引用对象被回收时,就会加入指定队							   列,通过这个队列可以跟踪对象的回收情况
          				   软引用、弱引用都非常适合来保存那些可有可无的缓存数据 
          				   三级缓存:内存->本地->网络
         虚(PhantomReference):一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实 						  例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知(对象回收跟踪)
                          它不能单独使用,也无法通过虚引用来获取被引用的对象,当试图通过虚引用的get()方法获取对象时,为null
                          虚引用必须和引用队列一起使用。虚引用在创建时必须提供一个引用队列作为参数。当垃圾回收器准备回收一个							  对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知对象的回收情况
                          可以将一些资源释放操作放置在虚引用中执行和记录
         终结器引用(Final reference):他可以实现对象的finalize()方法,也可以称为终结器引用,无需手动编码,其内部配合引用队列								   使用。在GC时,终结器引用入队。由Finalizer线程通过终结器引用找到被引用对象并调用它的 									   finalize()方法,第二次GC时才能回收被引用对象
  
四、垃圾回收器
   1.GC分类与性能指标
       按线程数分,可以分为串行垃圾回收器和并行垃圾回收器(多cpu)
       按工作模式分,分为并发式(交替工作)和独占式垃圾回收器
       按碎片处理方式分,可以分为压缩式回收器和非压缩式垃圾回收器
       按工作的内存区间分,可以分为年轻代和老年代回收器
       
       评估垃圾回收器性能指标:
          *吞吐量:运行用户代码的时间占总运行时间的比例(总运行时间:程序的运行时间+内存回收的时间)	【a/a+b】
          垃圾收集开销:吞吐量的补数,垃圾收集所用的时间与总运行时间的比例[b/a+b]
          *暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间
          收集频率:相对于应用程序的执行,收集操作发生的频率
          *内存占用:Java堆区所占内存的大小
          快速:一个对象从诞生到被回收所经历的时间
          简单来说,主要抓住两点:
                      吞吐量:这种情况应用程序能容忍较高的暂停时间,高吞吐量的应用程序有更长的时间基准,快速响应是不必考虑的
                      暂停时间
                    高吞吐量和低暂停时间是一对相互竞争的目标:
                       选择以吞吐量优先,那么必然需要降低内存回收的执行频率,但是这样会导致GC需要更长的暂停时间来执行内存回收
                       选择以低延迟优先,为了降低每次执行内存回收时的暂停时间,只能频繁执行内存回收,引起年轻代内存的缩减和导						 程序吞吐量下降
          现在的标准:在最大吞吐量优先的情况下,降低停顿时间
   2.不同的垃圾回收器概述
       串行回收器:Serial、Serial Old
       并行回收器:ParNew、Parallel Scavenge、Parallel Old
       并发回收器:CMS、G1
       
       7款经典收集器与垃圾分代之间的关系
          young Gen    Serial GC      Parallel Scavenge GC   ParNew GC            G1
          old Gen      Serial Old GC  Parallel Old           CMS GC               G1
           
       如何查看默认的垃圾回收器是什么?
        -XX:PrintCommandLineFlags:查看命令行相关参数(包含使用的垃圾收集器)
        使用命令行指令:jinfo -flag 相关垃圾收集器参数 进程ID
   3.Serial回收器:串行回收、
       Client模式下默认的新生代垃圾回收期
       Serial收集器采用复制算法、串行回收和“stop the world”机制的方式执行内存回收
       对老年代提供了Serial Old收集器,也采用了串行回收和"stop the world"机制,只不过内存回收算法使用了标记-压缩算法
           Serial Old是运行在Client模式下默认老年代垃圾回收器
           Serial Old在Server模式下主要有两个用途:
                     1.新生代的Parallel Scavenge配合使用
                     2.作为老年代CMS收集的后备垃圾收集方案
       优点:
           在单个cpu环境下高效,运行在Client模式下是不错的
       用指令 -XX:+UseSerialGC可以指定回收器
     
      现在已经不用了,只能在单核情况下使用
       
   4.ParNew回收器:并行回收
      ParNew收集器就是Serial收集器的多线程版本,处理新生代垃圾回收,采用复制算法
      CMS配合回收老年代(备用Serial Old)
      -XX:+UseParNewGC【jdk9中被遗弃】
      -XX:ParallelGCThread限制线程数量默认开启和cpu数据相同的线程数
      -XX:+UseConcMarkSweepGC用CMS处理老年代
   5.Parallel回收器:吞吐量优先
       也采用了复制算法、并行回收和"stop the world"机制
       和ParNew收集器不同,Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量,它也被称为吞吐量优先的垃圾收集器
       自适应调节策略也是Parallel Scavenge与ParNew一个重要区别
       高吞吐量可以高效运用cpu时间,尽快完成程序运算任务,主要适合在后台运算而不需要太多的交互任务。常见在服务器环境中使用。
       例如执行批量处理、订单处理、工资支付、科学计算应用程序
       用Parallel Old用于执行老年代的收集,采用了标记压缩算法,但同样也基于并行回收和"stop the world"机制 
       在server模式下会发挥良好的效果
 
       参数设置:
          -XX:+UseParallelGC 年轻代使用Parallel并行收集器
          -XX:+UseParallelOldGC 老年代使用并行回收收集器
             上面两个默认开启一个,另一个也会被开启,jdk8默认开启
          -XX:ParallelGCThreads设置年轻代并行收集器的线程数,一般地,最好与cpu数量相等【小于8用相等数量 大于3+[5*cpu]/8】
          -XX:MaxGCPauseMillis设置垃圾收集器最大停顿时间,对客户越短越好,对服务器吞吐量更重要
          -XX:GCTimeRatio:垃圾收集时间占总时间的比例(=1/(N+1)) 默认N为99,也就是垃圾回收时间不超过1%
          -XX:+UseAdaptiveSizePolicy 设置parallel Scavenge收集器具有自适应调节策略,在这模式下年轻代大小、Eden和				   survivor的比例、晋升老年代的对象年龄等参数会被自动调整
   6.CMS回收器:低延迟
        并发收集器,第一次实现了让垃圾收集线程与用户线程同时工作,尽可能缩短垃圾收集时用户线程的停顿时间。停顿时间也短就越适合与		用户交互的程序,采用了标记-清除算法并且也会"stop the world"
        与Serial 和ParNew配合使用
        jdk14后被删除
        执行过程:初始标记->并发标记->重新标记->并发清理->重置线程
          初始标记(Initial-Mark):所有线程都会"stop the world"只标记出GC Roots能直接关联到的对象,由于直接关联对象小,速								度非常快
          并发标记(Concurrent Mark):从GC Roots的直接关联对象开始遍历整个对象图过程,这个过程耗时较长但是不需要停顿用户线程
           							可以与垃圾收集线程一起并发运行
           重新标记(Remark):为了并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间							通常会比初始标记阶段长一些,但也远比并发标记阶段时间短[stw阶段]
                           【这个阶段其实就是:并发标记阶段在遍历GC Roots时用户也在执行,若此时遍历过一个对象发现没有引用把							  它当做垃圾,但由于用户线程并发执行,这期间可能导致遍历过的这个对象又被其他对象重新引用,所以才需要							  重新标记再遍历一遍看看有没有标记错的否则会导致被重新引用的对象被误清理掉】
           并发清除(Concurrent-sweep):此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间,也可以与用户线程同时并发
           
          CMS收集器不能像其他收集器那样等到老年代几乎满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应           用程序在CMS工作过程中依然有足够的空间支持应用程序运行,要是CMS运行期间预留的内存无法满足程序需要,就会出现一次				“Concurrent Mode Failure”失败,这时虚拟机就会启动后备方案:临时启用Serial Old 收集器来重新进行老年代的垃圾收集,		   这样停顿时间就长了
          
         采用标记清除算法会产生一些碎片,无法使用指针碰撞,只能选择空闲列表
         为什么不能使用标记压缩算法呢?
              因为是并发的在清除垃圾时用户线程也在执行如果用标记压缩算法会移动对象改变对象地址,就会导致用户线程找不到对象了
         
         优点:
           并发收集
           低延迟
         缺点:
           1.会产生内存碎片:导致并发清除后,用户线程可用的空间不足,无法分配对象,不得不提前触发Full GC
           2.CMS收集器对cpu资源非常敏感:在并发阶段,他虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总			吞吐量会降低
           3.CMS收集器无法处理浮动垃圾:并发标记阶段产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的			垃圾对象没有被及时回收,从而只能在下一次GC时释放这些之前未被回收的内存空间【就是之前在GC Root上关联的然后再并发标			记时断开了关联成为了垃圾,这些垃圾没办法回收】
         
          设置参数:
            -XX:+UseConcMarkSweepGC:使用cms收集器,同时年轻代会自动将-XX:+UseParNewGC打开
            -XX:CMSInitiatingOccupanyFraxtion:设置堆内存使用率阈值 jdk5及以前默认68% jdk6以上默认92%,如果内存增长缓慢												可以设置大一点
            -XX:+UseCMSCompactAtFullCollection:用于指定在执行完Full GC后对内存空间进行压缩整理,以此避免内存碎片产生
            -XX:CMSFullGCBeforeCompaction:设置执行多少次Full GC 后对内存空间进行压缩整理
            -XX:ParallelCMSThreads:设置CMS线程数量,默认(ParallelThread+3)/4 ParallelThreads是年轻代并行收集的线程									 数
            
   7.G1回收器:区域化分代式
         官方目标:在延迟可控情况下获得尽可能高的吞吐量
         为什么叫Garbage First(G1)?
            因为G1是一个并行回收器,把堆分割为很多不相关的区域(Region)(物理上不连续)。使用不同的Region来表示Eden、幸存者0			  区、幸存者1区,老年代等。
            G1 Gc有机计划地避免在整个Java堆中进行全区的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大			 小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。
            这种方式的侧重点在于回收垃圾最大量的区间(Region),所以叫:垃圾优先(Garbage First)
            是一款面向服务器应用的垃圾收集器,主要针对配备多核cpu及大容量内存的机器,以及高概率满足GC停顿时间的同时,兼顾吞吐量
            jdk1.7正式启用,jdk9默认
            -XX:+UseG1GC 启用
            
            G1回收器的优势:
                并行与并发:
                    并行性:在G1回收期间,可以有多个GC线程同时工作,此时用户线程STW(只要是并行就会STW)
                    并发性:G1拥有与应用程序交替执行的能力,部分工作可一和应用程序同时执行,因此,一般来说不会在整个回收阶段发							 生完全阻塞应用程序的情况
                分代收集:
                    也分年轻代和老年代,但从堆的结构上看,他不要求整个Eden区、年轻代或者老年代都是连续的,也不再坚持固定大小					  和固定数量。
                    将堆空间分为若干区域,这些区域中包含了逻辑上的年轻代和老年代。
                    和之前各类收集器不同,同时兼顾年轻代和老年代的回收
                空间整合:
                    CMS:“标记清除”算法、内存碎片、若干次GC后进行一次碎片整理
                    G1内存的回收是以Region为单位的,Region之间是赋值算法,但整体上实际可看作是标记-压缩算法,两种算法都可以					   避免内存碎片,有利于长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC,尤其是当java					  堆非常大时,G1的优势更明显
                可预测的停顿时间模型(即:软实时soft real-time):
                    G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在					  垃圾收集上的时间不得超过N毫秒。
                    G1可以只选取部分区域进行回收。
                    G1跟踪各个Region里面垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先					 列表,每次根据允许的收集时间,优先回收价值最大的Region。保证了有限时间内获取高收集效率。
                    停顿时间上G1可能比不上CMS最好的时候,但最差的时候比CMS要好
              
              缺点:用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用还是程序运行时的额外执行负载都要比CMS高;在小内存应				   用上CMS的表现大概率会优于G1 平衡点在6-8GB。
         
         G1回收器的参数设置:
             -XX:+UseG1GC  jdk9之前指定G1
             -XX:G1HeapRegionSize   设置每个Region的大小,值是2的幂,范围是1MB到32M之间,目标是根据最小的java堆大小划分									  出2048个区域。默认是堆内存的1/2000
             -XX:MaxGCPuseMillis:   设置期望达到的最大GC停顿时间指标,默认200ms
             -XX:ParallelGCThread   设置STW时GC线程数的值,最多为8
             -XX:ConcGCThreads      设置并发标记的线程数,将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4
             -XX:InitiatingHeapOccupancyPerecent 设置触发并发GC周期的java堆占用率的阈值。超过此值触发GC,默认45
         G1的设计原则就是简化JVM性能调优,开发人员只需要简单的三步即可完成调优:
             1.开启G1收集器
             2.设置堆最大内存
             3.设置最大停顿时间
         G1回收器应用场景:面向服务器端,针对大内存多处理器。当我们需要低延迟时考虑使用
         HotSpot垃圾收集器里,除了G1以外,其他的垃圾收集器使用内置的JVM垃圾收集线程操作,而G1可以采用应用线程承担后台运行的GC			工作,即当JVM的GC线程处理速度慢时,系统会调用应用程序线程帮助加速垃圾回收过程。
        
        分区:化整为零
           一个region只属于Eden、survivor、old当中的一个角色,回收了之后加入到空闲列表中下一次有可能分配为别的区;G1还增加			 了新的区,叫做Humongous内存区域,H块。主要用于存储大对象,如果超过1.5个region就当到H区。
           设置H的原因:对于堆中的大对象,默认直接会被分配到老年代,如果它是一个短期存在的大对象,就会对垃圾收集器产生负面影响,			 如果一个H区放不下,那么G1会寻找连续的H区来存储,为了能找到连续的H区,有时候不得不启动Full GC。G1的大多数行为都把H			区作为老年代的一部分来看待。
           
        G1 GC的垃圾回收过程包含三个环节:
           年轻代GC:(Eden主动回收 survivor被动回收)
               当年轻代的Eden区用尽时开始回收,G1年轻代收集阶段是一个并行(多个垃圾回收线程同时)的独占式收集器,stw停止所有用				   户线程,然后从年轻代区间移动存活的对象到survivor区或者老年区间(大对象存不下会放到老年代 8:1:1),也有可能两个				区间都会涉及
           老年代并发标记过程:
               当堆内存达到一定值(默认45%),开始老年代并发标记过程
           混合回收:
               标记完后开始混合回收,G1 GC从老年代移动存活对象到空闲区,这些空闲区就成了老年代的一部分;老年代回收不需要整个老				年代被回收,一次只需要扫描回收一小部分老年代Region(价值大小),同时这个老年代和年轻代一起被回收。 
           如果需要单线程、独占式、高强度的Full GC还是继续存在的,它对GC的评估失败提供了一种保护机制,即强力回收

       Remembered Set(记忆集):
           Region之间的引用,老年代引用了新生代 YGC时就会出问题了扫描的太多了导致暂停时间太长
           R Set可以避免全局扫描
           每个Region都有一个对应的Remembered Set
           每次Reference类型数据写操作时,都会产生一个Write Barrier暂停中断操作,然后检查是否在同一个Region,如果不同通过			CardTable把相关引用信息记录到对象的所在Region的R Set中
           当进行垃圾回收时,在GC根结点的枚举范围加入R Set;就可以保证不进行全局扫描。
           
      G1回收过程一:年轻代回收
          JVM启动,G1先准备好Eden区,程序运行过程中不断创建对象到Eden区,当Eden空间耗尽,G1启动一次年轻代垃圾回收,YGC时会		   stw,G1创建回收集,回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集包含Eden和Survivor区所有的内存分段
          
          
   8.垃圾回收器总结
         
   9.GC日志分析
        内存分配与垃圾回收的参数列表
         -XX:+PrintGC    			输出GC日志 类似:-verbose:gc
         -XX:+PrintGCDetails  		输出GC详细日志
         -XX:+PrintGCTimeStamps     输出GC时间戳(以基准时间形式)
         -XX:+PrintGCDateStamps     输出GC时间戳(以日期的形式,如2020-04-06-......)
         -XX:+PrintHeapAtGC         在进行GC的前后打印出堆的信息
         -Xloggc:../logs/gc.log     日志文件的输出路径
         
        Serial  新生代显示   DefNew
        ParNew             ParNew
        Parallel Scavenge  PSYoungGen
        G1                 garbage-first heap
        
        PSYoungGen:5986k       ->    696K(8704K)       5986k    ->704k(9216k)
                   新生代之前占用大小    之后大小(总大小)    堆之前大小   之后大小(总大小)
   10.垃圾回收器的新发展
         G1不断发展 jdk10以后Full GC可以并行了
         
         jdk11新特性
            Epsilon:运用于内存分配完直接结束的  无操作的
            Openjdk 12:Shenandoah GC    主打低延迟
            ZGC:可伸缩的低延迟垃圾回收器,处于试验阶段 主打低延迟
      
      ZGC:
        ZGC与Shenandoah目标高度相似,在尽可能对吞吐量影响不大的前提下,实现任意堆内存大小都可以把垃圾收集的停顿时间限制在10ms		   以内的低延迟;ZGC收集器是一款基于Region内存布局的,暂时不设分代,使用了读屏障、染色指针和内存多重映射等技术来实现可并发		   标记-压缩算法的以低延迟为首要目标的一款垃圾收集器
        ZGC工作过程分为4个阶段:并发标记-并发预备重分配-并发重分配-并发重映射等
        ZGC几乎所有地方都并发,除了初始标记的是STW的,所以停顿时间几乎就耗费在初始标记上,这部分的实际时间是非常短的
        -XX:+UnlockExperimentalVMOptions -XX:+UseZGC

*字节码与类的加载篇

*性能监控与调优篇

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值