• 七、性能测试之内存分析


    一、内存知识

    内存(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相关问题,应用服务器中高频率出现

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

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

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

  • 相关阅读:
    男女之间,动了真情却无法在一起,只需记住这三句话
    2023年高教社杯数学建模国赛 赛题浅析
    spring框架(SSM)
    时序数据库的关键技术点总结
    Node.js 入门教程 18 package.json 指南
    Eclipse配置Tomcat教程+创建第一个Java Web程序
    免费IP代理靠谱吗?靠谱的IP代理有哪些?
    C&C++结构实训(国防科大)
    Jenkins-Android源码编译【架构设计】(适用鸿蒙/自动化/多产品/持续迭代)
    Java中的线程
  • 原文地址:https://blog.csdn.net/qq_36792120/article/details/132613387