JVM

JVM

类加载

  • 加载 将class文件加载到内存, 将数据结构加载到方法区中
  • 连接
    • 检验 检验类是否符合JVM规范
    • 准备 给static变量赋初始值
    • 解析 将常量池中的符号引用替换为直接引用
  • 初始化 静态代码块 构造器
  • 使用
  • 卸载

类加载器

  • BootstrapClassLoader c++编写 加载JRE/lib/rt.jar
  • ExtesionClassLoader 加载/JRE/lib/ext/*.jar
  • AppClassLoader 加载classpath/*.jar
  • CustomClassLoader tomcat jboss实现

加载过程中会先检查类是否被已加载,检查顺序是自底向上,从 Custom ClassLoader 到 BootStrap ClassLoader 逐层检查,只要某个 Classloader 已加载就视为已加载此类,保证此类只所有 ClassLoader 加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

运行时数据区

线程共享: 方法区 堆

线程不共享: 程序计数器 本地方法栈 虚拟机栈

  • 程序计数器

    • 指向当前线程执行的字节码文件行号
    • 唯一一个不会发生OOM的区域
  • 本地方法栈

    • 虚拟机栈就是用来存储:局部变量、操作栈、动态链表、方法出口这些东西;

      这些东西有个特点:都是线程私有的,所以虚拟机栈是线程私有的

    • StackOverflowError 压栈深度超过虚拟机允许的最大深度

    • OutOfMemoryError 虚拟机栈可以进行动态扩展,但随着扩展会不断的申请内存,当无法申请足够内存的时候就会报错

  • 虚拟机栈 -Xms40m -Xmx40m -XX:+PrintGCDetails

    • 为虚拟机使用到的 e Native 方法服务(比如 C 语言写的程序和 C++写的程序)
  • 方法区 永久代 非堆

    ​ 用来存储:已经被虚拟机加载的类信息、常量、静态变量等

    • 运行时常量池 用来存放程序编译期生成的各种字面量和符号引用
    • 新生代

      • Eden
      • Survivor

      -XX:NewSize 和-XX:MaxNewSize
      用于设置年轻代的大小,建议设为整个堆大小的 1/3 或者 1/4,两个值设为一样大。
      -XX:SurvivorRatio 用来控制 Eden 和 survivor Ratio 比例
      -xms :JVM 初始分配的内存由-xms 决定。系统默认给-xms 的大小是服务器物理内存的 1/64
      -xmx :JVM 最大分配的内存由-xmx 决定。系统默认给-xmx 的大小是服务器物理内存的 1/4

      一般都是把 JVM 的-xms 和-xmx 都设置成相等。避免每次 GC 够调整堆的大小,造成内存抖动。

    • 老年代

      • Old

      那么老年代的堆大小= -Xmx - Xmn
      当然,老年代的大小也可以通过:-XX:OldSize 来指定
      或者通过调整新生代和老年代的比例:-XX:NewRatio (比如这个值是 4,那么就是新生代占对内存的 1/5,老年代占用对内存的4/5)

GC

判断对象是否可回收
  • 引用计数器
  • 可达性分析算法
    • GC Roots树 引用链
垃圾回收算法
  • 年轻代
    • Eden
      • 初始阶段,新创建的对象会分配给 Eden 区(如果创建的对象非常大,那么对象会直接进去老年代)
      • 随着对象往 Eden 区进行填充,Eden 区满了的时候,就会触 young GC------ Minor GC
      • 在这阶段会使用垃圾回收的算法— 复制算法(复制算法会将存活的对象复制到 from suvisor 区域,然后已经无用的对象被回收)
    • Suvisor
      • 这样每次对象还存活,两个 suvisor 区域中的对象,只要经过复制过程,年龄就会加 1
      • 当对象的年龄不断的增长,达到一个默认值“15”( -XX:MaxTenuringThreshold)的时候,对象就会进入老年代了
      • 在发生 minor GC 的时候。JVM 都会去检查每次晋升到老年代的对象大小是否已经大于老年代剩余的空间,如果大于那么就会出现 FULL GC
  • 老年代
    • old
  • 永久代
    • permanent

复制算法

复制算法将可用的内存容量划分成大小相等的两块,每次只使用其中的一块;当这一块内存用完,就会将还存活的对象放在另一块区域上,然后再把已使用的内存空间一次清理掉,这样每次清理垃圾的时候都是对整个半区进行垃圾回收,内存分配的时候也不用考虑内存碎片的问题了,这样对于内存的回收就更加简单高效

需要提前预留一半的内存区域用来存放存活的对象(经过垃圾收集后还存活的对象),这样导致可用的对象区域减小一半,总体的 GC 更加频繁了
如果出现存活对象数量比较多的时候,需要复制较多的对象,成本上升,效率降低
如果99%的对象都是存活的(老年代),那么老年代是无法使用这种算法的

标记清除算法

算法的分为两个阶段:
1:标记阶段
2:清除阶段
首先标记所有需要回收的对象,在标记完成之后统一回收所有被标记的对象

标记-清除算法有两个不足之处:
一个是效率问题,标记和清除两个过程的效率都不高
空间问题:标记清除后会产生大量不连续的内存碎片(空间碎片太多可能会导致后续的程序运行过程中需要分配较大的对象时,无法找到足够的连续内存,这样就导致不得不提前触发垃圾收集动作)

标记- - 整理算法

标记-整理算法和标记-清除算法很相似,但是标记整理算法并不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
不用担心内存碎片问题

分代收集算法

所谓的分代收集,就是根据新生代和老年代的特点,使用不同的垃圾回收算法

新生代在垃圾收集的时候都会有大批量的对象死去,只有少量的对象存活,那么就是用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;

老年代存活对象比例比较高,只有少量对象死去,所以使用标记-整理或者标记-清除算法就可以清理掉少量的垃圾对象

垃圾收集器

  • serial 单线程收集器

    • 它在垃圾收集的时候,必须暂停其他工作线程,直到垃圾收集完毕
    • -XX:+UseSerialGC
  • ParNew 多线程收集器

    • serial的多线程版
    • -XX:+UseParNewGC
  • Parallel Secavenge 收集器

    • -XX:+UseParallelGC
    Parallel Scavenge 收集器是一个新生代的收集器,并且使用复制算法,而且是一个并行的多线程收集器
    Parallel Scavenge 收集器的关注的点和其他收集器是不一样的;
    其他收集器是尽量缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge 收集器的目标是达到一个可控制的吞吐量
    (Throughput);
    吞吐量= = 运行用户代码时间 /( 运行用户代码时间+ + 垃圾收集时间) )
    (虚拟机总共运行 100 分钟,垃圾收集时间为 1 分钟,那么吞吐量就是 99%)
    注意:
    停顿时间越短越就越适合需要与用户交互的程序,良好的响应速度能够提升用户体验;
    而高吞吐量则可以高效的利用 U CPU 时间,尽快的完成程序的计算任务,主要是和在后台运行不需要太多交互的任务
    Parallel Scavenge 收集器提供了两个参数用于精确控制吞吐量,
    分别是控制:
    最大垃圾收集停顿时间-XX:MaxGCPauseMillis
    吞吐量大小-XX:GCTimeRatio
    与 Parallel Scavenge 收集器有关的还有一个参数:-XX:+UseAdaptiveSizePolicy(有了这个参数之后,就不要手工指定年轻代、
    Eden、Suvisor 区的比例,晋升老年代的对象年龄等,因为虚拟机会根据系统运行情况进行自适应调节
    
  • Slerial Old 收集器

    • Serial 的老年代版本,仍然使用单线程收集,不同的是使用标记-整理算法
    • -XX:+UseSerialGC
  • Parallel O Od ld 收集器

    • parallel Scavenge 的多线程版本,使用的是标记-整理算法
    • -XX:+UseParallelOldGC
  • CMS (concurrent mark sweep )收集器

    CMS 收集器是以获取最短垃圾收集停顿时间为目标的收集器;
    目前很大一部分的 java 应用几种在互联网的 B/S 系统服务器上,这类应用尤其注重服务器的响应速度,希望系统停顿时间最短,
    给用户带来良好的体验;
    CMS 收集器就非常符合这类应用的需求;
    CMS 收集器使用的算法是标记-清除算法实现的;
    整个过程分 4 个步骤:
    1、 初始标记
    2、 并发标记
    3、 重新标记
    4、 并发清除
    其中初始标记和重新标记都需要 stopTheWorld
    初始标记仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快;
    并发标记阶段就是进行 GC Roots Tracing(根搜索算法)的过程;
    重新标记阶段是为了修改并发标记期间因用户程序继续运行而导致产生变动的那一部分对象的标记记录, 这个阶段停顿的时长要稍微比初始标记停顿的时间稍微长一点,但是远比并发标记的时间小很多;
    整个过程耗时最长的是 并发标记和并发清除,但是他们都是可以与用户线程一起工作的,所以 CMS 收集器是停顿时间最短的垃圾收集器
    CMS 垃圾收集器缺点:
    1:CMS 收集器对 CPU 资源特别的敏感;
    CMS 在并发阶段,虽然不会导致用户线程停顿,但是会因为占用一部分线程而导致应用程序变慢,总吞吐量变低;
    2:使用标记-清除算法,会产生内存碎片(配合-XX:+UseCMSCompactAtFullCollection 使用)
    使用方式:-XX:+UseConcMarkSweepGC
    
  • G1收集器

    • G1 收集器是最新的垃圾收集器,能效最好的收集器
    G1 是将整个堆空间划分成大小想等的小块(每一块成为 region),每一块的内存是连续的。和分代收集算法一样,G1 中每个块也会充当 Eden、Suvisor、Old 三种角色,但是他们不是固定的,这样使得内存的使用更加灵活
    在 G1 中有个特殊的区域叫做 : Humongous 区域。如果一个对象的空间超过了分区容量的 50%以上,G1 就认为这是一个巨型对象;默认巨型对象是需要存储在老年代中的,但是如果这个巨型对象只是短期存在,那么会对垃圾收集器造成负面影响;为了解决这个问题,G1 专门划分了一个区域(Humongous)用来存储大对象
    
    Young GC 主要是对 Eden 区进行 GC,它在 Eden 空间耗尽时会被触发。在这种情况下,Eden 空间的数据移动到 Survivor 空间中,如果 Survivor 空间不够,Eden 空间的部分数据会直接晋升到老年代空间。Survivor 区的数据移动到新的 Survivor 区中,也有部分数据晋升到老年代空间中。最终 Eden 空间的数据为空,GC 停止工作,应用线程继续执行
    
    Mixed GC  当越来越多的对象晋升到老年代 old region 时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即 mixed gc,该算法并不是一个 old gc ,除了回收整个 young region ,还会回收一部分的 old region ,这里需要注意:是一部分老年代,而不是全部老年代。
    
    如果对象内存分配速度过快,mixed gc 来不及回收,导致老年代被填满,就会触发一次 full gc,G1 的 full gc 算法就是单线程执行的 serial old gc,会导致异常长时间的暂停时间,需要进行不断的调优,尽可能的避免 full gc
    
    java -Xms100m -Xmx100m -XX:MetaspaceSize=20m -XX:MaxMetaspaceSize=20m 
    -XX:+UseG1GC -XX:NewRatio=1
    -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:ParallelGCThreads=2 
    -XX:G1HeapRegionSize=10m
    -XX:+PrintHeapAtGC -XX:+PrintGCTimeStamps 
    -Xloggc:/root/JVM/gc.log 
    -jar JVM-1.0-SNAPSHOT.jar
    

案例

java -Xms100m -Xmx100m -Xmn50m -XX:MetaspaceSize=20m -XX:MaxMetaspaceSize=20m 
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC -XX:+UseCMSCompactAtFullCollection 
-XX:CMSInitiatingOccupancyFraction=85 -Xverify:none
-XX:+DisableExplicitGC -XX:+CMSParallelRemarkEnabled -XX:+PrintHeapAtGC 
-XX:+PrintGCTimeStamps
-Xloggc:/root/JVM/gc.log -jar JVM-1.0-SNAPSHOT.jar

-Xms:JVM 初始最小堆内存
-Xmx:JVM 允许最大堆内存
-XX:PermSize JVM 初始非堆内存
-XX:MaxPermSize JVM 允许最大的非堆内存
-XX:+UseConcMarkSweepGC:年老代激活 CMS 收集器(标记算法),可以尽量减少 fullGC
-XX:+UseParNewGC :设置年轻代为多线程并行收集
-XX:+UseCMSCompactAtFullCollection:在 FULL GC 的时候,对年老代的压缩(CMS 的时候,会导致内存碎片,使内存空间不连
续,可能会影响性能,但是可以消除碎片)
-XX:CMSInitiatingOccupancyFraction=85:当年老代空间被占用 85%的时候触发 CMS 垃圾收集

你可能感兴趣的:(Java)