JVM优化

为什么要对jvm进行优化

解决一下问题,例如:

  • 在生产环境程序突然卡主,没有任何日志的输出
  • 在某一段程序,CPU突然飙升
  • 不仅要让程序稳定,更要提升程序的运行速度

jvm的运行参数

1、jvm的参数类型分为三类:分别是:

  • 标准参数:指在往后的任何一个jvm的发行版本中都不会随意改变的参数
    • -help
    • -version
  • -X参数(非标准参数):在往后的jvm版本中可能会做出一些改变
    • -Xint
    • -Xcomp
  • -XX参数:使用频率较高的jvm调休参数
    • -XX:newSize
    • -XX:+UseSerialGC

实验

  • 在控制台输入 java -help
  • 找到 -D<名称>=<值> 设置系统属性
  • 编写java程序
public class TestJVM {

    public static void main(String[] args) {
        String str = System.getProperty("str");
        if(str == null){
            System.out.println("是空的");
        }else {
            System.out.println(str);
        }
    }
}

  • 在根目录下创建文件夹 mkdir /test
  • 在该目录下创建文件 TestJVM.java,代码copy
  • 编译该文件 javac TestJVM.java
  • 查看文件 ll,会发现生成了class文件
  • 执行class文件 java -Dstr=你真帅 TestJVM,会得到输出结果为:你真帅

2、标准参数

2.1-server与-client模式

可以通过-server或-client设置jvm的运行参数。

  • 它们的区别是Server VM的初始堆空间会大一些,默认使用的是并行垃圾回收器,启动慢运行快。
  • Client VM相对来讲会保守一些,初始堆空间会小一些,使用串行的垃圾回收器,它的目标是为了让JVM的启动速度更快,但运行速度会比Serverm模式慢些。
  • JVM在启动的时候会根据硬件和操作系统自动选择使用Server还是Client类型的JVM。
  • 32位操作系统
  • 如果是Windows系统,不论硬件配置如何,都默认使用Client类型的JVM。
  • 如果是其他操作系统上,机器配置有2GB以上的内存同时有2个以上CPU的话默认使用server模式,否则
    使用client模式。
  • 64位操作系统
    • 只有server类型,不支持client类型。

3、-X非标准参数

3.1-Xint、-Xcomp、-Xmixed

-Xint:解释编译模式,一边编译一边执行,效率相对较低

java -showversion -Xint TestJVM

-Xcomp:预编译模式,程序先编译完毕,然后执行class文件,效率相对较高。但是这种模式会牺牲编译器性能,原因是-xcomp没有让JVM启用JIT编译器的全部功能。JIT编译器可以对是否需要编译做判断,如果所有代码都进行编译的话,对于一些只执行一次的代码就没有意义了。

java -showversion -Xcomp TestJVM

-Xmixed:混合模式,编译器会自动选择模式,也是推荐的模式

4、-XX参数

-XX也是非标准参数,主要用于JVM的调休和debug的操作
-XX参数的使用有2种方式,一种是boolean类型,一种是非boolean类型

4.1 boolean类型

  • 格式:-XX:[±]<name> 表示启用或者是禁用<name>属性
    • 例如:-XX:+DisableExplicitGC表示禁用手动调用gc操作,也就是说调用System.gc()无效

4.2非boolean类型

  • 格式:-XX:<name>=<value> 表示<name>属性的值为<value>
    • 如:-XX:NewRatio=1表示新生代和老年代的比值

-Xms和-Xmx参数

-Xms与-Xmx分别是设置jvm的堆内存的初始大小和最大大小
-Xmx2048m:等价于-XX:MaxHeapSize,设置JVM最大堆内存为2048M
-Xms512m:等价于-XX:InitialHeapSize,设置JVM初始对内存为512M
适当的调整jvm的内存大小,可以充分利用服务器资源,让程序跑的更快
例:

java -Xms521m -Xmx2048m TestJVM

