JVM优化

作者: 云之彼端l | 来源:发表于2019-01-08 11:17 被阅读0次

1、JVM结构图

  • 1.1 类加载子系统与方法区:
    类加载子系统负责从文件系统或者网络中加载 Class 信息,加载的类信息存放于一块称为方法区的内存空间。
  • 1.2 Java 堆
    java 堆在虚拟机启动的时候建立,它是 java 程序最主要的内存工作区域。几乎所有的java 对象实例都存放在 java 堆中
  • 1.3 直接内存
    java 的 NIO 库允许 java 程序使用直接内存。直接内存是在 java 堆外的、直接向系统申请的内存空间。通常访问直接内存的速度会优于 java 堆。因此出于性能的考虑,读写频繁的场合可能会考虑使用直接内存。由于直接内存在 java 堆外,因此它的大小不会直接受限于 Xmx 指定的最大堆大小,但是系统内存是有限的, java 堆和直接内存的总和依然受限于操作系统能给出的最大内存。
  • 1.4 垃圾回收系统
    垃圾回收系统是 java 虚拟机的重要组成部分,垃圾回收器可以对方法区、 java 堆和直接内存进行回收。其中, java 堆是垃圾收集器的工作重点。和 C/C++不同, java 中所有的对象空间释放都是隐式的,也就是说, java 中没有类似 free()或者 delete()这样的函数释放指定的内存区域。对于不再使用的垃圾对象,垃圾回收系统会在后台默默工作,默默查找、标识并释放垃圾对象,完成包括 java 堆、方法区和直接内存中的全自动化管理。
  • 1.5 Java 栈
    每一个 java 虚拟机线程都有一个私有的 java 栈,一个线程的 java 栈在线程创建的时候被创建, java 栈中保存着帧信息, java 栈中保存着局部变量、方法参数,同时和 java 方法的调用、返回密切相关
  • 1.6 本地方法栈
    本地方法栈和 java 栈非常类似,最大的不同在于 java 栈用于方法的调用,而本地方法栈则用于本地方法的调用。本地方法(Native Method)是由其他语言(如C、C++ 或其他汇编语言)编写,编译成和处理器相关的代码。通过本地方法,java程序可以直接访问底层操作系统的资源,但是这么用的话,程序就变成了平台相关了
  • 1.7 PC 寄存器
    PC( Program Counter) 寄存器也是每一个线程私有的空间, java 虚拟机会为每一个 java线程创建 PC 寄存器。在任意时刻,一个 java 线程总是在执行一个方法,这个正在被执行的方法称为当前方法。如果当前方法不是本地方法, PC 寄存器就会指向当前正在被执行的指令。 如果当前方法是本地方法,那么 PC 寄存器的值就是 undefined
  • 1.8 执行引擎
    执行引擎是 java 虚拟机的最核心组件之一,它负责执行虚拟机的字节码,现代虚拟机为了提高执行效率,会使用即时编译(just in time)技术将方法编译成机器码后再执行。Java HotSpot Client VM(-client),为在客户端环境中减少启动时间而优化的执行引擎; 本地应用开发使用。(如: eclipse)
    Java HotSpot Server VM(-server),为在服务器环境中最大化程序执行速度而设计的执行引擎。 应用在服务端程序。(如: tomcat)
    Java HotSpot Client 模式和 Server 模式的区别:
    当虚拟机运行在-client 模式的时候,使用的是一个代号为 C1 的轻量级编译器, 而-server模式启动的虚拟机采用相对重量级,代号为 C2 的编译器. C2 比 C1 编译器编译的相对彻底,服务起来之后,性能更高
    JDK 安装目录/jre/lib/( x86、 i386、 amd32、 amd64) /jvm.cfg
    文件中的内容, -server 和-client 哪一个配置在上,执行引擎就是哪一个。 如果是 JDK1.5版本且是 64 位系统应用时, -client 无效。
    --64 位系统内容
    -server KNOWN
    -client IGNORE
    --32 位系统内容
    -server KNOWN
    -client KNOWN
    注意:在部分 JDK1.6 版本和后续的 JDK 版本(64 位系统)中, -client 参数已经不起作用了, Server 模式成为唯一

