JVM组成部分

1. 类加载器(ClassLoader)

  • 加载器主要种类:

    • 启动类加载器

      • <JAVA_HOME>/lib 或者被 -Xbootclasspath 参数所指定的路径

      • c++ 实现,是 JVM 的一部分

    • 扩展类加载器

      • <JAVA_HOME>/lib/ext 或者 java.ext.dirs 系统变量所指定的路径

      • 由 sun.misc.Launcher$ExtClassLoader 实现

    • 应用类加载器 (系统类加载器)

      • 用户类路径(java -classpath或-Djava.class.path变量所指的目录)

      • 由 sun.misc.Launcher$AppClassLoader 实现

  • 类加载机制:

    • 全盘负责:类的本身及依赖由指定加载器加载

    • 双亲委派:优先由父加载器加载

      • 好处:防止核心API库被随意篡改

      • 实现:java.lang.ClassLoader 中的 loadClass() 方法

    • 缓存机制:加载过的类都会缓存在内存中

2. 运行时数据区(Runtime Data Area)

  • 线程独享:

    • 程序计数器:
      字节码的行号指示器

    • (JVM)栈: ——-虚拟机执行Java方法(也就是字节码)服务

      帧栈(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息
      StackOverflowError: 线程请求的栈的深度大于虚拟机所允许的深度
      OutOfMemoryError: 扩展时无法申请到足够的内存

    • 本地方法栈: ——–为虚拟机使用到的 Native 方法服务

      类似JVM栈
      Sun HotSpot 虚拟机直接把Java虚拟机栈和本地方法栈合二为一

  • 线程共享:

    • 堆:存放对象实例

    • 方法区:含运行时常量池,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

3. 执行引擎(Execution Engine)

4. 本地库接口(Native Interface)


JAVA垃圾回收策略

1. 引用计数法

  • 说明:堆中每个对象实例均有引用计数
  • 优点:执行快,交织于程序运行中
  • 缺点:无法检测循环引用

2. 可达性分析算法

  • 可作为 GC Roots 的对象包括下面几种:
    • 虚拟机栈中引用的对象(栈帧中的本地变量表)
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中 JNI(Native方法)引用的对象
  • 要真正宣告一个对象死亡,至少要经历两次标记过程:
    • 没有与 GC Roots 相连接的引用链
    • 在 finalize() 方法中没有重新与引用链建立关联关系

JAVA内存泄漏

概念:对象可达却无用

原因:长生命周期对象持有短生命周期对象

情况:

  1. 静态集合类引起的内存泄漏
  2. 集合对象属性被修改,调用 remove() 不起作用
  3. 监听器:释放对象没有删除监听器
  4. 各种连接没有关闭,如 dataSource.getConnection(), io, socket
  5. 内部类
  6. 单例模式:若持有外部引用则此外部引用无法被回收

避免方法:

  1. 不轻易用 static,减少生命周期
  2. 及时关闭资源
  3. 不用的对象手动置 null

JVM 内存模型

HotSpot 中的 gc 内存模型,

JVM 内存模型

详见运行时数据区


常用的垃圾收集算法

1. 标记-清除算法 (Mark-Sweep)

  • 概念:从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收

  • 优点:只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效

  • 缺点:会造成内存碎片

2. 复制算法 (Copying)

  • 概念:把堆分成一个对象面和多个空闲面,对象满后将每个活动对象复制到空闲面

  • 优点:克服句柄的开销和解决内存碎片的问题

  • 缺点:浪费内存空间,对象存活率高时效率低

3. 标记-整理算法 (Mark-compact)

  • 概念:标记-清除的基础上,将所有的存活对象往左端空闲空间移动,并更新对应的指针

  • 优点:解决了内存碎片的问题

  • 缺点:标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高

4. 分代收集算法(目前大部分 JVM 的垃圾收集器采用的算法)

  • 堆区 Heap

    • 老年代(2) Old Generation: 主要以 Mark-Compact 为主,满后发生 Full GC (Major GC)

    • 年轻代(1) Young Generation: 主要以 Copying 为主,经常会有 Minor GC

      • eden 区(8):
        大部分对象生成区,GC 时将活对象 copy 到 S0 区,同时清空 eden 区

      • survivor0 区(1):
        满后将 eden 与 S0 区活对象 copy 到S1 区,同时清空 eden 与 S0 区,最后 S0, S1 区互换

      • survivor1 区(1):
        满后将活对象存入老年代

  • 永久代 Permanet Generation


常用的垃圾收集器

1. Serial 收集器:

  • 复制算法

  • 年轻代,单线程,client 级别默认 GC 方式,-XX:+UseSerialGC 指定

2. Serial Old 收集器:(Serial 的老年代版本)

  • 标记-整理算法

  • 老年代,单线程

3. ParNew 收集器:(Serial 的多线程版本)

  • 复制算法

  • 年轻代,多线程,多核 CPU 下比 Serial 更优表现

4. Parallel Scavenge 收集器:

  • 复制算法

  • 年轻代,多线程,高吞吐,高效利用 CPU,Server 级别默认 GC 方式,-XX:+UseParallelGC 指定收集器,-XX:ParallelGCThreads=n 指定线程

5. Parallel Old 收集器:(Parallel Scavenge 的老年代版本)

  • 复制算法

  • 老年代,多线程,高吞吐

6. CMS(Concurrent Mark Sweep) 收集器:

  • 标记清除算法
    1. 初始标记
    2. 并发标记
    3. 重新标记
    4. 并发清除
  • 高并发,低停顿,追求最短 GC 停顿时间,牺牲吞吐量,CPU 占用高, -XX:+UseConcMarkSweepGC 指定
    产生内存碎片至内存不能满足程序运行要求时出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 收集器,性能降低

7. G1(Garbage First) 收集器:(取代 CMS 的使命)

  • 混合收集,启发式算法

  • 无分代,有分区,优先 GC 收益高的分区,-XX:+UseG1GC 指定收集器,-XX:G1HeapRegionSize=n 指定分区大小
    每个分区分成若干 512 Byte 的 Card,标识堆内存最小可用粒度所有分区的卡片将会记录在全局卡片表(Global Card Table)中,内存回收是对卡片进行处理


导致 Full GC 的原因

1. System.gc()

此操作只建议 JVM 执行 Full GC,JVM不一定执行

2. 老年代空间不足

不要创建过大的对象或数组 -Xmn 参数调大新生代大小,让对象尽量在新生代就被回收而不进入老年代
-XX:MaxTenuringThredshold 参数调大进入老年代的年龄,让对象在新生代多存活一段时间

3. 空间分配担保失败

使用复制算法的 Minor GC 需要老年代内存空间作担保,担保失败会执行一次 Full GC

4. JDK1.7 以前的永久代空间不足

在 JDK1.7 及以前,HotSpot 的方法区在永久代内实现,永久代存放一些 class 信息,常量,静态变量等数据
系统加载类,反射类,调用方法过多时永久代可能被占满,未配置为 CMS GC 的情况下会执行 Full GC,若 Full GC 仍回收不了则会发生 OOM
可增大永久代空间或者使用 CMS GC

5. Concurrent Mode Failure

执行 CMS GC 的过程中老年代空间不足(有可能浮动垃圾过多所致)会报 Concurrent Mode Failure,从而触发 Full GC


内存分配若干细则

1. 对象优先在 Eden 区分配,若 Minor GC 后仍然存活则进入 S 区

2. -XX:PretenureSizeThreshold,大于此值的对象直接在老年代分配

3. -XX:MaxTenuringThreshold, 大于此值的对象从 S 区进入老年代

4. 动态对象年龄判定:并非一定要超过阈值,若 S 区中 年龄为 n 的对象占 S 区空间超过一半,年龄大于等于 n 的对象即可进入老年代

5. 空间分配担保:

Minor GC 前,JVM 检测

if (老年代最大可用的连续空间 > 年轻代所有对象空间总和)
    Minor GC
else
    if (HandlePromotionFailure && 老年代最大可用的连续空间 > 历次晋升到老年代对象的平均大小)
        Minor GC
    else
        Full GC

JVM 监控工具

  1. jvisualvm: 虚拟机监视和故障处理平台

  2. jps: 查看当前 JAVA 进程

  3. jstat: 显示 JVM 运行数据

  4. jmap: 内存监控

  5. jhat: 分析 heapdump 文件

  6. jstack: 线程快照

  7. jinfo: JVM 配置信息


利用监控工具调优

1. 堆信息查看

  • 可查看堆空间大小分配(年轻代、年老代、持久代分配)
  • 提供即时的垃圾回收功能
  • 垃圾监控(长时间监控回收情况)
  • 查看堆内类、对象信息查看:数量、类型等
  • 对象引用情况查看
  • 线程监控

3. 热点分析

  • CPU 热点:检查系统哪些方法占用的大量 CPU 时间
  • 内存热点:检查哪些对象在系统中数量最大

4. 快照分析

5. 内存泄漏检查


JVM 部分参数设置

1. 堆设置

  • -Xms:初始堆大小

  • -Xmx:最大堆大小

  • -XX:NewSize=n:设置年轻代大小

  • -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代年老代和的 1/4

  • -XX:SurvivorRatio=n:年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。如:3,表示 Eden:Survivor=3:2,一个Survivor区占整个年轻代的 1/5

  • -XX:MaxPermSize=n:设置持久代大小

2. 收集器设置

  • -XX:+UseSerialGC:设置串行收集器

  • -XX:+UseParallelGC:设置并行收集器

  • -XX:+UseParalledlOldGC:设置并行年老代收集器

  • -XX:+UseConcMarkSweepGC:设置并发收集器

3. 垃圾回收统计信息

  • -XX:+PrintGC:开启打印 gc 信息

  • -XX:+PrintGCDetails:打印 gc 详细信息

  • -XX:+PrintGCTimeStamps

  • -Xloggc:filename

4. 并行收集器设置

  • -XX:ParallelGCThreads=n:设置并行收集器收集时使用的 CPU 数

  • -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间

  • -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比

5. 并发收集器设置

  • -XX:+CMSIncrementalMode:设置为增量模式。适用于单 CPU 情况

  • -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的 CPU 数。并行收集线程数


相关资料: JVM笔记(下)