5、查看JVM的运行参数

  • 运行java命令时打印参数,需要添加-XX:+PrintFlagsFinal参数即可
    • java -XX:+PrintFlagsFinal -version
    • 从结果中可以看到,参数有boolean类型和数字类型,值的操作符是=或:=,分别代表默认值和被修改的值。
    • 运行 java -XX:+PrintFlagsFinal -XX:+ZeroTLAB -version 发现ZeroTLAB的值被修改了
  • 查看正在运行的JVM参数
    • 如果想要查看正在运行的jvm就需要借助于jinfo命令查看
      • 首先,启动一个tomcat用于测试,来观察下运行的jvm参数。
      • 通过jps 或者 jps -l 查看java进程
      • #查看所有的参数,用法:jinfo -flags <进程id>
        例:jinfo -flags 5466
      • #查看某一参数的值,用法:jinfo -flag <参数名> <进程id>
        例:jinfo -flag MaxHeapSize 5466
        得到
        -XX:MaxHeapSize=255852544(字节)

JVM的内存模型

1.不同版本JDK的内存模型

1.1 JDK1.7的内存模型

  • Young 年轻区(代)
    Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中,Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Eden区间变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。
  • Tenured 年老区
    Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。
  • Perm 永久区
    Perm代主要保存class,method,filed对象,这部份的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm中,这种情况下,一般重新启动应用服务器可以解决问题。
  • Virtual区:
    最大内存和初始内存的差值,就是Virtual区。

1.2 JDK1.8的内存模型

在这里插入图片描述
由上图可以看出,jdk1.8的内存模型是由2部分组成,年轻代 + 年老代。
年轻代:Eden + 2*Survivor
年老代:OldGen
在jdk1.8中变化最大的Perm区,用Metaspace(元数据空间)进行了替换。需要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是与1.7的永久代最大的区别所在。
在这里插入图片描述

2. 通过jstat命令进行查看堆内存使用情况

jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:
jstat [-命令选项] [进程id] [间隔时间/毫秒] [查询次数]

2.1 查看class加载统计

[root@node01 ~]# jps 
7080 Jps 6219 Bootstrap 
[root@node01 ~]# jstat -class 6219
Loaded Bytes Unloaded Bytes Time 
3273  7122.3 0       0.0   3.98

说明:
Loaded:加载class的数量
Bytes:所占用空间大小
Unloaded:未加载数量
Bytes:未加载占用空间
Time:时间

2.2 查看编译统计

[root@node01 ~]# jstat -compiler 6219 
Compiled Failed Invalid Time FailedType FailedMethod 
2376     1      0       8.04  1         org/apache/tomcat/util/IntrospectionUtils setProperty

说明:
Compiled:编译数量。
Failed:失败数量
Invalid:不可用数量
Time:时间
FailedType:失败类型
FailedMethod:失败的方法

2.3垃圾回收统计

[root@node01 ~]# jstat -gc 6219 
S0C    S1C    S0U S1U    EC      EU     OC      OU      MC      MU     CCSC     CCSU  YGC YGCT FGC FGCT GCT 
9216.0 8704.0 0.0 6127.3 62976.0 3560.4 33792.0 20434.9 23808.0 23196.1 2560.0 2361.6 7   1.078 1 0.244 1.323

#也可以指定打印的间隔和次数,每1秒中打印一次,共打印5次

[root@node01 ~]# jstat -gc 6219 1000 5

说明:
S0C:第一个Survivor区的大小(KB)
S1C:第二个Survivor区的大小(KB)
S0U:第一个Survivor区的使用大小(KB)
S1U:第二个Survivor区的使用大小(KB)
EC:Eden区的大小(KB)
EU:Eden区的使用大小(KB)
OC:Old区大小(KB)
OU:Old使用大小(KB)
MC:方法区大小(KB)
MU:方法区使用大小(KB) CCSC:压缩类空间大小(KB) CCSU:压缩类空间使用大小(KB) YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

3.jmap的使用以及内存溢出分析

前面通过jstat可以对jvm堆的内存进行统计分析,而jmap可以获取到更加详细的内容,如:内存使用情况的汇总、对内存溢出的定位与分析。

3.1 查看内存使用情况

jmap -heap [进程id]

[root@node01 ~]# jmap -heap 6219 
  • Heap Configuration: #堆内存配置信息
  • Heap Usage: # 堆内存的使用情况
  • PS Young Generation #年轻代
  • PS Old Generation #年老代

3.2 查看内存中对象数量及大小

  • 查看所有对象,包括活跃以及非活跃的
jmap -histo <pid> | more
  • 查看活跃对象
jmap -histo:live <pid> | more