2、堆结构及对象分代

  • 2.1 什么是分代,分代的必要性是什么
    Java 虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般分为年轻代、年老代和永久代(对 HotSpot 虚拟机而言),这就是 JVM 的内存分代策略。
    堆内存是虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的一块区域,我们程序所有的对象实例都存放在堆内存中。给堆内存分代是为了提高对象内存分配和垃圾回收的效率
  • 2.2 分代的划分
    Java 虚拟机将堆内存划分为年轻代(新创建的对象)、年老代(经过多次回收仍然存活下来的对象)和永久代(静态变量、类信息、常量等)
    HotSpot 将年轻代划分为三块,一块较大的 Eden(伊甸)空间和两块较小的 Survivor(幸存者) 空间,默认比例为 8: 1: 1。划分的目的是因为 HotSpot 采用复制算法来回收年轻代,设置这个比例是为了充分利用内存空间,减少浪费。如下内存简图:

3、垃圾回收算法及分代垃圾收集器

3.1 垃圾收集器的分类
  • 3.1.1 次收集器
    Scavenge GC, 指发生在年轻代的 GC,因为年轻代的 Java 对象大多都是朝生夕死,所以Scavenge GC 非常频繁,一般回收速度也比较快。
  • 3.1.2 全收集器
    Full GC, 指发生在年老代的 GC,Full GC 的速度一般会比 Scavenge GC慢10倍以上。当年老代内存不足或者显式调用 System.gc()方法时,会触发Full GC。
  • 3.1.3 垃圾回收器的常规匹配


3.2 常见垃圾回收算法
  • 3.2.1 引用计数( Reference Counting)
    比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为 0 的对象。此算法最致命的是无法处理循环引用的问题。
  • 3.2.2 复制( Copying)
    此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。此算法每次只处理正在使用中的对象,因此复制成本比较小, 同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。


  • 3.2.3 标记-清除( Mark-Sweep)
    此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。


  • 3.2.4 标记-整理( Mark-Compact)
    此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从
    根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。


