JVM:参数调优

本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记

前言

查看 JVM 系统默认值:使用 jps 和 jinfo 进行查看

-Xms:初始堆空间
-Xmx:最大堆空间
-Xss:栈空间

-Xms 和 -Xmx 最好调整一致,防止 JVM 频繁进行收集和回收

JVM参数类型

标配参数(从JDK1.0 - Java12都在,很稳定)

  • java -version
  • java -help
  • java -showversion

X 参数(了解)

  • -Xint:解释执行
  • -Xcomp:第一次使用就编译成本地代码
  • -Xmixed:混合模式

XX参数(重点)

  • Boolean类型

    • 公式:-XX:+/-某个属性

      +表示开启 / -表示关闭

    • 如:-XX:-PrintGCDetails:表示关闭了GC详情输出

  • key-value类型

    • 公式:-XX:属性key=属性value
    • 不满意初始值,可以通过相应命令调整
    • 如:-XX:MetaspaceSize=21807104,调整Java元空间的值

使用

查看运行的 Java 程序,JVM 参数是否开启,具体值为多少?

首先我们运行一个HelloGC的java程序

public class HelloGC {

    public static void main(String[] args) throws InterruptedException {
System.out.println("hello GC");
Thread.sleep(Integer.MAX_VALUE);
}
}

然后使用下列命令查看它的默认参数

jps:查看java的后台进程
jinfo:查看正在运行的java程序

具体使用:

C:\Users\ZhuCC\Desktop\Java\Code\DemoCode>jps -l
34452 org.jetbrains.jps.cmdline.Launcher
37396 sun.tools.jps.Jps
37624 HelloGC
23756

查看到 HelloGC 的进程号为:37624

我们使用 jinfo -flag 然后查看是否开启 PrintGCDetails 这个参数

jinfo -flag PrintGCDetails 37624

得到的内容为

-XX:-PrintGCDetails

上面提到了,-号表示关闭,即没有开启 PrintGCDetails 这个参数

下面我们需要在启动 HelloGC 的时候,增加 PrintGCDetails 这个参数,需要在运行程序的时候配置 JVM 参数

在 IDEA 中可以通过 Run->Edit Configurations->VM options 中进行添加相应的 JVM 参数配置:

然后在 VM Options 中加入下面的代码,现在 + 号表示开启

-XX:+PrintGCDetails

然后在使用 jinfo 查看我们的配置

jinfo -flag PrintGCDetails 33788

得到的结果为

-XX:+PrintGCDetails

我们看到原来的-号变成了+号,说明我们通过 VM Options 配置的 JVM 参数已经生效了

使用下列命令,会把 JVM 的全部默认参数输出

jinfo -flags PID

如,对上述 HelloGC 程序使用该命令,输出如下:

C:\Users\ZhuCC\Desktop\Java\Code\DemoCode>jinfo -flags 33788
Attaching to process ID 33788, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.241-b07
# 不是默认的 JVM 配置,会根据使用环境的新能而确定相应值
Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=427819008
0 -XX:MaxNewSize=1426063360 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=89128960 -XX:OldSize=179306496
-XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTi
meStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
# 通过Command line自行配置的JVM参数
Command line: -XX:+PrintGCDetails -javaagent:C:\Software\JetBrains\IntelliJ IDEA 2017.3.7\lib\idea
_rt.jar=7172:C:\Software\JetBrains\IntelliJ IDEA 2017.3.7\bin -Dfile.encoding=UTF-8

题外话

两个经典参数:-Xms-Xmx,这两个参数如何解释

这两个参数,还是属于 XX 参数,因为取了别名

  • -Xms 等价于 -XX:InitialHeapSize :初始化堆内存(默认只会用最大物理内存的1/64)
  • -Xmx 等价于 -XX:MaxHeapSize :最大堆内存(默认只会用最大物理内存的1/4)

JVM 默认参数

java -XX:+PrintFlagsInitial:主要是查看初始默认值

  • java -XX:+PrintFlagsInitial -version
  • java -XX:+PrintFlagsInitial(重要参数)
C:\Users\ZhuCC>java -XX:+PrintFlagsInitial
[Global flags]
intx ActiveProcessorCount = -1 {product}
uintx AdaptiveSizeDecrementScaleFactor = 4 {product}
uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product}
uintx AdaptiveSizePausePolicy = 0 {product}
...
intx CICompilerCount = 2 {product}
...

java -XX:+PrintFlagsFinal:表示修改以后,最终的值,即经过调整后的JVM参数值

