精华内容
下载资源
问答
  • Linux系统命令与CPU、硬盘、内存、网络状态监控
    千次阅读 多人点赞
    2021-10-07 16:12:23

    1. Linux常用工具命令:

    Linux系统中需要关注的指标包括CPU、硬盘、内存、网络状态这四个模块。

    常用于“监控”的Linux系统命令包括:

    请添加图片描述

    常用于“性能测试”的Linux系统命令包括:

    请添加图片描述

    常用于“优化”的Linux系统命令包括:

    请添加图片描述

    2. 基础命令和工具:

    2.1 uptime:机器启动时间+负载

    root@virtual-machine:/# uptime
     16:19:47 up 4 days,  5:30,  2 users,  load average: 1.30, 1.34, 1.34
    

    通常用于在线上应急或者技术攻关中,确定操作系统的重启时间。

    2.2 ulimit:用户资源

    Linux系统对每个登录用户都限制其最大进程数打开的最大文件句柄数。可根据实际的需求进行设置,使用 ulimit -a 来显示当前系统对用户的各项使用资源的限制。

    root@virtual-machine:/# ulimit -a
    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    scheduling priority             (-e) 0
    file size               (blocks, -f) unlimited
    pending signals                 (-i) 15394
    max locked memory       (kbytes, -l) 65536
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 1024
    pipe size            (512 bytes, -p) 8
    POSIX message queues     (bytes, -q) 819200
    real-time priority              (-r) 0
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) 15394
    virtual memory          (kbytes, -v) unlimited
    file locks                      (-x) unlimited
    

    2.3 curl http:HTTP返回结果

    curl命令用于查看HTTP调用返回的结果是否符合预期。

    curl -i "http://www.sina.com" # 打印请求响应头信息
    curl -I "http://www.sina.com" # 仅返回http头
    curl -v "http://www.sina.com" # 打印更多的调试信息
    curl -verbose "http://www.sina.com" # 打印更多的调试信息
    curl -d 'abc=def' "http://www.sina.com" # 使⽤post⽅法提交http请求
    curl -sw '%{http_code}' "http://www.sina.com" # 打印http响应码
    

    例如:

    root@virtual-machine:/# curl -I www.baidu.com
    HTTP/1.1 200 OK
    Accept-Ranges: bytes
    Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
    Connection: keep-alive
    Content-Length: 277
    Content-Type: text/html
    Date: Thu, 07 Oct 2021 08:28:21 GMT
    Etag: "575e1f72-115"
    Last-Modified: Mon, 13 Jun 2016 02:50:26 GMT
    Pragma: no-cache
    Server: bfe/1.0.8.18
    

    3. 关于进程的命令:

    3.1 ps:进程信息

    ps -ef 查看系统内所有进程,使用 grep进行过滤:

     root@virtual-machine:/# ps -ef | grep redis
    xuesong     6573    1379  0 10月03 ?      00:10:52 ./redis-server 127.0.0.1:6379
    root      309595  308064  0 16:33 pts/4    00:00:00 grep --color=auto redis
    

    使用 grep -v 反向过滤,忽略某些不想看到的内容:

    root@virtual-machine:/# ps -ef | grep redis | grep -v grep
    xuesong     6573    1379  0 10月03 ?      00:10:52 ./redis-server 127.0.0.1:6379
    

    ps命令中各列的含义:

    UID、PID、PPID、C、STIME、TTY、TIME、CMD
    在这里插入图片描述

    3.2 top:进程CPU内存信息

    top命令用于查看活动进程的CPU内存信息,能够实时显示系统中各个进程的资源占用情况,可以按照CPU、内存的使用情况和执行时间对进程进行排序。

    top - 17:04:13 up 4 days,  5:58,  2 users,  load average: 1.17, 1.33, 1.39
    任务: 360 total,   3 running, 356 sleeping,   1 stopped,   0 zombie
    %Cpu(s): 40.3 us, 11.2 sy,  0.0 ni, 48.2 id,  0.0 wa,  0.0 hi,  0.3 si,  0.0 st
    MiB Mem :   3906.4 total,    122.4 free,   3107.5 used,    676.5 buff/cache
    MiB Swap:    923.3 total,      0.0 free,    923.3 used.    505.3 avail Mem 
    
     进程号 USER      PR  NI    VIRT    RES    SHR    %CPU  %MEM     TIME+ COMMAND                                                              
     303054 root      20   0   78648  21620    176 R  99.7   0.5 167:38.28 gdb                                                                  
     307836 root      20   0       0      0      0 I   0.7   0.0   0:03.51 kworker/1:4-events                                                   
         10 root      20   0       0      0      0 S   0.3   0.0   0:13.30 ksoftirqd/0                                                          
       6573 xuesong   20   0   60588   1552    948 S   0.3   0.0  10:53.75 redis-server                                                         
       7715 root      20   0   84696    276      0 S   0.3   0.0   2:32.02 file_server                                                          
       7763 root      20   0   85108    892    556 S   0.3   0.0   3:34.93 http_msg_server                                                      
       7785 root      20   0  644128  44592    896 S   0.3   1.1  28:36.37 db_proxy_server                                                      
      45195 root      20   0  972708   4896      0 S   0.3   0.1  10:03.11 containerd                                                           
     245808 xuesong   20   0 2452252  34200  10200 S   0.3   0.9   0:28.57 WebExtensions                                                        
     309588 root      20   0       0      0      0 I   0.3   0.0   0:00.71 kworker/0:2-events                                                   
     309875 root      20   0   20792   3896   3128 R   0.3   0.1   0:01.75 top                                                                  
          1 root      20   0  171580   7808   3704 S   0.0   0.2   1:29.20 systemd                                                              
          2 root      20   0       0      0      0 S   0.0   0.0   0:00.58 kthreadd                                                             
          3 root       0 -20       0      0      0 I   0.0   0.0   0:00.01 rcu_gp                                                               
          4 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 rcu_par_gp      
    

    top命令参数详解参考:
    https://blog.csdn.net/yjclsx/article/details/81508455

    3.3 pidstat:进程资源:

    pidstat 用于监控全部或指定进程占用系统资源的情况,包括CPU、内存、磁盘I/O、线程切换、线程数等数据。

    命令参数:

    -u:	查看cpu相关的性能指标
    -r:	查看内存使用信息
    -d:	查看磁盘I/O统计数据
    -p: 指明进程号
    

    例如:

    //第一步先使用ps命令查看redis的进程号:
    root@virtual-machine:/home/xuesong# ps -ef | grep redis | grep -v grep
    xuesong     6573    1379  0 10月03 ?      00:10:54 ./redis-server 127.0.0.1:6379
    
    //第二步根据进程号6573查看进程的CPU、内存、磁盘的占用信息:
    root@virtual-machine:/# pidstat -urd -p 6573
    Linux 5.8.0-53-generic (xuesong-virtual-machine) 	2021年10月07日 	_x86_64_	(2 CPU)
    
    17时12分48秒   UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
    17时12分48秒  1000      6573    0.08    0.10    0.00    0.05    0.18     0  redis-server
    
    17时12分48秒   UID       PID  minflt/s  majflt/s     VSZ     RSS   %MEM  Command
    17时12分48秒  1000      6573     11.19      0.01   60588    1512   0.04  redis-server
    
    17时12分48秒   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
    17时12分48秒  1000      6573      1.56      0.00      0.00     447  redis-server
    

    CPU信息:

    %usr用户层正在使用的CPU百分比;
    %system系统层正在使用的CPU百分比;
    %CPU:进程整个占用的CPU时间百分比(%usr+%system)
    CPU:处理器个数
    %guest:运行虚拟机的CPU占用百分比。

    对于“计算密集型”的应用,正常情况下应该占用更多的 %usr;
    对于“IO密集型”的应用,正常情况下应该占用更多的 %system。

    所以,如果你的应用程序是IO密集型的,比如是处理网络IO的程序,但是却 %usr过高导致cpu过高,那么这个时候需要关注一下是不是应用程序写的有问题。

    内存信息:

    VSZ:该线程使用的虚拟内存(以KB为单位);
    RSS:该线程使用的物理内存(以KB为单位);
    %MEM:当前任务使用的有效内存的百分比;

    磁盘信息:
    kB_rd/s:每秒此进程从磁盘读取的字节数(kB/s);
    kB_wr/s:每秒此进程已经或者将要写入磁盘的字节数(kB/s)
    kB_ccwr/s:由任务取消的写入磁盘的字节数(kB/s)。

    4. 关于内存的命令:

    4.1 free:内存

    free命令用于显示系统内存的使用情况,包括总体内存、已经使用的内存;还可用于显示系统内核使用的缓冲区,包括缓冲(buffer)和缓存(cache)等。

    root@virtual-machine:/# free
                  总计         已用        空闲      共享    缓冲/缓存    可用
    内存:     4000136     3200592      126500       41356      673044      500884
    交换:      945464      945464           0
    

    5. 关于CPU使用情况的命令:

    5.1 vmstat:

    vmstat命令用于显示关于内核线程、虚拟内存、磁盘I/O、陷阱和CPU占用率的统计信息。

    root@virtual-machine:/# vmstat
    procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
     r  b 交换 空闲 缓冲 缓存   si   so    bi    bo   in   cs us sy id wa st
     1  0 945464 122688  13800 659896    1    3   133    65    5   20  3  2 95  0  0
    

    cs表示线程环境的切换次数,此数据如果太大时表明线程的同步机制有问题;(图中cs值为20)
    si和so较大时,说明系统频繁使用交换区,应该查看操作系统的内存是否够用

    6. 关于磁盘I/O的监控命令:

    6.1 iostat:IO状态

    iostat命令用于监控CPU占用率、平均负载值和I/O读写速度等。

    root@virtual-machine:/# iostat
    Linux 5.8.0-53-generic (virtual-machine) 	2021年10月07日 	_x86_64_	(2 CPU)
    
    avg-cpu:  %user   %nice %system %iowait  %steal   %idle
               3.30    0.09    1.81    0.04    0.00   94.75
    
    Device             tps    kB_read/s    kB_wrtn/s    kB_dscd/s    kB_read    kB_wrtn    kB_dscd
    loop0             0.02         0.55         0.00         0.00     204205          0          0
    loop1             0.01         0.03         0.00         0.00      10397          0          0
    loop10            0.00         0.01         0.00         0.00       3264          0          0
    loop11            0.22        13.99         0.00         0.00    5177743          0          0
    loop12            0.00         0.01         0.00         0.00       4109          0          0
    loop13            0.00         0.05         0.00         0.00      17772          0          0
    loop14            0.00         0.01         0.00         0.00       3933          0          0
    loop15            0.00         0.01         0.00         0.00       3248          0          0
    loop16            0.00         0.01         0.00         0.00       2415          0          0
    loop17            0.00         0.00         0.00         0.00       1203          0          0
    loop18            0.00         0.00         0.00         0.00         14          0          0
    loop2             0.01         0.03         0.00         0.00      12149          0          0
    loop3             0.01         0.18         0.00         0.00      66360          0          0
    loop4             0.03         0.62         0.00         0.00     227688          0          0
    loop5             0.00         0.01         0.00         0.00       3668          0          0
    loop6             0.04         0.35         0.00         0.00     129844          0          0
    loop7             0.00         0.15         0.00         0.00      55638          0          0
    loop8             0.02         0.04         0.00         0.00      13506          0          0
    loop9             0.03         1.21         0.00         0.00     448455          0          0
    sda               7.09       247.27       129.15         0.00   91507211   47796127          0
    scd0              0.00         0.00         0.00         0.00          4          0          0
    
    
    //cpu的统计信息:(如果是多cpu系统,显示的所有cpu的平均统计信息)
    
    %user:		⽤户进程消耗cpu的⽐例
    %nice:		⽤户进程优先级调整消耗的cpu⽐例
    %sys:		系统内核消耗的cpu⽐例
    %iowait:	等待磁盘io所消耗的cpu⽐例
    %idle:		闲置cpu的⽐例(不包括等待磁盘I/O)
    
    
    //磁盘的统计参数:
    
    tps:		该设备每秒的传输次数(Indicate the number of transfers per second that were issued to the device.)。"⼀次传输"意思是"⼀次I/O请求"。多个逻辑请求可能会被合并为"⼀次I/O请求""⼀次传输"请求的⼤⼩是未知的。
    kB_read/s:	每秒从设备(drive expressed)读取的数据量;
    kB_wrtn/s:	每秒向设备(drive expressed)写⼊的数据量;
    kB_read:	读取的总数据量;
    kB_wrtn:	写⼊的总数量数据量;这些单位都为Kilobytes
    
    

    5.2 df:硬盘使用情况:

    df命令用于查看文件系统的硬盘挂载点和空间使用情况。

    root@virtual-machine:/# df
    文件系统          1K-块     已用    可用 已用% 挂载点
    udev            1970520        0 1970520    0% /dev
    tmpfs            400016     3624  396392    1% /run
    /dev/sda2      19993200 14781992 4172568   78% /
    tmpfs           2000068        0 2000068    0% /dev/shm
    tmpfs              5120        4    5116    1% /run/lock
    tmpfs           2000068        0 2000068    0% /sys/fs/cgroup
    /dev/loop1       224256   224256       0  100% /snap/gnome-3-34-1804/66
    /dev/loop4       224256   224256       0  100% /snap/gnome-3-34-1804/72
    /dev/loop6        52224    52224       0  100% /snap/snap-store/547
    /dev/loop5        52352    52352       0  100% /snap/snap-store/518
    /dev/loop8        66688    66688       0  100% /snap/gtk-common-themes/1515
    /dev/sda1        523248     8036  515212    2% /boot/efi
    tmpfs            400012      120  399892    1% /run/user/1000
    /dev/loop10       56832    56832       0  100% /snap/core18/2074
    /dev/loop12       56832    56832       0  100% /snap/core18/2128
    /dev/loop16      101760   101760       0  100% /snap/core/11606
    /dev/loop9        33152    33152       0  100% /snap/snapd/12883
    /dev/loop0       213504   213504       0  100% /snap/code/74
    /dev/loop13       33152    33152       0  100% /snap/snapd/13170
    /dev/loop17      101760   101760       0  100% /snap/core/11743
    /dev/loop11         128      128       0  100% /snap/bare/5
    /dev/loop14      213504   213504       0  100% /snap/code/75
    /dev/loop15       66816    66816       0  100% /snap/gtk-common-themes/1519
    
    

    7. 查看网络信息和网络监控的命令:

    7.1 ifconfig:网卡信息

    ifconfig命令用于查看机器挂载的网卡情况。

    其中lo是本地回绕。

    root@virtual-machine:/# ifconfig
    docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
            inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
            inet6 fe80::42:29ff:fe65:1c1a  prefixlen 64  scopeid 0x20<link>
            ether 02:42:29:65:1c:1a  txqueuelen 0  (以太网)
            RX packets 0  bytes 0 (0.0 B)
            RX errors 0  dropped 0  overruns 0  frame 0
            TX packets 10  bytes 1104 (1.1 KB)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    
    ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
            inet 172.16.5X.139  netmask 255.255.255.0  broadcast 172.16.5X.255
            inet6 fe80::8798:75a4:862d:d083  prefixlen 64  scopeid 0x20<link>
            ether 00:0c:29:3b:d5:f0  txqueuelen 1000  (以太网)
            RX packets 2192604  bytes 2726506782 (2.7 GB)
            RX errors 0  dropped 0  overruns 0  frame 0
            TX packets 653905  bytes 83491630 (83.4 MB)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    
    lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
            inet 127.0.0.1  netmask 255.0.0.0
            inet6 ::1  prefixlen 128  scopeid 0x10<host>
            loop  txqueuelen 1000  (本地环回)
            RX packets 5533524  bytes 508807570 (508.8 MB)
            RX errors 0  dropped 0  overruns 0  frame 0
            TX packets 5533524  bytes 508807570 (508.8 MB)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    

    7.2 ping:检查网络连接

    ping命令是用检测网络故障的常用命令,可以用来测试一台主机到另外一台主机的网络是否连通。

    7.3 telnet:远程登录:

    telnet 是TCP/IP协议族的一员,是网络远程登录服务器的标准协议,帮助用户在本地计算机上连接远程主机。

    telnet与ssh的区别:
    本质区别:telnet是明文传输,ssh是加密传输;
    端口区别:telnet使用端口 23,ssh使用端口 22

    7.4 netstat:网络连接端口信息:

    netstat命令用于显示网络连接、端口信息等。

    主要能查看到的信息包括: ① 端口状态(TCP的11个状态)、② 某条连接上的本端和对端的IP/端口信息、③ 某条连接上的发送队列/接收队列中的缓存报文信息(Recv-Q/Send-Q)。

    常用参数:

    `-t`:	tcp
    `-u`:	udp
    `-l`:	listening	//监听态
    `-a`:	all			//所有的
    `-n`:	numeric		//不要显示主机名,而是显示IP地址
    `-c`:	continuous	//动态刷新,一秒一次
    `-p`:	program		//显示占用端口的主机程序名,如redis
    

    netstat -a:显示所有:
    注意其中显示的是localhost主机名,使用-n则可以转为IP地址127.0.0.1

    root@virtual-machine:/# netstat -a
    激活Internet连接 (服务器和已建立连接的)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State      
    tcp        0      0 0.0.0.0:8700            0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:microsoft-ds    0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN     
    tcp        0      0 localhost:33060         0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:8100            0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:10600           0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:8200            0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:8008            0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:netbios-ssn     0.0.0.0:*               LISTEN     
    tcp        0      0 localhost:6379          0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:http            0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:8400            0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:http-alt        0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:tproxy          0.0.0.0:*               LISTEN     
    tcp        0      0 localhost:domain        0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:ssh             0.0.0.0:*               LISTEN     
    tcp        0      0 localhost:ipp           0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:8600            0.0.0.0:*               LISTEN     
    tcp        0      0 localhost:8601          0.0.0.0:*               LISTEN     
    tcp        0      0 localhost:55934         localhost:mysql         ESTABLISHED
    tcp        0      0 localhost:36074         localhost:6379          ESTABLISHED
    tcp        0      0 localhost:36072         localhost:6379          ESTABLISHED
    tcp        0      0 localhost:55928         localhost:mysql         ESTABLISHED
    tcp        0      0 localhost:10600         localhost:59514         ESTABLISHED
    tcp        0      0 localhost:59536         localhost:10600         ESTABLISHED
    tcp        0      0 localhost:36060         localhost:6379          ESTABLISHED
    
    

    使用方法:

    1. 根据进程ID或应用程序名称查找端口:
      注意:此时必须使用参数 -p,否则不显示进程ID或者程序名,也就无法使用grep进行过滤了
    root@xuesong-virtual-machine:/# netstat -anp | grep redis
    tcp        0      0 127.0.0.1:6379          0.0.0.0:*               LISTEN      6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36062         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36066         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36060         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36058         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36076         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36074         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36072         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36064         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36068         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36070         ESTABLISHED 6573/./redis-server 
    
    
    //或者:
    root@xuesong-virtual-machine:/# netstat -anp | grep 6573
    tcp        0      0 127.0.0.1:6379          0.0.0.0:*               LISTEN      6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36062         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36066         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36060         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36058         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36076         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36074         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36072         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36064         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36068         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36070         ESTABLISHED 6573/./redis-server 
    
    1. 想要看看某个端口被哪个进程所占用,或者想要查看某个端口的当前TCP状态,使用netstat按照端口号进行过滤:
    root@xuesong-virtual-machine:/# netstat -antp | grep 6379
    tcp        0      0 127.0.0.1:6379          0.0.0.0:*               LISTEN      6573/./redis-server 
    tcp        0      0 127.0.0.1:36074         127.0.0.1:6379          ESTABLISHED 7785/./db_proxy_ser 
    tcp        0      0 127.0.0.1:36072         127.0.0.1:6379          ESTABLISHED 7785/./db_proxy_ser 
    tcp        0      0 127.0.0.1:36060         127.0.0.1:6379          ESTABLISHED 7785/./db_proxy_ser 
    tcp        0      0 127.0.0.1:36062         127.0.0.1:6379          ESTABLISHED 7785/./db_proxy_ser 
    tcp        0      0 127.0.0.1:36068         127.0.0.1:6379          ESTABLISHED 7785/./db_proxy_ser 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36062         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36066         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36060         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:36066         127.0.0.1:6379          ESTABLISHED 7785/./db_proxy_ser 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36058         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36076         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:36070         127.0.0.1:6379          ESTABLISHED 7785/./db_proxy_ser 
    tcp        0      0 127.0.0.1:36064         127.0.0.1:6379          ESTABLISHED 7785/./db_proxy_ser 
    tcp        0      0 127.0.0.1:36058         127.0.0.1:6379          ESTABLISHED 7785/./db_proxy_ser 
    tcp        0      0 127.0.0.1:36076         127.0.0.1:6379          ESTABLISHED 7785/./db_proxy_ser 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36074         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36072         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36064         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36068         ESTABLISHED 6573/./redis-server 
    tcp        0      0 127.0.0.1:6379          127.0.0.1:36070         ESTABLISHED 6573/./redis-server 
    

    -p是一个很有用的参数,可以查看到对应某个端口的应用程序名称及进程ID。

    7.5 tcpdump:网络抓包

    tcpdump是网络状况分析和跟踪工具,是可以用来抓包的使用命令。 使用前需要对TCP/IP有所熟悉,因为过滤使用的信息是TCP/IP格式。

    常用参数:

    `-i`:	interface		指定网卡
    `-n`:	Don't convert address to names	显示原始IP,不转换成主机名
    
    
    //原语运算:
    与:		`&&` 或者 `and`
    或:		`||` 或者 `or`
    非:		`!`  或者 `not`
    
    
    //例如:
    src or dst portrange 6000-8000 && tcp
    
    
    //限定词:
    //1. Type:
    host		:   IP地址
    port		:	端口
    portrange	:	设置端口范围
    
    //2. Dir:
    src
    dst
    src or dst
    src and dst
    
    //3. Proto:
    ethr
    feddi
    tr
    wlan
    ip
    ip6
    arp
    tcp
    udp
    
    
    //文件操作:
    -w		:		输出结果至文件
    -C		:		限制输出文件的大小,超出后,以后缀1等数字的方式递增,单位是 1,000,000 字节
    -W		:		指定输出文件的最大数量,超出后重写第一个文件
    -r		:		读取一个抓包文件
    -V		:		将待读取的多个文件名写入一个文件中,通过读取该文件同时读取多个文件
    

    练习:

    root@virtual-machine:/# tcpdump -n -i ens33 && tcp && src host 172.16.52.1 && src port 62557
    21:04:54.605637 IP 172.16.52.1.62557 > 172.16.52.129.22: Flags [P.], seq 397:433, ack 645856, win 2048, options [nop,nop,TS val 287647214 ecr 3287211780], length 36
    21:04:54.605740 IP 172.16.52.129.22 > 172.16.52.1.62557: Flags [P.], seq 646212:646568, ack 433, win 501, options [nop,nop,TS val 3287211781 ecr 287647214], length 356
    ......
    
    3637 packets captured
    3683 packets received by filter
    46 packets dropped by kernel
    

    9. Linux系统的高级工具:

    9.1 pstack:查看进程的调用栈

    pstack命令用来查看每个进程的调用栈,相当于是gdb的shell封装。使用方法是 pstack + 进程ID

    [root@VM_0_3_centos ~]# pstack 25901
    Thread 2 (Thread 0x7f950fd02700 (LWP 25902)):
    #0 0x00007f9514a1ff73 in select () from /usr/lib64/libc.so.6
    #1 0x00007f95136b95a5 in apr_sleep () from /usr/lib64/libapr-1.so.0
    #2 0x00007f9514645f19 in ?? ()
    #3 0x0000000001186ec0 in ?? ()
    #4 0x0000000001186ec0 in ?? ()
    #5 0x00007f950fd01d60 in ?? ()
    #6 0x00007f95146a3d9e in ?? ()
    #7 0x0000000001184da0 in ?? ()
    #8 0x0000000000000000 in ?? ()
    Thread 1 (Thread 0x7f9515b348c0 (LWP 25901)):
    #0 0x00007f9514a29483 in epoll_wait () from /usr/lib64/libc.so.6
    #1 0x000000000048604f in CEventDispatch::StartDispatch (this=0x1195600,
    wait_timeout=100) at /root/im/0voice_im/server/src/base/EventDispatch.cpp:365
    #2 0x0000000000479ad2 in netlib_eventloop (wait_timeout=100) at
    /root/im/0voice_im/server/src/base/netlib.cpp:160
    #3 0x000000000046f598 in main (argc=1, argv=0x7ffc278e13a8) at
    /root/im/0voice_im/server/src/login_server/login_server.cpp:132
    

    9.2 strace:查看进程使用的系统调用

    strace是Linux系统下的一款程序调试工具,用来监控一个应用程序所使用的系统调用,通过它可以跟踪系统调用,让你熟悉一个Linux程序在背后是怎么工作的。

    使用举例:

    root@xuesong-virtual-machine:/# ps -ef | grep redis
    xuesong     6573    1379  0 10月03 ?      00:11:06 ./redis-server 127.0.0.1:6379
    root      317750  313626  0 21:20 pts/4    00:00:00 grep --color=auto redis
    
    root@xuesong-virtual-machine:/# strace -p 6573
    strace: Process 6573 attached
    read(3, 0x7fff9710b807, 1)              = -1 EAGAIN (资源暂时不可用)
    epoll_wait(5, [], 10128, 41)            = 0
    getpid()                                = 6573
    openat(AT_FDCWD, "/proc/6573/stat", O_RDONLY) = 17
    read(17, "6573 (redis-server) R 1379 6573 "..., 4096) = 345
    close(17)                               = 0
    read(3, 0x7fff9710b807, 1)              = -1 EAGAIN (资源暂时不可用)
    epoll_wait(5, [], 10128, 100)           = 0
    getpid()                                = 6573
    openat(AT_FDCWD, "/proc/6573/stat", O_RDONLY) = 17
    read(17, "6573 (redis-server) R 1379 6573 "..., 4096) = 345
    close(17)                               = 0
    read(3, 0x7fff9710b807, 1)              = -1 EAGAIN (资源暂时不可用)
    epoll_wait(5, [], 10128, 99)            = 0
    getpid()                                = 6573
    ......
    

    9.3 proc:查看运行时的系统内核参数:

    Linux系统内核提供了通过 /proc 文件系统查看运行时内核内部数据结构的能力,也可以改鬓内核参数设置。

    例如:

    //查看CPU信息:
    cat /proc/cpuinfo
    
    //查看内存信息:
    cat /proc/meminfo
    
    //查看磁盘映射信息:
    cat /proc/mounts
    

    10. 日志监控工具:

    10.1 tail:

    11. 性能监控工具:

    11.1 pmap:

    11.2 nmon:

    11.3 glances:

    12. 性能测试:

    13. 性能测试sysbench:

    14. iperf测试服务器带宽:

    15. Linux性能分析:

    15.1 CPU性能原理篇:

    在进行服务端性能测试时,需要观察系统对CPU的使用情况,以此作为衡量整个系统性能的重要指标,对于Linux CPU主要的关注点在 利用率、运行队列、负载、上下文切换 等,因此了解这些指标的含义和常用的监控方法对性能测试有很大的帮助。

    15.1.1 CPU使用率:

    Linux CPU使⽤率主要是从以下⼏个维度进⾏统计:
    %usr:普通进程在⽤户模下下执⾏的时间;
    %sys:进程在内核模式下的执⾏时间;
    %nice:被提⾼优先级的进程在⽤户模式下的执⾏时间;
    %idle:空闲时间。
    %iowait:等待I/O完成的时间。
    %irp:处理硬中断请求花费的时间。
    %soft:处理软中断请求花费的时间。
    %steal:是衡量虚拟机CPU的指标,是指分配给本虚拟机的时间⽚被同⼀宿主机别的虚拟机占⽤,⼀
    般%steal值较⾼时,说明宿主机的资源使⽤已达到瓶颈;
    ⼀般情况下,CPU⼤部分的时间⽚都是消耗在⽤户态和内核态上。
    sys和user间的⽐例是相互影响的,%sys⽐例⾼意味着被测服务频繁的进⾏⽤户态和系统态之间的切换,
    会带来⼀定的CPU开销,这样分配处理业务的时间⽚就会较少,造成系统性能的下降。对于IO密集型系
    统,⽆论是⽹络IO还是磁盘IO,⼀般都会产⽣⼤量的中断,从⽽导致%sys相对升⾼,其中磁盘IO密集型系
    统,对磁盘的读写需要占⽤⼤量的CPU,会导致%iowait的值⼀定⽐例的升⾼,所以当出现%iowait较⾼
    时,需排查是否存在⼤量的不合理的⽇志操作,或者频繁的数据载⼊等情况;
    CPU利⽤的详细情况可以通过top,vmstat命令进⾏查看

    15.1.2 运行队列:

    当Linux内核要寻找⼀个新的进程在CPU上运⾏时,必须只考虑处于可运⾏状态的进程,(即在
    TASK_RUNNING状态的进程),因为扫描整个进程链表是相当低效的,所以引⼊了可运⾏状态进程的双
    向循环链表,也叫运⾏队列(runqueue)。每个CPU或者说每个核都会维持⼀个运⾏队列,队列中存放
    running和runnable两种状态的进程,CPU会不断的调度队列中的进程运⾏,因此队列中的进程数越多,
    每个进程分别到的时间⽚就越少,程序的时间也就越⻓,同时CPU会不断的处于运⾏状态,性能开销较
    ⼤。可以通过观察⼀定时间内运⾏队列中的进程数量来判断CPU是否达到的瓶颈,这就有了负载的概念;

    15.1.3 负载:

    CPU的负载(Load),是指在⼀段时间内 CPU正在处理以及等待 CPU处理的进程数之和的统计信息,也
    就是 CPU运⾏队列⻓度的统计信息。平均负载(Load Average)是指上⼀段时间内同时处于运⾏队列的
    平均进程数,Load Average是反应系统压⼒的重要指标,Load Average越⾼说明对于CPU资源的竞争越
    激烈,CPU资源⽐较短缺。对于资源的申请和维护其实也是需要很⼤的成本,所以在这种⾼Average
    Load的情况下CPU资源的⻓期“热竞争”也是对于硬件的⼀种损害。
    举个例⼦,把CPU处理进程的过程看成⽕⻋站售票:
    假如售票处有10个窗⼝,买票的⼈只有1个,毫⽆疑问,这个乘客随便找个窗⼝买完票就可以拍屁股⾛
    ⼈了,⽽且还有9个售票⼈员可以喝茶看报纸,这时的平均负载是0.1,售票运作毫⽆压⼒;
    如果买票的⼈有10个,那也还好,⼀个窗⼝⼀个⼈,所有窗⼝的售票⼈员都在⼯作,也可以快速的买到
    票,这时的平均负载是1,所有的售票窗⼝都在运作;
    如果有20个⼈在买票,这就要出现排队的情况,售票⼈员要不停的卖票,直到排队的都买到票,这时平
    均负载是2,所有售票窗⼝都在运作,⽽且出现了排队情况;
    如果是100,200⼈或者更多的⼈买票,这时售票⼈员就要不停的⼯作,为了不把售票⼈员累坏可能就要考
    虑增加窗⼝,也就是扩容了。⽽实际CPU处理进程时并不是像售票⼀样卖完⼀个⼈下⼀个⼈,CPU处理每
    个进程时是有固定的时间⽚,如果在时间⽚内这个任务没有处理完,就要挂起这个任务处理下⼀个,这是
    就会产⽣中断,⾼负载的情况下会不断产⽣进程间的调⽤,从⽽产⽣⼤量中断,造成系统的开销,所以在
    做性能测试时,要重点关注负载情况,来判读CPU是否达到了瓶颈,依据经验分析,单核CPU负载<2时,
    系统性能是良好的,当单核CPU负载>5时,那么就表明这个机器存在严重的性能问题。
    负载可以通过top,uptime、cat /proc/loadavg等命令查看1分钟,5分钟,15分钟的负载值:

    15.1.4 上下文切换:

    ⼀个标准的Linux内核可以⽀持运⾏50~50000个进程运⾏,对于普通的CPU,内核会调度和执⾏这些进
    程。每个进程都会分到CPU的时间⽚来运⾏,当⼀个进程⽤完时间⽚或者被更⾼优先级的进程抢占后,它
    会备份到CPU的运⾏队列中,同时其他进程在CPU上运⾏。这个进程切换的过程被称作上下⽂切换。
    Linux系统具有两个不同级别的运⾏模式,内核态和⽤户态,其中内核态的运⾏级别要⼤于⽤户态,这个级
    别可以进⾏任何操作,⼀般内核运⾏与内核态,⽽应⽤程序是运⾏在⽤户态。当发⽣上下⽂切换时,通过
    系统调⽤,处于⽤户态的应⽤程序就会进⼊内核空间,待调⽤完成之后重新返回值⽤户态运⾏,因此上下
    ⽂切换存在系统开销,会⼀定程度上增加%sys的值。
    当⼀个进程在执⾏时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下⽂。
    当内核需要切换到另⼀个进程时,它需要保存当前进程的所有状态,即保存当前进程的上下⽂,以便在再
    次执⾏该进程时,能够必得到切换时的状态执⾏下去。在LINUX中,当前进程上下⽂均保存在进程的任务
    数据结构中。在发⽣中断时,内核就在被中断进程的上下⽂中,在内核态下执⾏中断服务例程。但同时会保
    留所有需要⽤到的资源,以便中断服务结束时能恢复被中断进程的执⾏。
    引起上下⽂切换的原因有哪些? 对于抢占式操作系统⽽⾔, ⼤体有⼏种:

    1. 当前任务的时间⽚⽤完之后,系统CPU正常调度下⼀个任务;
    2. 当前任务碰到IO阻塞,调度线程将挂起此任务,继续下⼀个任务;
    3. 多个任务抢占锁资源,当前任务没有抢到,被调度器挂起,继续下⼀个任务;
    4. ⽤户代码挂起当前任务,让出CPU时间;
    5. 硬件中断;
      vmstat中的cs为中断数量

    15.1.6 中断:

    中断是指CPU执⾏程序时,由于发⽣了某种随机的事件(外部或内部),引起CPU暂时中断正在运⾏的程
    序,转去执⾏⼀段特殊的服务程序(称为中断服务程序或中断处理程序),以处理该事件,该事件处理完后⼜
    返回被中断的程序继续执⾏。引⼊中断的原因有以下⼏点:

    1. 提⾼数据传输率;
    2. 避免了CPU不断检测外设状态的过程,提⾼了CPU的利⽤率。
    3. 实现对特殊事件的实时响应。如多任务系统操作系统中缺⻚中断、设备中断、各类异常、实时钟等
      中断根据中断源的不同可以分为硬中断和软中断:
      硬中断: 硬中断⼜称外部中断,是由硬件产⽣,如键盘,⿏标,打印机等。每个设备或设备集都有它
      ⾃⼰的中断请求(IRQ),基于IRQ,CPU可以将相应的请求分发到对应的硬件驱动上,处理中断的驱
      动是需要在CPU上运⾏的,因此中断产⽣时,CPU会中断当前正在运⾏的任务来处理中断,在多核的
      操作系统中,⼀个中断通常只能中断⼀颗CPU(核)。
      软中断: 软中断⼜称内部中断,由软件系统本身发给操作系统内核的中断信号。通常是由硬中断处理
      程序或进程调度程序对操作系统内核的中断,也就是我们常说的系统调⽤(System Call)。⼀般情况下
      软中断是处理I/O请求时发⽣订单,这些请求会调⽤内核中的处理I/O的程序,对于某些设备,I/O请求
      需要被⽴即处理,⽽磁盘I/O请求通常可以排队并且可以稍后处理。根据I/O模型的不同,进程或许会被
      挂起直到I/O完成,此时内核调度器就会选择另⼀个进程去运⾏。I/O可以在进程之间产⽣并且调度过程
      通常和磁盘I/O的⽅式是相同。在I/O密集型系统中可能会出现⼤量的中断请求,中断会产⽣中断上下
      ⽂,造成CPU开。
      可以通过top,vmstat等查看CPU的相关命令中监控中断情况

    15.1.7 负载与CPU利用率的关系:

    负载和CPU利⽤率都是衡量CPU性能的指标,那么CPU利⽤率⾼的话负载⼀定⾼吗?要回答这个问题⾸先
    要CPU资源在什么情况下会被消耗,CPU利⽤率其实是指为了处理进程抢占CPU时间⽚的⽐例,也就是只
    有处于运⾏状态的进程才会获得时间⽚,从⽽造成CPU利⽤率的上升,⽽所需CPU时间⽚的多少取决于进
    程中需要CPU运算逻辑的复杂程度。⽽负载是指运⾏队列中等待和运⾏的进程数的总和,在负载很⾼时,
    可能是出于等待运⾏的进程数较多,这些进⾏并不会占⽤过多的时间⽚,队列中进程消耗的CPU资源也有
    可能只是集中在某⼏个对CPU运算依赖较⾼的⼏个进程上。所以CPU利⽤率和负载之间并没有硬性的⽐例
    关联关系,衡量CPU性能时要同时关注两个性能指标,综合考虑。

    15.2 遇到CPU利用率高该如何排查:

    遇到CPU使⽤率⾼时,⾸先确认CPU是消耗在哪⼀块,如果是内核态占⽤CPU较⾼:

    1. %iowait ⾼,这时要重点关注磁盘IO的相关操作,是否存在不合理的写⽇志操作,数据库操作等;
    2. %soft或%cs ⾼,观察CPU负载是否较⾼、⽹卡流量是否较⼤,可不可以精简数据、代码在是否在多
      线程操作上存在不合适的中断操作等;
    3. %steal ⾼,这种情况⼀般发⽣在虚拟机上,这时要查看宿主机是否资源超限;
      如果是⽤户态较⾼,且没有达到预期的性能,说明应⽤程序需要优化。

    15.2.1 根据指标查找工具:

    15.2.2 如何迅速的分析CPU性能瓶颈:

    15.2.3 性能优化方法论:

    15.2.4 性能指标总结:

    关于CPU的经验指标,可以作为参考:

    1. 对于每一个CPU来说,运行队列不要超过2,例如,如果是双核CPU,就不要超过4;
    2. 如果CPU在满负荷运行,应该符合下列分布:
      %usr time: 65%~70%,如果高于此数值,可以考虑对应用程序进行优化;
      %system time:30%~35%,如果高于此数值,观察是否有过多的中断或者上下文切换;
      %idle:0%~5%
    3. 对于上下文切换,要结合CPU使用率来看,如果CPU使用满足上述分布,大量的上下文切换也是可以接受的。
    更多相关内容
  • 使用Windbg定位Windows C++程序内存泄露

    千次阅读 多人点赞 2021-11-12 20:50:26
    去年我们也遇到过类似的问题,很大概率是第三方安全软件导致的,第三方安全库注入到我们的进程,应该是注入的库有内存泄露,导致我们的进程出现问题。本文简单讲述一下如何使用Windbg来定位Windows程序内存...

    目录

    1、概述

    2、使用Windbg监测内存泄露的一般步骤

    3、详解整个操作过程

    3.1、gflags.exe和umdh.exe介绍

    3.2、启动cmd命令行

    3.3、设置pdb符号库路径

    3.4、调用gflags设置启用udmh.exe的堆栈跟踪

    3.5、第一次使用umdh抓取堆内存使用快照

    3.6、第二次使用umdh抓取堆内存使用快照

    3.7、比较两次堆内存使用快照,得出结论

    4、Windbg分析内存泄露的不足


           最近有个客户在使用我们的Windows软件时又遇到了内存泄露问题,软件在客户的机器环境上运行半个多小时后就会出现闪退崩溃。去年我们也遇到过类似的问题,很大概率是第三方安全软件导致的,第三方安全库注入到我们的进程中,应该是注入的库有内存泄露,导致我们的进程出现问题。本文简单讲述一下如何使用Windbg来定位Windows程序中的内存泄露问题。

    1、概述

           Windows平台上分析内存泄露的工具有很多,比如Rational PurifyBoundsChecker等。但这些工具已经好几年不再维护了,老的版本已不再兼容较新的Visual Studio了。即便能兼容,也是比较麻烦的,比如BoundsChecker就需要将头文件和库添加到工程中重新编译程序,大型软件中包含了多个模块,在不确定发生内存泄露的模块的情况下,在所有模块中都添加BoundsChecker的文件重新编译一下是不现实的。

           我们需要一个工具,在不需要重新编译程序的情况下,就能直接去分析程序是否存在内存泄露。之前一直在寻找这样的工具,一直没有找到合适的。使用过腾讯的tMemoryMonitor内存监测工具,但该工具并不好用,很多实际场景下的内存泄露都监测不到。

           最后只能使用Windbg去检测内存泄露。Windbg是微软提供的Windows下强大的调试工具,可以分析多种软件异常问题,在我们日常开发过程中会频繁地使用到该工具,但之前使用Windbg监测内存泄露问题还是第一次。目前我们已经使用Windbg定位了好几起内存泄露的问题,Windbg还是比较给力的。

    2、使用Windbg监测内存泄露的一般步骤

           在监测之前,需要安装新版本的Windbg,因为我们实际上是使用Windbg安装目录下的gflags.exeumdh.exe两个程序去完成内存监测的。

           新版本的Windbg已经合并到Windows SDK包中,要安装Windbg,则需要下载Windows SDK开发包,在安装时可以选择只安装Windbg,操作起来有点小麻烦。我已经将10.0版本的Windbg安装包上传至CSDN上,直接去下载安装即可,其链接为:https://download.csdn.net/download/chenlycly/34256522https://download.csdn.net/download/chenlycly/34256522https://download.csdn.net/download/chenlycly/34256522

            使用gflags.exe和umdh.exe监测内存泄露的一般步骤如下:

    1)右键以管理员权限启动cmd命令行窗口,用cd命令,切换到windbg的安装目录中,比如我的安装路径是:C:\Program Files\Windows Kits\10\Debuggers\x86。
    2)在cmd中输入:gflags /i XXX.exe +ust。
    3)在cmd中输入:umdh.exe -pn:XXX.exe -f:E:\log1.txt,等待命令执行完成。
    4)让软件运行一会,操作软件使之产生内存泄露。
    5)在cmd中输入:umdh.exe -pn:XXX.exe -f:E:\log2.txt,等待命令执行完成。
    6)在cmd中输入:umdh.exe E:\log1.txt E:\log2.txt -f:E:\result.txt,等待命令执行完成。
    7)查看result.txt文件中的堆内存使用变化的统计,定位问题。

    3、详解整个操作过程

    3.1、gflags.exe和umdh.exe介绍

            gflags.exe和umdh.exe是新版本Windbg安装路径下的程序。

            gflags.exe (Global Flags) ,全局标志编辑器 ,用来启用和禁用高级调试、诊断和故障排除功能。 它通常用于打开其他工具跟踪、计数和日志的指示器。可以通过命令行执行其相关的操作,也可以在gflags的UI界面上设置:

           umdh.exe(User-Mode Dump Heap),用户转储堆程序,用来追踪并分析进程的堆内存分配。 对于每个堆内存的分配,umdh都可以追踪其分配的大小、开销的大小、指向分配的指针和分配堆栈。 如果进程具有多个活动内存堆,umdh将追踪所有堆。 此类分析可以实时展示,也可以保存在日志文件中。该工具程序是没有UI界面,是通过cmd命令行执行相关操作的。

    3.2、启动cmd命令行

           一般我们需要右键以管理员权限运行cmd命令行窗口,特别是在Win10系统中。然后用cd命令,切换到windbg的安装目录中,比如我的安装路径是:C:\Program Files\Windows Kits\10\Debuggers\x86,以方便下面在命令行中直接启动该路径下的gflags.exe和umdh.exe两个程序。

    3.3、设置pdb符号库路径

           umdh最终会分析出使用堆内存的函数调用堆栈,为了方便查看到函数调用堆栈中的具体函数,可以设置pdb符号库文件路径。我们可以在命令行中设置如下的环境变量:

    set _NT_SYMBOL_PATH="D:\MySymbols;srv*C:\WINDOWS\Symbols*http://msdl.microsoft.com/download/symbols"

    环境变量的名称为_NT_SYMBOL_PATH,其值是包含pdb的路径。路径包含两部分,前半部分的D:\MySymbols是我们软件模块的符号库文件,后半部分则是微软系统库在线下载的符号库路径。

           如果不想设置微软系统库在线pdb路径,可以只设置软件自己模块的pdb文件。除了在命令行中添加环境变量,也可以在系统中添加环境变量。以win10系统为例,在系统搜索栏中搜索环境变量,找到编辑环境变量的入口,依次操作即可:

    3.4、调用gflags设置启用udmh.exe的堆栈跟踪

            继续在cmd命令行中输入:

    gflags /i XXX.exe +ust

    其中/i用来指定目标进程的名称,参数ust的含义是:Create user mode stack trace database。此命令用来给目标进程创建用户模式堆栈跟踪数据库,用于追踪堆内存的使用情况

    3.5、第一次使用umdh抓取堆内存使用快照

            继续在cmd命令行中输入:

    umdh.exe -pn:XXX.exe -f:E:\log1.txt

    其中-pn参数用来指定目标进程的进程名,-f参数用来指定存放抓取来的堆内存使用快照数据信息。此命令运行后,将第一次抓取的堆内存使用快照数据保存到E:\log1.txt中

           输入该命令后,让程序运行一会,尽可能运行的时间长一会,保证在这段时间内产生足够多的内存泄露,这样windbg的监测结果更准确一些。

    3.6、第二次使用umdh抓取堆内存使用快照

            继续在cmd中输入如下的命令:

    umdh.exe -pn:XXX.exe -f:E:\log2.txt

    命令说明同上。此命令运行后,将第二次抓取的堆内存使用快照数据保存到E:\log2.txt中

    3.7、比较两次堆内存使用快照,得出结论

            最后在cmd中输入:

    umdh.exe E:\log1.txt E:\log2.txt -f:E:\result.txt

    比较log1.txt 和log2.txt两文件中的堆内存使用变化量,得出统计数据,将统计结果保存到E:\result.txt文件中。打开E:\result.txt,即可查看到堆内存的变化情况,按堆内存申请数量从高到底排列,每一项统计结果都有详细的函数调用堆栈,一般我们只需要分析使用量较高的几项即可。一般第一项就是发生内存泄露的堆栈,比如:

     4、Windbg分析内存泄露的不足

           Windbg只能监测两个时间点的申请堆内存的变化量,并没有去统计释放的堆内存,这一点和Linux上排查内存泄露的神器Valgrind要差一些。所以Windbg统计出来的结果中,排在第一位的项可能并不是内存泄露的项,需要我们去结合代码将其他几项过滤掉,最终确定发生内存泄露的那一项。 

    展开全文
  • 上篇博客我们写到了 Java/Android 内存的分配以及相关 GC 的详细分析,这篇博客我们会继续分析 Android 中内存泄漏的检测以及相关案例,和 Android 的内存优化相关内容。 Android 内存泄漏案例和检测   常见...

    上篇博客我们写到了 Java/Android 内存的分配以及相关 GC 的详细分析,这篇博客我们会继续分析 Android 中内存泄漏的检测以及相关案例,和 Android 的内存优化相关内容。
      上篇:Android 性能优化之内存泄漏检测以及内存优化(上)
      中篇:Android 性能优化之内存泄漏检测以及内存优化(中)
      下篇:Android 性能优化之内存泄漏检测以及内存优化(下)
      转载请注明出处:http://blog.csdn.net/self_study/article/details/66969064
      对技术感兴趣的同鞋加群544645972一起交流。

    Android 内存泄漏检测

    通过上篇博客我们了解了 Android JVM/ART 内存的相关知识和泄漏的原因,再来归类一下内存泄漏的源头,这里我们简单将其归为一下三类:

    • 自身编码引起
    • 由项目开发人员自身的编码造成;
    • 第三方代码引起
    • 这里的第三方代码包含两类,第三方非开源的 SDK 和开源的第三方框架;
    • 系统原因
    • 由 Android 系统自身造成的泄漏,如像 WebView、InputMethodManager 等引起的问题,还有某些第三方 ROM 存在的问题。

    Android 内存泄漏的定位,检测与修复

    内存泄漏不像闪退的 BUG,排查起来相对要困难一些,比较极端的情况是当你的应用 OOM 才发现存在内存泄漏问题,到了这种情况才去排查处理问题的话,对用户的影响就太大了,为此我们应该在编码阶段尽早地发现问题,而不是拖到上线之后去影响用户体验,下面总结一下常用内存泄漏的定位和检测工具:

    Lint

    Lint 是 Android studio 自带的静态代码分析工具,使用起来也很方便,选中需要扫描的 module,然后点击顶部菜单栏 Analyze -> Inspect Code ,选择需要扫描的地方即可:
    这里写图片描述      这里写图片描述
    这里写图片描述
    最后在 Performance 里面有一项是 Handler reference leaks,里面列出来了可能由于内部 Handler 对象持有外部 Activity 引用导致内存泄漏的地方,这些地方都可以根据实际的使用场景去排查一下,因为毕竟不是每个内部 Handler 对象都会导致内存泄漏。Lint 还可以自定义扫描规则,使用姿势很多很强大,感兴趣的可以去了解一下,除了 Lint 之外,还有像 FindBugs、Checkstyle 等静态代码分析工具也是很不错的。

    StrictMode

    StrictMode 是 Android 系统提供的 API,在开发环境下引入可以更早的暴露发现问题给开发者,于开发阶段解决它,StrictMode 最常被使用来检测在主线程中进行读写磁盘或者网络操作等耗时任务,把这些耗时任务放置于主线程会造成主线程阻塞卡顿甚至可能出现 ANR ,官方例子:

     public void onCreate() {
         if (DEVELOPER_MODE) {
             StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                     .detectDiskReads()
                     .detectDiskWrites()
                     .detectNetwork()   // or .detectAll() for all detectable problems
                     .penaltyLog()
                     .build());
             StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                     .detectLeakedSqlLiteObjects()
                     .detectLeakedClosableObjects()
                     .penaltyLog()
                     .penaltyDeath()
                     .build());
         }
         super.onCreate();
     }
    

    把上面这段代码放在早期初始化的 Application、Activity 或者其他应用组件的 onCreate 函数里面来启用 StrictMode 功能,一般 StrictMode 只是在测试环境下启用,到了线上环境就不要开启这个功能。启用 StrictMode 之后,在 logcat 过滤日志的地方加上 StrictMode 的过滤 tag,如果发现一堆红色告警的 log,说明可能就出现了内存泄漏或者其他的相关问题了:
    这里写图片描述
    比如上面这个就是因为调用 registerReceiver 之后忘记调用 unRegisterReceiver 导致的 activity 泄漏,根据错误信息便可以定位和修复问题。

    LeakCanary

    LeakCanary 是一个 Android 内存泄漏检测的神器,正确使用可以大大减少内存泄漏和 OOM 问题,地址:

    https://github.com/square/leakcanary
    

    集成 LeakCanary 也很简单,在 build.gradle 文件中加入:

    dependencies {
       debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
       releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
       testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
     }
    

    然后在 Application 类中添加下面代码:

    public class ExampleApplication extends Application {
    
      @Override public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
          // This process is dedicated to LeakCanary for heap analysis.
          // You should not init your app in this process.
          return;
        }
        LeakCanary.install(this);
        // Normal app init code...
      }
    }
    

    上面两步做完之后就算是集成了 LeakCanary 了,非常简单方便,如果程序出现了内存泄漏会弹出 notification,点击这个 notification 就会进入到下面这个界面,或者集成 LeakCanary 之后在桌面会有一个 LeakCanary 的图标,点击进去是所有的内存泄漏列表,点击其中一项同样是进入到下面界面:
    这里写图片描述
    这个界面就会详细展示引用持有链,一目了然,对于问题的解决方便了很多,堪称神器,更多实用姿势可以看看 LeakCanary FAQ
      还有一点需要提到的是,LeakCanary 在检测内存泄漏的时候会阻塞主界面,这是一点体验有点不爽的地方,但是这时候阻塞肯定是必要的,因为此时必须要挂起线程来获取当前堆的状态。然后也并不是每个 LeakCanary 提示的地方都有内存泄漏,这时候可能需要借助 MAT 等工具去具体分析。不过 LeakCanary 有一点非常好的地方是因为 Android 系统也会有一些内存泄漏,而 LeakCanary 对此则提供了一个 AndroidExcludedRefs 类来帮助我们排除这些问题。

    adb shell && Memory Usage

    可以通过命令 adb shell dumpsys meminfo [package name][-d] 来将指定 package name 的内存信息打印出来,-d 选项会额外打印信息,数据单位为 KB,这种模式可以通过 Activities 选项非常直观地看到 Activity 未释放导致的内存泄漏:
    在这里插入图片描述
    一般情况下我们只需要关心 PSS Total + Private Dirty。相关字段的含义如下:

    ColumnDescription
    Private Dirty/Clean这是你指定应用单独占用的内存数量,换句话说这个就是你应用完全被销毁之后系统可以回收的内存数。一般来说,最重要的部分就是 Privity Dirty 部分,因为这个是你指定进程单独占用的内存数,而且 Android 没有使用 Swap 策略(交换分区),所以这部分内存只能存在于 RAM 中。所有应用创建的 Dalvik 和 Native 堆都是 Private Dirty RAM,和 Zygote 进程共享的 Dalvik 和 Native 堆都是 Shared Dirty RAM。Private Dirty/Clean 的相同之处在于他们都只是被当前这个进程引用,区别在于 Clean 是内存中的页面一直没有被该进程写入修改(映射持久文件使用的内存页,例如 so 和 dex,因此如果内存紧张的话可能会被置换出去,下次再使用的时候重新读入即可)而 Dirty 则进行了写入修改,所以 Private Clean 部分内存进行写入操作之后就会被挪到 Private Dirty 中
    Proportional Set Size (Pss)这个值是在应用所有占用 RAM 的基础上增加和其他应用进程共享的内存(比如同一个动态链接库被三个应用使用到,那么每个应用的 PSS 都会增加 so.size/3),如果只有你一个应用在使用这部分内存,那么这部分内存将直接加到 PSS 上。绝大多数情况下 PSS 都是大于等于 Private Dirty/Clean 数值
    Swap Dirty某些 Android 设备可以开启 Swap 策略,Linux 下又一个策略可以把内存压缩之后交换到 RAM 里面的一个特殊区域,当使用之后又会把它解压缩之后重新交换进内存
    Heap Size虚拟机分配的堆大小,数值上基本等于 Heap Alloc(已经分配的部分)+ Heap Free(可用部分)
    Heap Alloc统计的是虚拟机分配的所有应用实例的内存,也会将引用从 Zygote 共享的部分算进去,所以导致此值大于 Pss Total 和 Private Dirty
    Dalvik HeapDalvik 虚拟机使用的内存,Pss 包括了和其他应用共有的部分,而 Private Dirty 部分则是包含了应用自己分配和已经修改的 Zygote Allocation 页。包含 /dev/ashmem/dalvik-main space, /dev/ashmem/dalvik-allocspace, /dev/ashmem/dalvik-large object space, /dev/ashmem/dalvik-zygote space, /dev/ashmem/dalvik-non moving space的 Pss 部分
    Dalvik Other用于存放类的数据结构及关系以及管理 Dalvik 的内存(如, just-in-time compilation (JIT) and GC bookkeeping),随着类和函数数量,代码复杂度的增加而增加,除了 Dalvik Heap 外其他的以 /dev/ashmem/dalvik-*开头的空间。
    Ashmem共享内存
    Other dev除了在 Dalvik Heap、Dalvik Other 、Ashmem 列出内存之外(5.1 版本之后也剔除了 /dev/kgsl-3d0 )其他以/dev/* 开头的内存区域
    .so && .dex mmap.so mmap 和 .dex mmap 是用来映射 so 和 dex 代码的内存,Pss Total 包含了和其他应用共享的框架代码和动态库,Private Clean 则是应用单独的代码部分。通常情况下实际映射的内存会更大,因为当前只加载了已经执行的代码在内存中。.so mmap 通常也会有 Private Dirty ,这是因为 so 库被加载到最终地址做了相关写入修改
    .oat && .art mmap代码映像占用的内存,它是基于通常情况下多个应用共同使用的预加载类,这部分内存是多个应用共享,不会被单个应用影响。尽管 .art mmap 映像包含 Object 实例,它仍然不会计入您的堆大小。
    EGL mtrack5.1 版本之前被称为Graphics, gralloc 分配的内存,主要是窗口系统,所有 Surface Buffer(4.1 版本之后开启了三倍缓冲,所以在没有 SurfaceView 和 TextureView 的情况下 EGL mtrack = 3 * 单个buffer的内存大小) 和 Atlas Buffer 的总和。按道理来说,Atlas Buffer 实际上是一个共享内存并且不应该被算入每个进程的内存占用里面,但是实际情况 Surface Buffer 和 Atlas Buffer 都被算入了,所以当评价一个应用的内存占用时,基本可以忽略该栏
    GL mtrack5.1 版本之前被称为 GL,驱动上报的GL内存使用情况。 和 Gfx dev 加一起主要是 GL texture 大小,GL command buffer,固定的全局驱动程序 RAM 开销等的总和。 这部分内存可以在/d/kgsl/proc/PID/mem里面查看。
    Gfx dev5.1 版本加入,/dev/kgsl-3d0 Pss 部分内存区域占用,和 GL mtrack 一样都是 GPU 驱动上报的内存大小。用户驱动空间和内核驱动空间共享一块内存区域,在某些 Android 设备上可能会重复计算两次从而造成 Gfx dev 比实际的要大一些
    Unknown系统无法将其分类到其他更具体的一个项中的任何 RAM 页。 其 Pss Total 与 Zygote共享

    在这里插入图片描述

    官方指引:https://developer.android.com/studio/command-line/dumpsys#meminfo
    其他资料:https://blog.csdn.net/msf568834002/article/details/78881341
         http://www.voidcn.com/article/p-wfedutyx-qx.html
         https://unity3d.com/cn/learn/tutorials/topics/best-practices/android-memory-management
         https://my.oschina.net/jerikc/blog/391907

    Android Memory Profiler

    Memory Profiler 是 Android Studio 自带的一个监控内存使用状态的工具,打开 Profiler 之后入口如下所示:
    在这里插入图片描述
    在 Android Profiler 点开之后 logcat 的右侧就是 Profiler 工具,其中可以检测内存、CPU、网络等内容,我们这里只用到了 Memory Profiler 功能,点击红色箭头所指的区域,就会 dump 此时此刻的 Memory 信息,并且生成一个 .hprof 文件,dump 完成之后会自动打开这个文件的显示界面,当然也可以点击最左侧的 Heap Dump 的保存按钮将 .hprof 文件保存下来,需要注意的是这个文件因为不是标准的Java SE HPROF 格式,所以如果需要在 MAT 上看就需要通过命令 hprof-conv heap-original.hprof heap-converted.hprof 进行转换:
    在这里插入图片描述
      接着我们来分析一下这个生成的 .hprof 文件所展示的信息:
    在这里插入图片描述
    首先左上角的下拉框,可以选择 App Heap、Image Heap 和 Zygote Heap,对应的就是上篇博客讲到的 Allocation Space,Image Space 和 Zygote Space,我们这里选择 Allocation Space,然后第二个选择 Arrange by package 这一项,展开之后就能看见一个树形结构了,然后继续展开我们应用包名的对应对象,就可以很清晰的看到有多少个 Activity 对象了,红框里面的信息解释如下:

    ColumnDescription
    JavaDalvik Heap。从 Java 或 Kotlin 代码分配的对象内存
    NativeNative Heap。从 C 或 C++ 代码分配的对象内存。即使应用中不使用 C++,也可能会看到此处使用的一些原生内存,因为 Android 框架使用原生内存代表您处理各种任务,如处理图像资源和其他图形时,即使只采用 Java 或 Kotlin 语言
    GraphicsGfxdev + EGL mtrack + GL mtrack。图形缓冲区队列向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存(这是与 CPU 共享的内存,不是 GPU 专用内存)
    StackStack。应用中的原生堆栈和 Java 堆栈使用的内存。 这通常与应用运行多少线程有关
    Codeall mmaps。应用用于处理代码和资源(如 dex 字节码、已优化或已编译的 dex 码、.so 库和字体)的内存
    OtherOther dev + Unknown。应用使用的系统不确定如何分类的内存
    Allocated应用分配的 Java/Kotlin 对象数,没有计入 C 或 C++ 中分配的对象
    ColumnDescription
    Allocations指定的堆中实例数
    Native SizeNative 层所有实例的总大小(以字节为单位)
    Shallow Size此堆中所有实例的总大小(以字节为单位)
    Retained Size为此类的所有实例而保留的内存总大小(以字节为单位),会大于等于 Shallow Size,代表该对象回收时可释放内存大小
    Depth从 GC Root 到该对象的引用链路的最短步数

    如果发现某个 Activity 对象存在多个,并且 GC 后也不会减少,那就大概率是该 Activity 泄漏了,点击展开 Refereces,从对象找到 GC Roots就可以找到是被谁强饮用无法释放了:
    这里写图片描述
    可以看到 Thread 对象持有了 SecondActivity 对象的引用,也就是 GC Root 持有了该 Activity 的引用,导致这个 Activity 无法回收,问题的根源我们就发现了,接下来去处理它就好了。
      Android Memory Profiler 除了上面提到的记录展示某个时刻应用内存占用详情的功能外,还能获取某段时间内应用的对象分配情况,使用方式也很简单,找一个 8.0 及之上的 Android 手机,在 Memory Profiler 时间线上拖动一个起始和结束即可:
    在这里插入图片描述
    这样下方的 Live Allocation 框里面就是应用内存实时分配的详情,里面四个选项分别代表:

    ColumnDescription
    Allocations这段时间之内指定的堆中分配的实例数
    DealLocations这段时间之内指定的堆中释放的实例数
    Total Count此堆中所有实例的数量
    Shallow Size此堆中所有实例的总大小(以字节为单位)

    MAT

    MAT(Memory Analyzer Tools)是一个 Eclipse 插件,它是一个快速、功能丰富的 JAVA heap 分析工具,它可以帮助我们查找内存泄漏和减少内存消耗,MAT 插件的下载地址:Eclipse Memory Analyzer Open Source Project,上面通过 Android studio 生成的 .hprof 文件因为格式稍有不同,所以我们需要现将 AS 生成的 .hprof 文件保存到本地之后,通过 hprof-conv heap-original.hprof heap-converted.hprof 命令进行转换。通过 MAT 去打开转换之后的这个文件:
    这里写图片描述
    用的最多的就是 Histogram 功能,点击 Actions 下的 Histogram 项就可以得到 Histogram 结果:
    这里写图片描述
    我们可以在左上角写入一个正则表达式,然后就可以对所有的 Class Name 进行筛选了,很方便,顶栏展示的信息 “Objects” 代表该类名对象的数量,剩下的 “Shallow Heap” 和 “Retained Heap” 则和 Android Memory Profiler 类似。咱们接着点击 SecondActivity,然后右键:
    这里写图片描述
    在弹出来的菜单中选择 List objects->with incoming references 将该类的实例全部列出来:
    这里写图片描述
    通过这个列表我们可以看到 SecondActivity@0x12faa900 这个对象被一个 this$00x12c65140 的匿名内部类对象持有,然后展开这一项,发现这个对象是一个 handler 对象:
    这里写图片描述
    快速定位找到这个对象没有被释放的原因,可以右键 Path to GC Roots->exclude all phantom/weak/soft etc. references 来显示出这个对象到 GC Root 的引用链,因为强引用才会导致对象无法释放,所以这里我们要排除其他三种引用:
    这里写图片描述
    这么处理之后的结果就很明显了:
    这里写图片描述
    一个非常明显的强引用持有链,GC Root 我们前面的博客中说到包含了线程,所以这里的 Thread 对象 GC Root 持有了 SecondActivity 的引用,导致该 Activity 无法被释放。
      MAT 还有一个功能就是能够对比两个 .hprof 文件,将两个文件都添加到 Compare Basket 里面:
    这里写图片描述
    添加进去之后点击右上角的 ! 按钮,然后就会生成两个文件的对比:
    这里写图片描述
    同样适用正则表达式将需要的类筛选出来:
    这里写图片描述
    结果也很明显,退出 Activity 之后该 Activity 对象未被回收,仍然在内存中,或者可以调整对比选项让对比结果更加明显:
    这里写图片描述
    也可以对比两个对象集合,方法与此类似,都是将两个 Dump 结果中的对象集合添加到 Compare Basket 中去对比,找出差异后用 Histogram 查询的方法找出 GC Root,定位到具体的某个对象上。

    常见的内存泄漏案例

    我们来看看常见的导致内存泄漏的案例:

    静态变量造成的内存泄漏

    由于静态变量的生命周期和应用一样长,所以如果静态变量持有 Activity 或者 Activity 中 View 对象的应用,就会导致该静态变量一直直接或者间接持有 Activity 的引用,导致该 Activity 无法释放,从而引发内存泄漏,不过需要注意的是在大多数这种情况下由于静态变量只是持有了一个 Activity 的引用,所以导致的结果只是一个 Activity 对象未能在退出之后释放,这种问题一般不会导致 OOM 问题,只能通过上面介绍过的几种工具在开发中去观察发现。
      这种问题的解决思路很简单,就是不让静态变量直接或者间接持有 Activity 的强引用,可以将其修改为 soft reference 或者 weak reference 等等之类的,或者如果可以的话将 Activity Context 更换为 Application Context,这样就能保证生命周期一致不会导致内存泄漏的问题了。

    内部类持有外部类引用

    我们上面的 demo 中模拟的就是内部类对象持有外部类对象的引用导致外部类对象无法释放的问题,在 Java 中非静态内部类和匿名内部类会持有他们所属外部类对象的引用,如果这个非静态内部类对象或者匿名内部类对象被一个耗时的线程(或者其他 GC Root)直接或者间接的引用,甚至这些内部类对象本身就在做一些耗时操作,这样就会导致这个内部类对象直接或者间接无法释放,内部类对象无法释放,外部类的对象也就无法释放造成内存泄漏,而且如果无法释放的对象积累起来就会造成 OOM,示例代码如下所示:

    public class SecondActivity extends AppCompatActivity{
        private Handler handler;
        private Bitmap bitmap;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_second);
            bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.pic);//decode 一个大图来模拟内存无法释放导致的崩溃
            findViewById(R.id.btn_second).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    finish();
                }
            });
    
            handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
    
                }
            };
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    handler.sendEmptyMessage(0);
                }
            }).start();
        }
    }
    

    这个问题的解决方法可以根据实际情况进行选择:

    • 将非静态内部类或者匿名内部类修改为静态内部类,比如 Handler 修改为静态内部类,然后让 Handler 持有外部 Activity 的一个 Weak Reference 或者 Soft Reference;
    • 在 Activity 页面销毁的时候将耗时任务停止,这样就能保证 GC Root 不会间接持有 Activity 的引用,也就不会导致内存泄漏;

    错误使用 Activity Context

    这个很好理解,在一个错误的地方使用 Activity Context,造成 Activity Context 被静态变量长时间引用导致无法释放而引发的内存泄漏,这个问题的处理方式也很简单,如果可以的话修改为 Application Context 或者将强引用变成其他引用。

    资源对象没关闭造成的内存泄漏

    资源性对象比如(Cursor,File 文件等)往往都用了一些缓冲,我们在不使用的时候应该及时关闭它们,以便它们的缓冲对象被及时回收,这些缓冲不仅存在于 java 虚拟机内,还存在于 java 虚拟机外,如果我们仅仅是把它的引用设置为 null 而不关闭它们,往往会造成内存泄漏。但是有些资源性对象,比如 SQLiteCursor(在析构函数 finalize(),如果我们没有关闭它,它自己会调 close() 关闭),如果我们没有关闭它系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的 close() 函数,将其关闭掉,然后再置为 null,在我们的程序退出时一定要确保我们的资源性对象已经关闭。
      程序中经常会进行查询数据库的操作,但是经常会有使用完毕 Cursor 后没有关闭的情况,如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会出现内存问题,这样就会给以后的测试和问题排查带来困难和风险,示例代码:

    Cursor cursor = getContentResolver().query(uri...); 
    if (cursor.moveToNext()) { 
    ... ... 
    } 
    

    更正代码:

    Cursor cursor = null;
    try {
        cursor = getContentResolver().query(uri...);
        if (cursor != null && cursor.moveToNext()) {
            ... ...
        }
    } finally {
        if (cursor != null) {
            try {
                cursor.close();
            } catch (Exception e) {
                //ignore this
            }
        }
    }
    

    集合中对象没清理造成的内存泄漏

    在实际开发过程中难免会有把对象添加到集合容器(比如 ArrayList)中的需求,如果在一个对象使用结束之后未将该对象从该容器中移除掉,就会造成该对象不能被正确回收,从而造成内存泄漏,解决办法当然就是在使用完之后将该对象从容器中移除。

    WebView造成的内存泄露

    具体的可以看看我的这篇博客:android WebView详解,常见漏洞详解和安全源码(下)

    未取消注册导致的内存泄漏

    一些 Android 程序可能引用我们的 Android 程序的对象(比如注册机制),即使我们的 Android 程序已经结束了,但是别的应用程序仍然还持有对我们 Android 程序某个对象的引用,这样也会造成内存不能被回收,比如调用 registerReceiver 后未调用unregisterReceiver。假设我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息,则可以在 LockScreen 中定义一个 PhoneStateListener 的对象,同时将它注册到 TelephonyManager 服务中,对于 LockScreen 对象,当需要显示锁屏界面的时候就会创建一个 LockScreen 对象,而当锁屏界面消失的时候 LockScreen 对象就会被释放掉,但是如果在释放 LockScreen 对象的时候忘记取消我们之前注册的 PhoneStateListener 对象,则会间接导致 LockScreen 无法被回收,如果不断的使锁屏界面显示和消失,则最终会由于大量的 LockScreen 对象没有办法被回收而引起 OOM,虽然有些系统程序本身好像是可以自动取消注册的(当然不及时),但是我们还是应该在程序结束时明确的取消注册。

    因为内存碎片导致分配内存不足

    还有一种情况是因为频繁的内存分配和释放,导致内存区域里面存在很多碎片,当这些碎片足够多,new 一个大对象的时候,所有的碎片中没有一个碎片足够大以分配给这个对象,但是所有的碎片空间加起来又是足够的时候,就会出现 OOM,而且这种 OOM 从某种意义上讲,是完全能够避免的。
      由于产生内存碎片的场景很多,从 Memory Profiler 来看,下面场景的内存抖动是很容易产生内存碎片的:
    这里写图片描述
    最常见产生内存抖动的例子就是在 ListView 的 getView 方法中未复用 convertView 导致 View 的频繁创建和释放,针对这个问题的处理方式那当然就是复用 convertView;或者是 String 拼接创建大量小的对象(比如在一些频繁调用的地方打字符串拼接的 log 的时候);如果是其他的问题,就需要通过 Memory Profiler 去观察内存的实时分配释放情况,找到内存抖动的地方修复它,或者如果当出现下面这种情况下的 OOM 时,也是由于内存碎片导致无法分配内存:
    这里写图片描述
    出现上面这种类型的 Crash 时就要去分析应用里面是不是存在大量分配释放对象的地方了。

    Android 内存优化

    内存优化请看下篇:Android 性能优化之内存泄漏检测以及内存优化(下)

    引用

    http://blog.csdn.net/luoshengyang/article/details/42555483
    http://blog.csdn.net/luoshengyang/article/details/41688319
    http://blog.csdn.net/luoshengyang/article/details/42492621
    http://blog.csdn.net/luoshengyang/article/details/41338251
    http://blog.csdn.net/luoshengyang/article/details/41581063
    https://mp.weixin.qq.com/s?__biz=MzA4MzEwOTkyMQ==&mid=2667377215&idx=1&sn=26e3e9ec5f4cf3e7ed1e90a0790cc071&chksm=84f32371b384aa67166a3ff60e3f8ffdfbeed17b4c8b46b538d5a3eec524c9d0bcac33951a1a&scene=0&key=c2240201df732cf062d22d3cf95164740442d817864520af90bb0e71fa51102f2e91475a4f597ec20653c59d305c8a3e518d3f575d419dfcf8fb63a776e0d9fa6d3a9a6a52e84fedf3f467fe4af1ba8b&ascene=0&uin=Mjg5MDI3NjQ2Mg%3D%3D&devicetype=iMac+MacBookPro11%2C4+OSX+OSX+10.12.3+build(16D32)&version=12010310&nettype=WIFI&fontScale=100&pass_ticket=Upl17Ws6QQsmZSia%2F%2B0xkZs9DYxAJBQicqh8rcaxYUjcu3ztlJUPxYrQKML%2BUtuf
    http://geek.csdn.net/news/detail/127226
    http://www.jianshu.com/p/216b03c22bb8
    https://zhuanlan.zhihu.com/p/25213586
    https://joyrun.github.io/2016/08/08/AndroidMemoryLeak/
    http://www.cnblogs.com/larack/p/6071209.html
    https://source.android.com/devices/tech/dalvik/gc-debug.html
    http://blog.csdn.net/high2011/article/details/53138202
    http://gityuan.com/2015/10/03/Android-GC/
    http://www.ayqy.net/blog/android-gc-log解读/
    https://developer.android.com/studio/profile/investigate-ram.html
    https://zhuanlan.zhihu.com/p/26043999
    https://blog.csdn.net/msf568834002/article/details/78881341

    展开全文
  • linux进程内存分布

    千次阅读 多人点赞 2020-06-18 11:10:48
    程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。 初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。 未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。 栈 (Stack):...

    很多小伙伴在调试C代码的时候非常痛苦,C语言不像java那样可以给你指出具体的错误地方和错误原因,C语音因为指针的特殊性和C语言版本的兼容性的需要,很难直接定位到错误的地方。特别是各种段错误、溢出等。要想提高调试效率,了解和掌握进程内存布局还是很有必要的。了解了内存空间分配,有时候就可以通过指针或者地址的位置来确定是否是程序本身写错了等等。

     进程空间分布概述

    对于一个进程,其空间分布如下图所示:

    程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。

    初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。

    未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。

    栈 (Stack):存储局部、临时变量,函数调用时,存储函数的返回指针,用于控制函数的调用和返回。在程序块开始时自动分配内存,结束时自动释放内存,其操作方式类似于数据结构中的栈。

    堆 (Heap):存储动态内存分配,需要程序员手工分配,手工释放.注意它与数据结构中的堆是两回事,分配方式类似于链表。

     

    注:1.Text, BSS, Data段在编译时已经决定了进程将占用多少VM
    可以通过size,知道这些信息。

    2. 正常情况下,Linux进程不能对用来存放程序代码的内存区域执行写操作,即程序代码是以只读的方式加载到内存中,但它可以被多个进程安全的共享。

    内核空间和用户空间

    Linux的虚拟地址空间范围为0~4G(intel x86架构32位),Linux内核将这4G字节的空间分为两部分,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF)供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF)供各个进程使用,称为“用户空间”。

    因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

    Linux使用两级保护机制:0级供内核使用,3级供用户程序使用,每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的,最高的1GB字节虚拟内核空间则为所有进程以及内核所共享。

    内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。 虽然内核空间占据了每个虚拟空间中的最高1GB字节,但映射到物理内存却总是从最低地址(0x00000000),另外,使用虚拟地址可以很好的保护内核空间被用户空间破坏,虚拟地址到物理地址转换过程有操作系统和CPU共同完成(操作系统为CPU设置好页表,CPU通过MMU单元进行地址转换)。

    :多任务操作系统中的每一个进程都运行在一个属于它自己的内存沙盒中,这个沙盒就是虚拟地址空间(virtual address space),在32位模式下,它总是一个4GB的内存地址块。这些虚拟地址通过页表(page table)映射到物理内存,页表由操作系统维护并被处理器引用。每个进程都拥有一套属于它自己的页表。

    进程内存空间分布如下图所示:

     



    通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间

    : 1.这里是32位内核地址空间划分,64位内核地址空间划分是不同的
    2.现代的操作系统都处于32位保护模式下。每个进程一般都能寻址4G的物理空间。但是我们的物理内存一般都是几百M,进程怎么能获得4G 的物理空间呢?这就是使用了虚拟地址的好处,通常我们使用一种叫做虚拟内存的技术来实现,因为可以使用硬盘中的一部分来当作内存使用 。

     


    Linux系统对自身进行了划分,一部分核心软件独立于普通应用程序,运行在较高的特权级别上,它们驻留在被保护的内存空间上,拥有访问硬件设备的所有权限,Linux将此称为内核空间。

    相对地,应用程序则是在“用户空间”中运行。运行在用户空间的应用程序只能看到允许它们使用的部分系统资源,并且不能使用某些特定的系统功能,也不能直接访问内核空间和硬件设备,以及其他一些具体的使用限制。

    将用户空间和内核空间置于这种非对称访问机制下有很好的安全性,能有效抵御恶意用户的窥探,也能防止质量低劣的用户程序的侵害,从而使系统运行得更稳定可靠。

    内核空间在页表中拥有较高的特权级(ring2或以下),因此只要用户态的程序试图访问这些页,就会导致一个页错误(page fault)。在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存,内核代码和数据总是可寻址的,随时准备处理中断和系统调用。与之相反,用户模式地址空间的映射随着进程切换的发生而不断的变化,如下图所示:

     

    上图中蓝色区域表示映射到物理内存的虚拟地址,而白色区域表示未映射的部分。可以看出,Firefox使用了相当多的虚拟地址空间,因为它占用内存较多。

     进程内存布局

    Linux进程标准的内存段布局,如下图所示,地址空间中的各个条带对应于不同的内存段(memory segment),如:堆、栈之类的。
     

     


    :这些段只是简单的虚拟内存地址空间范围,与Intel处理器的段没有任何关系。

    几乎每个进程的虚拟地址空间中各段的分布都与上图完全一致,这就给远程发掘程序漏洞的人打开了方便之门。一个发掘过程往往需要引用绝对内存地址:栈地址,库函数地址等。远程攻击者必须依赖地址空间分布的一致性,来探索出这些地址。如果让他们猜个正着,那么有人就会被整了。因此,地址空间的随机排布方式便逐渐流行起来,Linux通过对栈、内存映射段、堆的起始地址加上随机的偏移量来打乱布局。但不幸的是,32位地址空间相当紧凑,这给随机化所留下的空间不大,削弱了这种技巧的效果。

    进程地址空间中最顶部的段是栈,大多数编程语言将之用于存储函数参数和局部变量。调用一个方法或函数会将一个新的栈帧(stack frame)压入到栈中,这个栈帧会在函数返回时被清理掉。由于栈中数据严格的遵守FIFO的顺序,这个简单的设计意味着不必使用复杂的数据结构来追踪栈中的内容,只需要一个简单的指针指向栈的顶端即可,因此压栈(pushing)和退栈(popping)过程非常迅速、准确。进程中的每一个线程都有属于自己的栈。

    通过不断向栈中压入数据,超出其容量就会耗尽栈所对应的内存区域,这将触发一个页故障(page fault),而被Linux的expand_stack()处理,它会调用acct_stack_growth()来检查是否还有合适的地方用于栈的增长。如果栈的大小低于RLIMIT_STACK(通常为8MB),那么一般情况下栈会被加长,程序继续执行,感觉不到发生了什么事情。这是一种将栈扩展到所需大小的常规机制。然而,如果达到了最大栈空间的大小,就会栈溢出(stack overflow),程序收到一个段错误(segmentation fault)。


    :动态栈增长是唯一一种访问未映射内存区域而被允许的情形,其他任何对未映射内存区域的访问都会触发页错误,从而导致段错误。一些被映射的区域是只读的,因此企图写这些区域也会导致段错误。

    内存映射段
    在栈的下方是内存映射段,内核将文件的内容直接映射到内存。任何应用程序都可以通过Linux的mmap()系统调用或者Windows的CreateFileMapping()/MapViewOfFile()请求这种映射。内存映射是一种方便高效的文件I/O方式,所以它被用来加载动态库。创建一个不对应于任何文件的匿名内存映射也是可能的,此方法用于存放程序的数据。在Linux中,如果你通过malloc()请求一大块内存,C运行库将会创建这样一个匿名映射而不是使用堆内存。“大块”意味着比MMAP_THRESHOLD还大,缺省128KB,可以通过mallocp()调整。


    与栈一样,堆用于运行时内存分配;但不同的是,堆用于存储那些生存期与函数调用无关的数据。大部分语言都提供了堆管理功能。在C语言中,堆分配的接口是malloc()函数。如果堆中有足够的空间来满足内存请求,它就可以被语言运行时库处理而不需要内核参与,否则,堆会被扩大,通过brk()系统调用来分配请求所需的内存块。堆管理是很复杂的,需要精细的算法来应付我们程序中杂乱的分配模式,优化速度和内存使用效率。处理一个堆请求所需的时间会大幅度的变动。实时系统通过特殊目的分配器来解决这个问题。堆在分配过程中可能会变得零零碎碎,如下图所示:
     



    一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式类似于链表。


    BBS和数据段
    在C语言中,BSS和数据段保存的都是静态(全局)变量的内容。区别在于BSS保存的是未被初始化的静态变量内容,他们的值不是直接在程序的源码中设定的。BSS内存区域是匿名的,它不映射到任何文件。如果你写static intcntActiveUsers,则cntActiveUsers的内容就会保存到BSS中去。
    数据段保存在源代码中已经初始化的静态变量的内容。数据段不是匿名的,它映射了一部分的程序二进制镜像,也就是源代码中指定了初始值的静态变量。所以,如果你写static int cntActiveUsers=10,则cntActiveUsers的内容就保存在了数据段中,而且初始值是10。尽管数据段映射了一个文件,但它是一个私有内存映射,这意味着更改此处的内存不会影响被映射的文件。

    你可以通过阅读文件/proc/pid_of_process/maps来检验一个Linux进程中的内存区域。记住:一个段可能包含许多区域。比如,每个内存映射文件在mmap段中都有属于自己的区域,动态库拥有类似BSS和数据段的额外区域。有时人们提到“数据段”,指的是全部的数据段+BSS+堆。

    你还可以通过nm和objdump命令来察看二进制镜像,打印其中的符号,它们的地址,段等信息。最后需要指出的是,前文描述的虚拟地址布局在linux中是一种“灵活布局”,而且作为默认方式已经有些年头了,它假设我们有值RLIMT_STACK。但是,当没有该值得限制时,Linux退回到“经典布局”,如下图所示:

     

    全局变量(初始化和未初始化)位于数据段

    局部变量位于栈段 

    const 常量位于栈段 

    常字符串位于文本段 

    动态申请内存位于堆段 

    查看进程的内存布局

    C语言程序实例分析如下所示:

    #include <stdio.h>
    #include <malloc.h>
    
    void print(char *, int);
    int g1=12;
    long g2;
    void main(){
      char *s1 = "abcde";
      char *s2 = "abcde";
      char s3[] = "abcd";
      long int *s4[100];
      char *s5 = "abcde";//常量字符串"abcde"在常量区,但是s1,s2,s5本身在stack上,但它们用有相同的地址
      int a = 5;
      int b = 6; //a和b在stack上,所以&a>&b
      const int c =10;
      sleep(60);
      printf("变量地址\n&s1=%p\n&s2=%p\n&s3=%p\n&s4=%p\n&s5=%p\ns1=%p\ns2=%p\ns3=%p\ns4=%p\ns5=%p\na=%p\nb=%p\n",&s1,&s2,&s3,&s4,&s5,s1,s2,s3,s4,s5,&a,&b);
      printf("&g1=%p\n &g2=%p\n&c=%p\n",&g1,&g2,&c);
      printf("变量地址在进程调用中");
      print("ddddddd",5);
      printf("main=%p, print=%p\n",main,print);
      while(1){}
    }
    void print(char *str, int p)
    {
      char *s1 = "abcde";
      char *s2 = "abcde";
      char s3[] = "abcd";
      long int *s4[100];
      char *s5 = "abcde";//常量字符串"abcde"在常量区,但是s1,s2,s5本身在stack上,但它们用有相同的地址
      int a = 5;
      int b = 6; //a和b在stack上,所以&a>&b
      int c;
      int d;
      char *q = str;
      int m =p;
      char *r=(char*)malloc(1);
      char *w = (char*)malloc(1);
       printf("变量地址\n&s1=%p\n&s2=%p\n&s3=%p\n&s4=%p\n&s5=%p\ns1=%p\ns2=%p\ns3=%p\ns4=%p\ns5=%p\na=%p\nb=%p\n",&s1,&s2,&s3,&s4,&s5,s1,s2,s3,s4,s5,&a,&b);
       printf("str=%p\nq=%p\n&q=%p\n&p=%p\n&m=%p\nr=%p\nw=%p\n&r=%p\n&w=%p\n",&str,q,&q,&p,&m,r,w,&r,&w);
    }

     

    变量地址
    &s1=0x7ffe949e0308
    &s2=0x7ffe949e0310
    &s3=0x7ffe949e0640
    &s4=0x7ffe949e0320
    &s5=0x7ffe949e0318
    s1=0x400998
    s2=0x400998//s1,s2,s5都指向同一个地址,该地址就是字符串常量保存的位置,位于text段
    s3=0x7ffe949e0640
    s4=0x7ffe949e0320
    s5=0x400998
    a=0x7ffe949e02fc
    b=0x7ffe949e0300
    &g1=0x601050 //位于bss段
     &g2=0x601060
    &c=0x7ffe949e0304
    变量地址在进程调用中变量地址
    &s1=0x7ffe949dff80
    &s2=0x7ffe949dff88
    &s3=0x7ffe949e02d0
    &s4=0x7ffe949dffb0
    &s5=0x7ffe949dff90
    s1=0x400998
    s2=0x400998
    s3=0x7ffe949e02d0
    s4=0x7ffe949dffb0
    s5=0x400998
    a=0x7ffe949dff74
    b=0x7ffe949dff78
    str=0x7ffe949dff68
    q=0x400a2f
    &q=0x7ffe949dff98
    &p=0x7ffe949dff64
    &m=0x7ffe949dff7c
    r=0x1d3c420
    w=0x1d3c440
    &r=0x7ffe949dffa0
    &w=0x7ffe949dffa8
    main=0x400626, print=0x40076f

    使用/proc/进程id/maps文件查看进程的内存空间分布

    查看进程的内存布局: 

    00400000-00401000这一段可以读可以执行,是text段,也就是程序段;

    00600000-00601000这一段可读,应该是data段

    00601000-0060200这一段可读可写,应该是bss段

    01d3c000-01d5d000这一段可读可写,属于heap区

    7f44bbfd0000-7f44bc5c2000这一段应该是内存映射区(会增长)

    7ffe949c0000-7ffe949e2000这一段属于stack区,长度为0x22000字节,

    这里写图片描述

    其中未分配的堆栈内存中一部分用于内存映射也就是mmap。

    从上图可以看出,进程的内存空间从低地址到高地址内存布局依次是: 保留区 –> 文本段–>数据段—>堆—>共享库或mmap—>栈–>环境变量—>内核空间

    查看该进程对应的内存布局,结果如下:

    每一行依次对应的是: 地址范围、权限、偏移量、设备、文件inode、映射对象

    64位地址时将0x0000,0000,0000,0000 – 0x0000,7fff,ffff,f000这128T地址用于用户空间。参见定义:

    #define TASK_SIZE_MAX   ((1UL << 47) - PAGE_SIZE),注意这里还减去了一个页面的大小做为保护。

    而0xffff,8000,0000,0000以上为系统空间地址。注意:该地址前4个都是f,这是因为目前实际上只用了64位地址中的48位(高16位是没有用的),而从地址0x0000,7fff,ffff,ffff到0xffff,8000,0000,0000中间是一个巨大的空洞,是为以后的扩展预留的。

    从上述地址值来看,64位系统中应该有48根地址总线,低位:0~47位才是有效的可变地址,高位:48~63位全补0或全补1。一般高位全补0对应的地址空间是用户态。高位全补1对应的是内核态,如上面的第19行(vsyscall段)。这64位的地址空间并不能全部被使用(太多了),所以用户态和内核态之间会有未使用的空间(据说叫AMD64空洞)。

    而真正的系统空间的起始地址,是从0xffff,8800,0000,0000开始的,参见:

    #define __PAGE_OFFSET     _AC(0xffff,8800,0000,0000, UL)

    而32位地址时系统空间的起始地址为0xC000,0000。

    另外0xffff,8800,0000,0000 – 0xffff,c7ff,ffff,ffff这64T直接和物理内存进行映射,0xffff,c900,0000,0000 – 0xffff,e8ff,ffff,ffff这32T用于vmalloc/ioremap的地址空间。

    至于用户空间的几个段的划分,不同的架构和编译选项貌似还不一样。暂时没有找到合适的解释。

    查看程序各段的大小,使用size 命令:

        text       data        bss        dec        hex    filename
       2379        576          8       2963        b93    mmtest

    使用 pmap 命令查看进程内存分布

    pmap [参数] [进程pid]

    参数:

    • -d 显示详细设备信息
    • -q 不显示首尾行信息

    运行测试程序,用top或ps 命令查询进程pid:

    属性含义
    Address地址空间:起始地址~
    Kbytes大小
    Mode权限:r可读、w可写、x可执行、s共享内存、p私有内存
    Offset虚拟内存偏移量
    Device所在设备(主:次): 008:00008表示sda8
    mapped虚拟内存分配大小
    shared共享内存大小

     mmtest 是运行程序的名字 
    .so 是使用的动态链接库 
    stack 使用的栈空间 
    anon 预分配的虚拟内存,还未有数据占用

    附录:

    栈与堆的区别:

    参考:https://zhuanlan.zhihu.com/p/26857760

    https://blog.csdn.net/chenyijun/article/details/79441166

    展开全文
  • 1. 信息在计算机系统的表示 我们知道,信息在计算机系统是以二进制的方式进行传送,存储的。那么信息在计算机系统是如何表示的呢?在这里可分为数值信息和非数值信息两个方面进行讨论。 数据信息分类示意图...
  • linux目录:/bin: 存放二进制可执行文件(ls、cat、mkdir等);.../proc: 虚拟文件系统目录,是系统内存的映射。可直接访问这个目录来获取系统信息;/root: 超级用户(系统管理员)的主目录(特权阶级^o^);/sbin...
  • linuxShell历史命令记录文件的路径是什么
  • Linux下查看某一进程占用的内存

    千次阅读 2021-11-01 14:56:25
    Linux下查看某一个进程所占用的内存,首先可以通过ps命令找到进程id,比如:ps -ef|grep flink,可以看到flink task这个程序的进程id 已知pid是8678,现在可以使用如下命令查看内存: 这样可以动态实时的看到...
  • Spark中内存模型管理

    千次阅读 2018-07-18 12:08:52
    Spark 作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统扮演着非常重要的角色。理解 Spark 内存管理的基本原理,有助于更好地开发 Spark 应用程序和进行性能调优。本文旨在梳理出 Spark 内存管理的...
  • 在基于ue的手游开发,经常会发现android系统的实际内存占用要比我们预估的高很多,优化内存的占用就要先明确究竟每1k实际的内存占用分布在哪里及如何运用工具有效的获取真实的内存组成,本文将结合项目经验详细...
  • linux内存管理--进程在内存中的分布

    千次阅读 2017-07-12 13:39:24
     所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等。不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内存是事先静态分配和统一...
  • 没有内存,怎么还能跑程序呢

    万次阅读 多人点赞 2020-02-26 10:33:00
    主存(RAM) 是一件非常重要的资源,必须要小心对待内存。虽然目前大多数内存的增长速度要比 IBM 7094 要快的多,但是,程序大小的增长要比内存的增长还快很多。正如帕金森定律说的那样:不管存储器有多大,但是程序...
  • documentlinux内存机制CPU内存虚拟内存硬盘物理内存内存和虚拟内存跟 Windows 完全不同的 Linux 内存机制Swap配置对性能的影响 linux内存机制 Linux支持虚拟内存(Virtual Mmemory),虚拟内存是指使用磁盘当作RAM的...
  • Linux常用命令

    万次阅读 多人点赞 2021-05-31 21:17:39
    r now 重启 halt 关机 reboot 重启 sync 把内存的数据同步到磁盘 注意: 不管是重启还是关机,首先运行sync命令,把内存中的数据写到磁盘; 目前shutdown、reboot、halt都支持在操作之前进行sync,但小心使得...
  • tar –xvf file.tar 解压 tar包 unzip file.zip 解压zip unrar e file.rar 解压rar free -m 查看服务器内存使用情况 ps查看进程 2.如何查看所有java进程 grep是搜索关键字 ps -ef | grep java -aux 显示所有状态 ps...
  • 20 Redis 的内存空间存储效率问题

    万次阅读 2021-12-09 20:48:43
    做了数据删除,数据量已经不大了,使用 top 命令查看时还会发现 Redis 占用了很多内存。这是因为当数据删除后,Redis 释放的内存空间会由内存分配器管理,并不会立即返回给操作系统。所以操作系统仍然会记录着给 ...
  • Linux常用命令大全(非常全!!!)

    万次阅读 多人点赞 2019-07-25 16:40:12
    Linux常用命令大全(非常全!!!) 最近都在和Linux打交道,感觉还不错。我觉得Linux相比windows比较麻烦的就是很多东西都要用命令来控制,当然,这也是很多人喜欢linux的原因,比较短小但却功能强大。我将我了解...
  • Linuxexec命令相关:

    万次阅读 2018-09-12 15:15:49
    exec和source都属于bash内部命令(builtins commands),在bash下输入man exec或man source可以查看所有的内部命令信息。  bash shell的命令分为两类:外部命令和内部命令。外部命令是通过系统调用或独立的程序实现的...
  • JVM内存模型和垃圾回收机制

    千次阅读 2019-06-26 08:00:00
    JVM内存模型 根据Java虚拟机规范,Java数据区域分为五大数据区域。 其中方法区和堆是所有线程共享的,虚拟机...JDK8已经彻底移除了方法区,JDK8引入了一个新的内存区域叫metaspace(元空间),后边详细介绍...
  • Linux命令学习知识大全(2022)

    千次阅读 多人点赞 2022-04-20 11:28:17
    /sys—————虚拟文件————输出设备的硬件信息 /tmp—————临时文件存放———重启后就不在——类似于内存 /usr—————二进制-库文件-文档-二级程序源码————/usr/local /var—————内容可能增长的...
  • 问题: 内存使用率88%高于80%...物理机内存使用率和使用free命令计算结果是一致的。物理机和容器两者内存计算数据是独立的 解决步骤: 1、通过java进程命令定位 系统进程并使用jmap工具dump文件。 ps -ef | gre...
  • linux 磁盘管理命令

    千次阅读 2017-08-11 10:18:44
    linux 磁盘管理命令,fdisk:强大的磁盘分区工具.badblocks:检查设备坏块 ; 磁盘管理命令;du:统计文件和目录占用的磁盘空间;df:显示文件系统磁盘空间使用情况;mkfs:设置磁盘的文件系统;pvdisplay:查看组成LVM...
  • 全面理解Java内存模型

    万次阅读 2018-02-27 08:28:17
    JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。如果我们要想深入了解Java并发编程,就要先理解好Java内存模型。Java内存模型定义了多线程之间共享变量...
  • 小白都能看懂的java虚拟机内存区域划分

    万次阅读 多人点赞 2019-11-26 17:21:27
    说白了赋值号=后面的就是操作数,在这些操作数进行赋值,运算的时候需要内存存放,那就是存放在操作数栈,作为临时存放操作数的一小块内存区域。 接下来我们再说说方法出口。 方法出口说白了不就是方法执行完了...
  • 大页内存(HugePages)在通用程序优化的应用

    万次阅读 多人点赞 2014-12-13 20:04:04
    这方面的资料比较贫乏,而且网上绝大多数资料都是介绍它在Oracle数据库的应用,这会让人产生一种错觉:这种技术只能在Oracle数据库应用。但其实,大页内存可以算是一种非常通用的优化技术,应用范围很广,针对...
  • JVM内存结构和Java内存模型别再傻傻分不清了

    万次阅读 多人点赞 2020-03-04 20:53:30
    JVM内存结构和Java内存模型都是面试的热点问题,名字看感觉都差不多,网上有些博客也都把这两个概念混着用,实际上他们之间差别还是挺大的。 通俗点说,JVM内存结构是与JVM的内部存储结构相关,而Java内存模型是与多...
  • Java虚拟机(JVM)内存设置详解

    千次阅读 2017-09-15 17:26:22
    Java虚拟机(JVM)内存设置详解   在一些规模稍大的应用,Java虚拟机(JVM)的内存设置尤为重要,想在项目取得好的效率,GC(垃圾回收)的设置是第一步。 PermGen space:全称是Permanent ...
  • 这段bootloader程序会先初始化DDR等外设,然后将Linux内核从flash(NAND,NOR FLASH, SD, MMC 等)拷贝到 DDR ,最后启动 Linux 内核。当然了, bootloader 的实际工作要复杂的多,但是它最主要的工作就是启动 ...
  • 基础篇:Linux 常用命令总结

    万次阅读 多人点赞 2021-08-29 19:25:39
    1️⃣ rpm 命令 – RPM软件包管理器 2️⃣ find 命令 – 查找和搜索文件 3️⃣ startx 命令 – 初始化X-windows 4️⃣ uname 命令 – 显示系统信息 5️⃣ vmstat 命令 – 显示虚拟内存状态 磁盘管理 1️⃣ df 命令 ...
  • Linux内存管理

    千次阅读 2018-02-05 14:49:01
    在linux下,使用top,vmstat,free等命令查看系统或者进程的内存使用情况时,经常看到buff/cache memeory,swap,avail Mem等,他们都代表什么意思呢?这篇文章将来聊一聊Linux下的内存管理并解答这个问题。 讨论...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 177,431
精华内容 70,972
关键字:

内存中只能存放命令