3.3 分代垃圾收集器
  • 3.3.1 串行收集器( Serial)
    Serial 收集器是 Hotspot 运行在Client 模式下的默认新生代收集器, 它的特点是: 只用一个 CPU( 计算核心) /一条收集线程去完成 GC 工作, 且在进行垃圾收集时必须暂停其他所有的工作线程(“ Stop The World” -后面简称 STW)。可以使用-XX:+UseSerialGC 打开。虽然是单线程收集, 但它却简单而高效, 在 VM 管理内存不大的情况下(收集几十 M~一两百 M 的新生代), 停顿时间完全可以控制在几十毫秒~一百多毫秒内。

  • 3.3.2 并行收集器( ParNew)
    ParNew 收集器其实是前面 Serial 的多线程版本, 除使用多条线程进行 GC 外, 包括 Serial可用的所有控制参数、收集算法、 STW、对象分配规则、回收策略等都与 Serial 完全一样(也是 VM 启用 CMS 收集器-XX: +UseConcMarkSweepGC 的默认新生代收集器)。ParNew 收集线程数与 CPU 的数量相同, 因此在 CPU 数量过大的环境中, 可用-XX:ParallelGCThreads=<N>参数控制 GC 线程数

  • 3.3.3 Parallel Scavenge 收集器
    与 ParNew 类似, Parallel Scavenge 也是使用复制算法, 也是并行多线程收集器. 但与其他收集器关注尽可能缩短垃圾收集时间不同, Parallel Scavenge 更关注系统吞吐量:
    系统吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
    停顿时间越短就越适用于用户交互的程序-良好的响应速度能提升用户的体验;而高吞
    吐量则适用于后台运算而不需要太多交互的任务-可以最高效率地利用 CPU时间,尽快地完成程序的运算任务。参数如下:
    -XX:MaxGCPauseMillis :(毫秒数) 收集器将尽力保证内存回收花费的时间不超过
    设定值, 但如果太小将会导致 GC 的频率增加。
    -XX:GCTimeRatio: (整数:0 < GCTimeRatio < 100) 是垃圾收集时间占总时间的比率。
    -XX:+UseAdaptiveSizePolicy:启用 GC 自适应的调节策略: 不再需要手工指定-Xmn、-XX:SurvivorRatio、 -XX:PretenureSizeThreshold 等细节参数, VM会根据当前系统的运行情况收集性能监控信息, 动态调整这些参数以提供最合适的停顿时间或最大的吞吐量。

  • 3.3.4 Serial Old 收集器
    Serial Old 是 Serial 收集器的老年代版本, 同样是单线程收集器,使用“标记-整理”算法

  • 3.3.5 Parallel Old 收集器
    Parallel Old 是 Parallel Scavenge 收集器的老年代版本, 使用多线程和“标记-整理”算法, 吞吐量优先, 主要与 Parallel Scavenge 配合在注重吞吐量及 CPU 资源敏感系统内使用;

  • 3.3.6 CMS 收集器( Concurrent Mark Sweep)
    CMS(Concurrent Mark Sweep)收集器是一款具有划时代意义的收集器, 一款真正意义上的并发收集器, 虽然现在已经有了理论意义上表现更好的 G1 收集器, 但现在主流互联网企业线上选用的仍是 CMS(如 Taobao、微店)。
    CMS是一种以获取最短回收停顿时间为目标的收集器(CMS又称多并发低暂停的收集器),基于”标记-清除”算法实现, 整个 GC 过程分为以下 4 个步骤:

  1. 初始标记(CMS initial mark)
  2. 并发标记(CMS concurrent mark: GC Roots Tracing 过程)
  3. 重新标记(CMS remark)
  4. 并发清除(CMS concurrent sweep: 已死对象将会就地释放)
  • 3.3.7 分区收集- G1 收集器
    G1(Garbage-First)是一款面向服务端应用的收集器, 主要目标用于配备多颗 CPU 的服务器治理大内存,-XX:+UseG1GC 启用 G1 收集器。

4、JVM优化

4.1 JDK 常用 JVM 优化相关命令
bin 描述 功能
jps 打印 Hotspot VM 进程 VMID、 JVM 参数、 main()函数参数、主类名/Jar 路径
jstat 查看 Hotspot VM 运行时信息 类加载、内存、 GC[可分代查看]、 JIT 编译命令格式: jstat -gc 10340 250 20
jinfo 查看和修改虚拟机各项配置 -flag name=value
jmap heapdump: 生成 VM堆转储快照、查询 finalize 执行队列、 Java 堆和永久代详细信息 jmap -dump:live,format=b,file=heap.bin [VMID]
jstack 查看 VM 当前时刻的线程快照: 当前 VM 内每一条线程正在执行的方法堆栈集合 Thread.getAllStackTraces()提供了类似的功能
javap 查看经 javac 之后产生的 JVM 字节码代码 自动解析.class 文件, 避免了去理解 class 文件格式以及手动解析 class 文件内容
jcmd 一个多功能工具, 可以用来导出堆, 查看 Java 进程、导出线程信息、 执行GC、查看性能相关数据等 几乎集合了 jps、 jstat、 jinfo、jmap、 jstack 所有功能
jconsole 基于 JMX 的可视化监视、管理工具 可以查看内存、线程、类、CPU 信息, 以及对 JMX MBean 进行管理
jvisualvm JDK 中最强大运行监视和故障处理工具 可以监控内存泄露、跟踪垃圾回收、执行时内存分析、 CPU分析、线程分析
  • 4.1.1 jps
    jps -l
    显示线程 id 和执行线程的主类名
    jps -v
    显示线程 id 和执行线程的主类名和 JVM 配置信息


  • 4.1.2 jstat
    jstat -参数 + 线程 id + 执行时间(单位毫秒)+ 执行次数
    jstat -gc 4488 10 5

    S0C:年轻代中第一个survivor(幸存区)的容量 (字节)
    S1C:年轻代中第二个survivor(幸存区)的容量 (字节)
    S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
    S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
    EC - eden 初始空间大小
    EU - eden 使用空间大小
    OC - old 初始空间大小
    OU - old 使用空间大小
    PC - permanent 初始空间大小
    PU - permanent 使用空间大小
    YGC - youngGC 收集次数
    YGCT - youngGC 收集使用时长, 单位秒
    FGC - fullGC 收集次数
    FGCT - fullGC 收集使用时长
    GCT - 总计收集使用总时长 YGCT+FGCT
  • 4.1.3 jvisualvm
    一个 JDK 内置的图形化 VM 监视管理工具
