七、性能测试之内存分析

一、内存知识

内存(memory),又叫主存,是cpu与其他设备沟通的桥梁,主要用来临时存放数据,配合cpu工作,协调cpu的处理速度

  • 1、理解
    • 硬盘数据、外设数据、网络传输数据,要进入cpu前,都要先进入内存
    • 临时存放,在断电后,内存内容就会丢失
    • 当打开一个软件,就会分配虚拟内存、物理内存空间,cpu读取虚拟内存
    • 程序在启动时,并不会把所有的数据都加到内存
    • 32位的系统,最大支持的内存条,只有4g,64位系统,最大可以支持128T
    • 程序在启动时,会有一个内存配置信息,就会告诉系统,我要在整个内存条中,申请多少m内存空间
  • 2、内存的组成:内存地址、存储单元
    • 内存地址:一个编号,用于指示数据位置(绝对地址、相对地址)
    • 存储单元:存放实际数据的地方
    • 内存地址与存储单元的关系:门牌号和房屋
      • 门牌号找到你的家庭地址(内存地址),房屋能装人和各种家居用品(存储单元)
        在这里插入图片描述
      • 数据大小
        • 写过代码的都知道,定义一个数据,要声明数据类型:
          • 为什么要声明这样一个类型?
            • 为了分配存储空间大小,存储大小一定要比实际数据大,才能装下实际数据(东西多,袋子小就装不下)
            • 1、单个数据:int、float、char…
            • 2、连续数据:列表,数组…
              • 分配一个连续的存储单元
                • python:列表 [8,‘nmb’,[‘vip8’,‘vip12’],]
                • 连续的存储单元–> 内存卡
                • 数据存储是不是可以更复杂?
                • 所以,就有了数据结构
              • 列表中,插入一个数据,要把插入位置之后的所有数据都移动位置,所以,这种速度是比较慢,这个时候,我们可以用链表
            • 3、内存—树形结构
              • 树形结构
                在这里插入图片描述

                • 1、链表
                  • 首先它也是一种数组,只是它的每个数据存储的是数据值+下一个元素的地址。如果要在链表中,插入一个数据,插入位置前一个元素中下一个元素的地址,指向性插入的数据的地址,被插入的元素记录的下一个元素地址,数据本身不用移动。
                  • 这种数据插入方式,速度要比列表要块
                  • 但是,读取某个数据的速度降低,因为我们每查询一个数据,都要从链表的第1个数据开始查找,一直到找到为止,这个中间,我们可能要进行大量IO数据交互,那么它的IO可能消耗比较高
              • 2、二叉树
                • 建立在链表的基础上的一种数据结构
                • 以第一个数据为原数据,后续的数据与这个数据比较,小的放左边,大的放右边,生成一个链表
                • 查找数据时,比数据大的,我就去右边找,比数据小,我就去左边找,这个时候,IO就比链表要少很多
                • 增删和链表一样
                  在这里插入图片描述
  • 4、数据结构
    • 堆栈
      • 不是一个,而是两种不同的数据结构
      • 栈(stack
        • LIFO== Last In First Out 后进先出
          • 就像收纳箱装东西,先进去的在最下面;取出来时,最上面的最先出来
          • 装入叫压入(push),取出叫弹出(pop)
          • 存放程序的变量
      • 队列(queue)
        • FIFO == First In First Out 先进先出
          • 就像排队打饭(顺序排列)/循环转圈(循环队列)
        • 堆(heap)
          • 类似图书馆书架上的图书
          • 一种经过排序的树形结构
          • 存放程序的对象
  • 二、内存使用

    • 一个程序运行起来,需要分配一块内存空间,无异常时,就在分配的这块内存空间弹性伸缩存储
    • 这个空间,至少会包括一块栈区和一块堆区,还会包括其他
      在这里插入图片描述
      • 栈区:存放程序中的局部变量,变量有一定的作用域,离开作用域,空间就会被释放,所以更新速度快,生命周期短
      • 堆区:存放程序中的数组和对象。凡是new出来的都存在堆里,如果数据消失,实体不会马上释放的
        • 就像男女朋友确认关系后,所有人都知道了。某天掰了,他们俩没有明确关系了,但是双方可能都不能马上找到新朋友,要被另外的单身份子收割,需要一定的时间
    • 一个程序: 如: 这个程序启动要 256m
      • 先有一个虚拟内存地址 + 物理内存地址
      • 虚拟内存地址: 记录物理内存中存储了哪些数据,在什么地方
    • 1、典型案例:JVM(java虚拟机)
      • 包含程序计数器,java虚拟机栈,本地方法栈,方法区,堆内存
        • 1)程序计数器:记录程序执行字节码的行号指示器
        • 2) java虚拟机栈:java方法执行时的内存模型
        • 3)方法区:共享内存区域,存储已被虚拟机加载的数据
        • 4)堆区:
          • 堆内存:
            在这里插入图片描述

            • 划分为新生代,老年代,永久代(元空间)
              • 1)新生代New:昙花一现,朝生夕死的对象( 比如你写的代码的方法里面的变量)
                • 新生代又分为:Eden,Surivivor1,Surivivor2
                  • Eden:存放jvm刚分配的对象
                  • Surivivor:两个空间一样大,Eden中未被GC的对象,经过copy算法,会在这两个区间来回copy,默认拷贝超过15次,就被移入Tenured年老代
              • 2)老年代Teunred:大对象or多次被GC后还在的对象(顽固分子)
              • 3)永久代Perm(元空间):类信息,常量,静态变量等
            • 堆内存的空间要经过不断分配和回收,才能得到高效的利用,那哪些会被回收,什么时候回收,怎么回收呢?
    • 2、回收(GC)
      • YGC(minor GC),针对新生代(young generation)得den区进行资源回收
      • FGC(major GC),处理的区域同时包括新生代和年老代
      • 不管是YGC还是FGC,都会造成一定程度的卡顿,及时采用新的垃圾回收算法,也只能减少卡顿时间,不能完全消除卡顿(比如删大文件,就会卡一下)
      • FGC通常比较慢,少则几百毫秒,多则几秒,如果频繁了,会导致性能变差
      • YGC一般几十到上百毫秒,如果耗时达到秒级,频繁,还会导致性能变差
      • 性能测试中,对gc是要多关注
        • 如果新生代资源分配过多,那么老年代这变就要少,老年代的空间,我可能就要经常的进行FGC, FGC频率高了,那么累计的gc的时间就长,导致性能比较差
        • 如果新生代分配的资源少了,那么老年代就分配多些,我的新生代的资源回收频率YGC就要高, 那么累计的ygc的时间也可能长,我的性能也可能较差
        • 那么两者之间有完美的比率吗?
          • 是没有的,比如写个查找,有人用递归,有人用循环,这样分配的内存是不一样的。只能边调试边测试(改堆栈的配置)
      • 哪些会被回收?
        • 1、是否已死:引用计数法(被引用的计数等于0,回收),可达性算法(没有引用链,回收)
        • 2、垃圾回收法
      • 什么时候回收?
        • 分配空间不足(注意:不是内存空间不足),才会执行回收
        • 定时回收
      • 怎么回收?
        • 垃圾回收算法:新生代-复制算法(清理eden,将存活的复制到survivor)
        • 老年代-标记整理算法(先标记,再整理,就像电脑删数据,先点击删除丢到回收站,然后在回收站那边再整理下进行删除)
      • 内存资源回收
        • 刚刚我们讲到了资源回收,只是在讲的时候才讲,其他时候没有,因为 本地方法栈,程序计数器,虚拟机栈 ,这些是不需要进行垃圾回收的
        • java的内存回收机制,内存空间中垃圾回收的工作由垃圾回收器(garbage collector)完成的,它的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,name称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可用回收其占据的空间,用于再分配
    • 3、常见问题
      • 1、内存溢出
        • 内存不够用,程序在申请内存时,申请不到足够的内存
        • 程序启动要256m,它内存溢出是指的溢出它本身的内存,而不是整个内存(比如你机器有8g,还剩4g,但还是内存溢出了,这是正常的,因为它溢出是指溢出自己本身的256m,而不是8g)
        • java.lang.StackOverFlowError栈溢出(线程请求的栈深度大于虚拟机运行时的最大深度)
        • 内存溢出在错误日志会出现,后续我们可以通过jmap,arthas工具进行查看和分析
      • 2、内存泄露
        • 内存的资源不及时释放,一直占用,导致可用的内存资源越来越少。
        • 内存泄露一定会导致内存溢出
    4、内存相关参数