#对象说明

  • B byte
  • C char
  • D double
  • F float
  • I int
  • J long
  • Z boolean
  • [ 数组,如[I表示int[]
  • [L+类名 其他对象

3.3 将内存使用情况dump到文件中

有些时候我们需要将jvm当前内存中的情况dump到文件中,然后对它进行分析,jmap也是支持dump到文件中的。
用法:jmap -dump:format=b,file=dumpFileName <pid>
示例:jmap -dump:format=b,file=/tmp/dump.dat 6219

3.4 使用 jhat命令对dump文件进行分析(用的不多,如果不想看可以跳过)

用法:
命令:jhat -port <端口号> <file>
例:jhat -port 9999 /tmp/dump.dat
在浏览器输入 :ip:9999进行查看
此时会发现有很多的类的全路径,将进度条放到最后
在这里插入图片描述
点进去之后,点击查看:
在这里插入图片描述
点击OQL,可以使用Example中案例进行查询,不再赘述。

3.5使用mat工具进行查询分析

3.5.1 mat介绍:

MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。
官网地址:mat官网地址

3.5.2 安装:略
3.5.3 使用方法:
  • 使用sz命令将生成的dump.dat文件下载到本地中
    如果没有sz命令执行以下命令进行下载yum install lrzsz
  • 打开mat工具,点击 file-> Open Heap Dump 选择刚刚下载好的文件打开
    在这里插入图片描述
  • 查看可能存在内存泄漏的分析
    在这里插入图片描述
3.5.4 实战:使用mat分析内存泄漏
  • 编写应用程序模拟内存溢出
public class TestJVMOutOfMemory {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for(int i=0; i<1000000 ;i++){
            String str = "";
            for(int j=0;j<1000;j++){
                str += UUID.randomUUID().toString();
            }
            list.add(str);
        }
        System.out.println("ok");
    }
}

在idea中设置jvm的内存(初始堆内存为8M,最大堆内存为8M),并指定输出:

-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

运行程序,等待程序报错

Dumping heap to java_pid1704.hprof ...
Heap dump file created [9486082 bytes in 0.018 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.base/java.util.Arrays.copyOfRange(Arrays.java:4030)
	at java.base/java.lang.StringLatin1.newString(StringLatin1.java:715)
	at java.base/java.lang.StringBuilder.toString(StringBuilder.java:448)
	at TestJVMOutOfMemory.main(TestJVMOutOfMemory.java:11)

Process finished with exit code 1

找到输出的.hprof 文件,使用mat工具打开
在这里插入图片描述
在这里插入图片描述

3.6 进程的六种状态

在这里插入图片描述

  • 初始态(NEW)
    • 创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
  • 运行态(RUNNABLE),在Java中,运行态包括 就绪态 和 运行态。
    • 就绪态
      • 该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行。
      • 所有就绪态的线程存放在就绪队列中。
    • 运行态
      • 获得CPU执行权,正在执行的线程。
      • 由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程。
  • 阻塞态(BLOCKED)
    • 当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。
    • 而在Java中,阻塞态专指请求锁失败时进入的状态。
    • 由一个阻塞队列存放所有阻塞态的线程。
    • 处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。
  • 等待态(WAITING)
    • 当前线程中调用wait、join、park函数时,当前线程就会进入等待态。
    • 也有一个等待队列存放所有等待态的线程。
    • 线程处于等待态表示它需要等待其他线程的指示才能继续运行
    • 进入等待态的线程会释放CPU执行权,并释放资源(如:锁)
  • 超时等待态(TIMED_WAITING)
    • 当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态;
    • 它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒;
    • 进入该状态后释放CPU执行权 和 占有的资源。
    • 与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
  • 终止态(TERMINATED)
    • 线程执行结束后的状态。
3.6.1 jstack命令

有些时候我们需要查看下jvm中的线程执行情况,比如,发现服务器的CPU的负载突然增高了、出现了死锁、死循环等,我们该如何分析呢?
由于程序是正常运行的,没有任何的输出,从日志方面也看不出什么问题,所以就需要看下jvm的内部线程的执行情况,然后再进行分析查找出原因。
这个时候,就需要借助于jstack命令了,jstack的作用是将正在运行的jvm的线程情况进行快照,并且打印出来:
#用法:jstack
例 :jstack 6219

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值