4.2 JVM 常见参数
  • 4.2.1 内存设置
    -Xms:初始堆大小, JVM 启动的时候,给定堆空间大小。
    -Xmx:最大堆大小, JVM 运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。
    -Xmn:设置年轻代大小。整个堆大小=年轻代大小+年老代大小+永久代大小。永久代一般固定大小为 64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun 官方推荐配置为整个堆的 3/8。
    -Xss: 设置每个线程的 Java 栈大小。 JDK5.0 以后每个线程 Java 栈大小为 1M,以前每个线程堆栈大小为 256K。 根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000 左右。
    -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:设置永久代大小
    -XX:MaxTenuringThreshold:设置垃圾最大年龄。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在 Survivor 区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。
  • 4.2.2 内存设置经验
    JVM 中最大堆大小有三方面限制:相关操作系统的数据模型( 32-bt 还是 64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。 32 位系统 下,一般限制在 1.5G~2G;64 为操作系统对内存无限制。
    Tomcat 配置方式: 编写 catalina.bat|catalina.sh,增加 JAVA_OPTS 参数设置。
    常见设置
适合开发过程的测试应用。要求物理内存大于4G:
-Xmx3550m -Xms3550m -Xmn2g -Xss128k 
适合高并发本地测试使用。 且大数据对象相对较多( 如 IO 流):
-Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4
-XX:MaxPermSize=160m -XX:MaxTenuringThreshold=0 
环境: 16G 物理内存,高并发服务,重量级对象中等( 线程池,连接池等),常用对象比例为 40%( 运行过程中产生的对象 40%是生命周期较长的)
-Xmx10G -Xms10G -Xss1M -XX:NewRatio=3 -XX:SurvivorRatio=4 -XX:MaxPermSize=2048m -XX:MaxTenuringThreshold=5
  • 4.2.3 收集器设置
    -XX:+UseSerialGC:设置串行收集器,年轻代收集器, 次收集器
    -XX:+UseParallelGC:设置并行收集器
    -XX:+UseParNewGC:设置年轻代为并行收集。可与 CMS 收集同时使用。 JDK5.0 以上, JVM会根据系统配置自行设置,所以无需再设置此值
    -XX:+UseParallelOldGC:设置并行年老代收集器, JDK6.0 支持对年老代并行收集
    -XX:+UseConcMarkSweepGC:设置年老代并发收集器, 测试中配置这个以后,-XX:NewRatio的配置失效,原因不明。所以,此时年轻代大小最好用-Xmn 设置
    -XX:+UseG1GC:设置 G1 收集器

  • 4.2.4 垃圾回收统计信息
    类似日志的配置信息。会有控制台相关信息输出。
    -XX:+PrintGC
    -XX:+Printetails
    -XX:+PrintGCTimeStamps
    -Xloggc:filename

  • 4.2.5 并行收集器设置
    -XX:ParallelGCThreads=n:设置并行收集器收集时最大线程数使用的 CPU 数。并行收集线程数。
    -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间,单位毫秒。可以减少 STW 时间。
    -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为 1/(1+n)并发收集器设置
    -XX:+CMSIncrementalMode:设置为增量模式。适用于单 CPU 情况。
    -XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的 Survivor 区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
    -XX:CMSFullGCsBeforeCompaction=n:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次 GC 以后对内存空间进行压缩、整理。
    -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片

  • 4.2.6 收集器设置经验
    关于收集器的选择 JVM 给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下, JDK5.0 以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。 JDK5.0 以后, JVM 会根据当前系统配置进行判断。
    常见配置
并行收集器主要以到达一定的吞吐量为目标,适用于科学计算和后台处理等。
-Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
使用 ParallelGC 作为并行收集器, GC 线程为 20( CPU 核心数>=20 时),内存问题根据硬件配置具体提供。 建议使用物理内存的 80%左右作为 JVM 内存容量。
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
指定老年代收集器,在 JDK5.0之后的版本,ParallelGC对应的全收集器就是ParallelOldGC。可以忽略
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
指定 GC 时最大暂停时间。单位是毫秒。 每次 GC 最长使用 100 毫秒。 可以尽可能提高工作线程的执行资源。
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
UseAdaptiveSizePolicy 是提高年轻代 GC 效率的配置。次收集器执行效率。
并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域、互联网领域等。
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
指定年轻代收集器为 ParNew,年老代收集器 ConcurrentMarkSweep,并发 GC 线程数为20( CPU 核心>=20),并发 GC 的线程数建议使用( CPU 核心数+3) /4 或 CPU 核心数【 不推荐使用】。

-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
CMSFullGCsBeforeCompaction=5 执行 5 次 GC 后,运行一次内存的整理。
UseCMSCompactAtFullCollection 执行老年代内存整理。 可以避免内存碎片,提高 GC 过程中的效率,减少停顿时间。
  • 4.2.7 总结
    年轻代大小选择:
    响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
    年老代大小选择:
    响应时间优先的应用: 年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。

相关文章

  • jvm 相关阅读

    相关阅读 JVM性能优化1-JVM简介 JVM性能优化2-编译器 JVM性能优化3-垃圾回收 JVM性能优化4-C...

  • JVM调优

    JVM(Java虚拟机)优化大全和案例实战 JVM 优化经验总结 JVM 数据存储介绍及性能优化 JVM诊断...

  • Jvm优化技术

    Jvm优化技术有:逃逸分析、方法内联 一:Jvm优化技术之逃逸分析 1:概念 JVM的优化技术,可以有效减少Jav...

  • 后端文章精选- 收藏集 - 掘金

    【玩转 JVM 性能优化】Java 的伸缩性 - 后端 - 掘金感谢朋友【吴杰】投递本文。 JVM性能优化系列文章...

  • JVM介绍系列文章

    知晓JVM系列(一):对JVM总览知晓JVM系列(二):JVM内存管理机制与优化初探知晓JVM系列(三) :常用的...

  • JVM(十三):后端编译优化

    JVM(十三):后端编译优化 在 JVM(一):源文件的转变 中我们介绍了 Java 中的前端优化,即将 Java...

  • ElasticSearch 优化配置

    索引建立优化 配置优化./config/elasticsearch.yml ./config/jvm.option...

  • JVM学习系列学习一

    本文主要内容: ​ 一:为什么要对JVM进行优化? 我们在自己电脑上进开发的时候,几乎很少考虑对JVM进行优化。但...

  • Java性能调优

    概览 设计优化 Java程序优化 Java程序优化并行程序开发及优化 JVM调优 Java性能调优工具

  • 常用的后端性能优化六种方式:缓存化+服务化+异步化等

    性能优化专题 前端性能优化 数据库性能优化 jvm和多线程优化 架构层面优化 缓存性能优化 常用的后端性能优化六大...

网友评论

    本文标题:JVM优化

    本文链接:https://www.haomeiwen.com/subject/jylvrqtx.html