C:\Users\ZhuCC>java -XX:+PrintFlagsFinal
[Global flags]
intx ActiveProcessorCount = -1 {product}
uintx AdaptiveSizeDecrementScaleFactor = 4 {product}
uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product}
uintx AdaptiveSizePausePolicy = 0 {product}
...
intx CICompilerCount := 3 {product}
...

注意:如果有 := 表示修改过的, = 表示没有修改过的

常用 JVM 基本配置参数

划下重点:

  • JDK8 使用了元空间取代了 JDK6 中的永久代
  • 元空间用本地物理内存,不再由JVM管理
  • 字符串池和类的静态变量还是在Java堆中

查看堆内存

查看 JVM 的初始化堆内存 -Xms 和最大堆内存 -Xmx

public class HeapSize {
public static void main(String[] args) {
// 返回Java虚拟机中内存的总量
long totalMemory = Runtime.getRuntime().totalMemory(); // 返回Java虚拟机中试图使用的最大内存量
long maxMemory = Runtime.getRuntime().maxMemory(); System.out.println("TOTAL_MEMORY(-Xms) = " + totalMemory + "(Byte) -- " + (totalMemory / (double)1024 / 1024) + "MB");
System.out.println("MAX_MEMORY(-Xmx) = " + maxMemory + "(Byte) -- " + (maxMemory / (double)1024 / 1024) + "MB");
}
}

运行结果为:

TOTAL_MEMORY(-Xms) = 257425408(Byte) -- 245.5MB
MAX_MEMORY(-Xmx) = 3803185152(Byte) -- 3627.0MB

-Xms 初始堆内存为:物理内存的1/64

-Xmx 最大堆内存为:系统物理内存的1/4

打印 JVM 默认参数

使用 JVM 参数 -XX:+PrintCommandLineFlags 打印出 JVM 的默认的简单初始化参数

比如我的机器输出为:

-XX:InitialHeapSize=267290176 -XX:MaxHeapSize=4276642816 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 

一些常用调优参数

  • -Xms:初始化堆内存,默认为物理内存的1/64,等价于 -XX:initialHeapSize

  • -Xmx:最大堆内存,默认为物理内存的1/4,等价于 -XX:MaxHeapSize

  • -Xss:设计单个线程栈的大小,一般默认为512K~1024K,等价于 -XX:ThreadStackSize

    进一步说明:

    • 使用 jinfo -flag ThreadStackSize 会发现 -XX:ThreadStackSize=0
    • 这个值的大小是取决于平台的
      • Linux/x64:1024KB
      • OS X:1024KB
      • Oracle Solaris:1024KB
      • Windows:取决于虚拟内存的大小
  • -Xmn:设置年轻代大小

  • -XX:MetaspaceSize:设置元空间大小

    • 元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现,不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存,因此,默认情况下,元空间的大小仅受本地内存限制。
    • 但是默认的元空间大小:只有20多M -XX:MetaspaceSize=21807104
    • 为了防止在频繁的实例化对象的时候,让元空间出现OOM,因此可以把元空间设置的大一些:-Xms10m -Xmx10m -XX:MetaspaceSize=1024m -XX:+PrintCommandLineFlags
  • -XX:PrintGCDetails:输出详细GC收集日志信息

  • -XX:SurvivorRatio:调节新生代中 eden 和 S0、S1的空间比例

  • -XX:NewRatio:配置年轻代 new 和老年代 old 在堆结构的占比

  • -XX:MaxTenuringThreshold:设置对象进入老年代的阈值

垃圾回收

JVM 命令 -XX:PrintGCDetails:输出详细GC收集日志信息

  • GC
  • Full GC

我们使用一段代码,制造出垃圾回收的过程

首先我们设置一下程序的启动配置: 设置初始堆内存为10M,最大堆内存为10M

-Xms10m -Xmx10m -XX:+PrintGCDetails

然后用下列代码,创建一个大于堆空间大小的byte类型数组

byte [] byteArray = new byte[50 * 1024 * 1024];

运行后,发现会出现下列错误,这就是OOM:java内存溢出,也就是堆空间不足

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at HeapSize.main(HeapSize.java:5)

同时还打印出了GC垃圾回收时候的详情