参数含义
-Xms初始堆大小
-Xmx最大堆空间
-Xmn设置新生代大小
-XX:SurivivorRatio新生代eden空间,from空间,to空间的比例关系(8:1:1)
-XX:PermSize方法区初始大小
-XX:MaxPermSize方法区最大值
-XX:metaspaceSize元空间GC阈值
-XX:MaxMetaspaceSize最大元空间大小
-Xss栈大小
-XX:MaxDirectMemorySize直接内存大小,默认为最大堆空间

三、内存分析

1、查看内存
  • 1)free:free -h

在这里插入图片描述

  • Mem:物理内存

    • total(合计)、used(已被使用)、free(未被使用)、shared(共享)、buff/cache(缓冲区/缓存)、available(新进程可分配)
      • buff:对原始磁盘块(操作系统与磁盘交流的最小单位)的临时存储
      • cache:从磁盘读取文件的页缓存
      • availabe=free(未被使用)+可回收的
  • swap:交换分区

    • 一种虚拟内存,由磁盘虚拟化而来,存在于内存和磁盘之间,因为磁盘和内存之间存在差异
  • 2)top:Ee

在这里插入图片描述

  • VIRT:虚拟内存使用量 VIRT=SWAP+RES

  • RES:物理内存使用量+未换出的虚拟内存大小 RES=CODE+DATA

  • SHR:共享内存的使用量

  • SWAP:虚拟内存中被换出的大小

  • CODE:代码占用的物理内存大小

  • DATA:代码之外的部分占用的物理内存大小

  • %MEM:使用的物理内存占总内存的比率

  • 2、内存分析工具
    • jmap

    在这里插入图片描述

    • 命令:jmap [options] pid
    • options
      • -dump :生成java堆栈的快照信息
      • -heap :显示java堆详细信息,使用哪种回收机制,参数配置,分代情况
      • -histo :显示堆中对象统计信息,包括类,实例数量
      • jmap -F -dump:format=b,file=文件名.bin 进程id ===执行时间较长,需要等待结束
      • jmap -F -dump:live,format=b,file=xxx .bin pid
  • 3、确定oom问题
    • 方法1、使用jmap进行定位
      • 看请求的响应信息, 一般的情况下,出现内存溢出问题,在响应信息中都会有所体现nested exception is java.lang.OutOfMemoryError: Java heap space
        在这里插入图片描述

      • 有些项目,在log日志中,会有体现(不一定有)

      • 我们看系统的内存

        • 内存并没有被完全消耗掉
        • 定位这个问题:
          • 生成内存溢出堆栈文件
            • 获得进程id ps -ef |grep java jps
            • jmap -dump:live,format=b,file=heap_xxxtest_20210811002.hprof 2419
            • 运行完毕后它会生成hprof结尾的二进制文件(但是这个文件我们 tail -f 看是会乱码的)
              在这里插入图片描述
    • 方法2、使用arthas进行定位
      • 介绍
        • arthas是阿里开源的java诊断工具,实现了jvm自带的几乎所有诊断功能,深受jvm分析人员喜爱
      • 安装
        • curl -O https://arthas.aliyun.com/arthas-boot.jar
      • 工具上手:java -jar arthas-boot.jar
      • 如果需要获取帮助:java -jar arthas-boot.jar --help在这里插入图片描述
      • 内存泄露抓包
        • dashboard:显示当前系统实时数据的面板
        • heapdump:生成hprof文件(不建议把这个文件放桌面,建议新建个文件夹把它丢进去,因为mat打开后会生成非常多分析文件,看着很乱)
          在这里插入图片描述
  • 4、使用MAT进行分析