[GC (Allocation Failure) [PSYoungGen: 1658K->488K(2560K)] 1658K->632K(9728K), 0.0015767 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 488K->488K(2560K)] 632K->648K(9728K), 0.0004406 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 488K->0K(2560K)] [ParOldGen: 160K->598K(7168K)] 648K->598K(9728K), [Metaspace: 3212K->3212K(1056768K)], 0.0033779 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 598K->598K(9728K), 0.0001481 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 598K->580K(7168K)] 598K->580K(9728K), [Metaspace: 3212K->3212K(1056768K)], 0.0031963 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 2560K, used 138K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 6% used [0x00000000ffd00000,0x00000000ffd228c8,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 580K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 8% used [0x00000000ff600000,0x00000000ff691358,0x00000000ffd00000)
Metaspace used 3259K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 353K, capacity 388K, committed 512K, reserved 1048576K

问题发生的原因:

因为们通过 -Xms10m-Xmx10m 只给Java堆栈设置了10M的空间,但是创建了50M的对象,因此就会出现空间不足,而导致出错

同时在垃圾收集的时候,我们看到有两个过程:GC 和 Full GC

GC过程:

[GC (Allocation Failure) [PSYoungGen: 1972K->504K(2560K)] 1972K->740K(9728K), 0.0156109 secs] [Times: user=0.00 sys=0.00, real=0.03 secs]
# 其中:GC (Allocation Failure):表示分配失败,那么就需要触发年轻代空间中的内容被回收
# [PSYoungGen: 1972K->504K(2560K)] 1972K->740K(9728K)

Full GC:

Full GC 大部分发生在老年代

[Full GC (Allocation Failure) [PSYoungGen: 488K->0K(2560K)] [ParOldGen: 160K->598K(7168K)] 648K->598K(9728K), [Metaspace: 3212K->3212K(1056768K)], 0.0033779 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

规律:

[名称: GC前内存占用 -> GC后内存占用 (该区内存总大小)]

当我们出现了老年代垃圾回收都没有用的时候,就会出现OOM异常

调整Heap中各区策略

  1. -XX:SurvivorRatio

    调节新生代中 eden 和 from、to 的空间比例,默认为 -XX:SurvivorRatio=8,即:Eden:from:to = 8:1:1,如下图:

    设置为:-XX:SurvivorRatio=4,则为 Eden:from:to = 4:1:1,如下图:

    SurvivorRatio 值就是设置 eden 区的比例占多少,from 和 to 相同

    Java堆从GC的角度还可以细分为:新生代(Eden区,From Survivor区合To Survivor区)和老年代

    1. 对象从eden与SurvivorFrom复制到SurvivorTo中,年龄 + 1

      进一步:首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到SurvivorFrom去,当Eden区再次触发GC的时候会扫描Eden区合From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果对象的年龄已经到达老年的标准,则赋值到老年代区),通知把这些对象的年龄 + 1;

    2. 清空eden与SurvivorFrom

      然后,清空eden,SurvivorFrom中的对象,也即复制之后有交换,谁空谁是to;

    3. SurvivorTo和SurvivorFrom互换

      最后,SurvivorTo和SurvivorFrom互换,原SurvivorTo成为下一次GC时的SurvivorFrom区,部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认为15),最终如果还是存活,就存入老年代

  2. -XX:NewRatio

    配置年轻代 new 和老年代 old 在堆结构的占比,NewRadio 值就是设置老年代的占比

    默认:-XX:NewRatio=2 新生代占1,老年代2,年轻代占整个堆的1/3,如下图:

    -XX:NewRatio=4:新生代占1,老年代占4,年轻代占整个堆的1/5,这样会导致新生代新生代特别小,造成频繁的进行GC收集

  3. -XX:MaxTenuringThreshold

    设置对象进入老年代的阈值,SurvivorTo 和 SurvivorFrom 互换,原 SurvivorTo 成为下一次 GC 时的SurvivorFrom 区,部分对象会在 From 和 To 区域中复制来复制去,如此交换 n 次(n 就是通过该 JVM 参数设置),最终如果还是存活,就存入老年代

    这里就是调整这个次数的,默认是15,并且在 Java8 中规定该值在 0~15之间

    查看默认进入老年代年龄:jinfo -flag MaxTenuringThreshold PID

    -XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻对象不经过 Survivor 区,直接进入老年代。对于年老代比较多的应用,可以提高效率。

    而如果将此值设置为一个较大的值,则年轻代对象会在 Survivor 区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收,减少 Full GC 发生的次数。

JVM:参数调优的更多相关文章

  1. JVM参数调优

    JVM参数调优 JVM参数调优是一个很头痛的问题,可能和应用有关系,下面是本人一些调优的实践经验,希望对读者能有帮助,环境LinuxAS4,resin2.1.17,JDK6.0,2CPU,4G内存,d ...

  2. (转)JVM参数调优八大技巧

    这里和大家分享一下JVM参数调优的八条经验,JVM参数调优,这是很头痛的问题,设置的不好,JVM不断执行FullGC,导致整个系统变得很慢,网站停滞时间能达10秒以上,相信通过本文的学习你对JVM参数 ...

  3. SpringBoot-内部运行jvm参数调优

    SpringBoot JVM参数调优 这个根据服务器的内存大小,来设置堆参数. -Xms :设置Java堆栈的初始化大小 -Xmx :设置最大的java堆大小 实例参数-XX:+PrintGCDeta ...

  4. 性能测试三十六:内存溢出和JVM常见参数及JVM参数调优

    堆内存溢出: 此种溢出,加内存只能缓解问题,不能根除问题,需优化代码堆内存中存在大量对象,这些对象都有被引用,当所有对象占用空间达到堆内存的最大值,就会出现内存溢出OutOfMemory:Java h ...

  5. 【学习】011 JVM参数调优配置

    自动内存管理机制 Java虚拟机原理 所谓虚拟机,就是一台虚拟的机器.他是一款软件,用来执行一系列虚拟计算指令,大体上虚拟机可以分为 系统虚拟机和程序虚拟机, 大名鼎鼎的Visual Box.Vmar ...

  6. JVM参数调优:Eclipse启动实践

    本文主要参考自<深入理解 Java 虚拟机>.这本书是国人写的难得的不是照搬代码注释的且不是废话连篇的技术书,内容涵盖了 Java 从源码到字节码到执行的整个过程,包括了 JVM(Java ...

  7. JVM参数调优总结

    一.前言 要想成为一名高级Java开发具备JVM调优的能力必不可少,能够根据项目实际情况进行JVM调优的前提是理解JVM原理和常用JVM参数的含义及作用,虽然<深入理解Java虚拟机>这本 ...

  8. Java后端进阶-JVM参数调优

    package com.study.performance; import org.springframework.boot.SpringApplication; import org.springf ...

  9. jvm 参数调优

    FROM: http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html#CMSInitiatingOccupancyFraction ...

  10. SpringBoot-外部运行jvm参数调优

    外部运行调优 java -server -Xms32m -Xmx32m  -jar springboot_v2.jar

随机推荐

  1. vue的常见理论问题

    1.什么是 mvvm? mvvm 和 mvc 区别? MVVM 是 Model-View-ViewModel 的缩写.mvvm 是一种设计思想.Model 层代表数据模型,View 代表 UI 组件. ...

  2. 并发容器之ConcurrentMap

    一.concurentMap 1.数据结构,分段数组segment不扩容,里面的table扩容,每次翻倍,table中放的是entry链表的头地址: 2.初始化 segment和table的长度都是2 ...

  3. 注解@Component方式代替xml装配bean

    一.@Component 用注解来装配bean 1. 2.对类使用注解,装配bean: 3.类中,注入其他对象: 二.bean.xml中配置@Componet方式装配bean 1.开启注解装配bean ...

  4. TCP协议和套接字

    一.TCP通信概述,逻辑连接就是三次握手 二.客户端和服务端实现TCP协议通信基本步骤 1.客户端套接字对象 Socket 2.服务端套接字ServerSocket 客户端补充完整代码:除了创建各自的 ...

  5. sed 找出含有某个字符串的行 注释掉

    1.源文件例子 [root@node1 ~]# cat /etc/fstab # # /etc/fstab # Created by anaconda on Mon Mar 1 18:32:15 20 ...

  6. STM32CbueIDE 与 J-Link

    STM32CbueIDE 与 J-Link 无论是 STM32CbueIDE 还是 Keil, 在使用 JLink 的时候都不过是先启 JLink 的 GDB 服务,然后再"远程" ...

  7. Jmeter系列(26)- 常用逻辑控制器(5) | 循环控制器Loop Controller

    循环控制器(Loop Controller) 字面意思,循环该控制器下的请求 设定固定循环次数,或者一直循环 同线程组的循环是父子关系,大家可以试下,如果线程组设置了一直循环,而循环控制器设置了2次, ...

  8. php nginx 路径批量配置

    * 假设 E:\upload 作为图片上传的位置 nginx 做web服务 * 创建文件conf.php 放到这个目录下 <?php function handleDir($it, &$ ...

  9. python序列类型及一些操作

    序列分类 1.按存放的数据类型分类: 容器类型(能存放不同类型的数据):list.tuple.coolections.deque 扁平序列(只能存放一种类型的数据):str.bytes.bytearr ...

  10. genymotion启动模拟器后,sdk查询adb devices为空-解决方案

    我们在genymotion中安装了一个安卓模拟器,比如Google Nexus 4,启动该模拟器后,在cmd中输入adb devices,发现为空. 解决方案:在genymotion选择Setting ...