在这里插入图片描述

  • 解压MemoryAnalyer工具
    • 打开工具,open heap dump
    • 点击 histogram
      • histogram:列出内存中的对象,对象的个数及大小
        • class name 类名,出问题的类:com.xxx.xxx
        • objects(类的对象数量,对象被创建的次数)
        • shallow(浅)heap对象内存消耗大小,不包括其他对象的引用
        • retailed(保留)heap,被GC能回收的内存的总和
    • 没有java基础的同学, hprof文件给开发去定位, 有基础的同学,mat工具自己来分析
      在这里插入图片描述
  • 5、JVM分析
    • 输出GC日志
      • jvm的启动参数中加入-XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCtimestamps -XX:+PrintGCApplicationStopedTime (包含IO操作,所以生产环境一般不添加,测试环境也只有定位问题才开启,否则损耗性能)
        启动后输出:GC概要信息、详细信息、gc时间、gc造成的应用暂停时间
    • 常用分析工具
      • 1、 jcosole
        • jdk自带的内存分析工具,有图形界面(windows才有,linux没有),可以查看jvm内存信息,线程信息,类加载信息,MBean信息
        • jconsole.sh pid
      • 2、 jstat
        • jdk自带的分析GC工具,参数很多
        • jstat -gcutil pid 10000 间隔1w毫秒显示一次gc信息
          在这里插入图片描述
          • 参数说明:
            • S0:新生代survivor0区
            • S1:新生代survivor1区
            • E:新生代eden区
            • O:年老代
            • M:方法区回收比例
            • CSS:类空间回收比例
            • YGC:minor gc次数
            • YGCT:minor gc耗费的时间
            • FGC:full gc的次数
            • FGCT:full gc的耗时
            • GCT:gc总耗时
    • 总结一下:

      • cpu相关问题,应用服务器中高频率出现

      • 内存: 工作中经常遇到,比较难

      • 网络:见的多,但是不是最难,只是因为大家网络知识跟不上

      • 磁盘问题: 相对来说问题是最少, 一般集中在文件服务器\数据服务器

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值