mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-26 21:26:41 +08:00
905 lines
26 KiB
HTML
905 lines
26 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>03 深入剖析:哪些资源,容易成为瓶颈?.md</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/00 Java 性能优化,是进阶高级架构师的炼金石.md">00 Java 性能优化,是进阶高级架构师的炼金石.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/01 理论分析:性能优化,有哪些衡量指标?需要注意什么?.md">01 理论分析:性能优化,有哪些衡量指标?需要注意什么?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/02 理论分析:性能优化有章可循,谈谈常用的切入点.md">02 理论分析:性能优化有章可循,谈谈常用的切入点.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/Java 性能优化实战-完/03 深入剖析:哪些资源,容易成为瓶颈?.md">03 深入剖析:哪些资源,容易成为瓶颈?.md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/04 工具实践:如何获取代码性能数据?.md">04 工具实践:如何获取代码性能数据?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/05 工具实践:基准测试 JMH,精确测量方法性能.md">05 工具实践:基准测试 JMH,精确测量方法性能.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/06 案例分析:缓冲区如何让代码加速.md">06 案例分析:缓冲区如何让代码加速.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/07 案例分析:无处不在的缓存,高并发系统的法宝.md">07 案例分析:无处不在的缓存,高并发系统的法宝.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/08 案例分析:Redis 如何助力秒杀业务.md">08 案例分析:Redis 如何助力秒杀业务.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/09 案例分析:池化对象的应用场景.md">09 案例分析:池化对象的应用场景.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/10 案例分析:大对象复用的目标和注意点.md">10 案例分析:大对象复用的目标和注意点.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/11 案例分析:如何用设计模式优化性能.md">11 案例分析:如何用设计模式优化性能.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/12 案例分析:并行计算让代码“飞”起来.md">12 案例分析:并行计算让代码“飞”起来.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/13 案例分析:多线程锁的优化.md">13 案例分析:多线程锁的优化.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/14 案例分析:乐观锁和无锁.md">14 案例分析:乐观锁和无锁.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/15 案例分析:从 BIO 到 NIO,再到 AIO.md">15 案例分析:从 BIO 到 NIO,再到 AIO.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/16 案例分析:常见 Java 代码优化法则.md">16 案例分析:常见 Java 代码优化法则.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/17 高级进阶:JVM 如何完成垃圾回收?.md">17 高级进阶:JVM 如何完成垃圾回收?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/18 高级进阶:JIT 如何影响 JVM 的性能?.md">18 高级进阶:JIT 如何影响 JVM 的性能?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/19 高级进阶:JVM 常见优化参数.md">19 高级进阶:JVM 常见优化参数.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/20 SpringBoot 服务性能优化.md">20 SpringBoot 服务性能优化.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/21 性能优化的过程方法与求职面经总结.md">21 性能优化的过程方法与求职面经总结.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/22 结束语 实践出真知.md">22 结束语 实践出真知.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>03 深入剖析:哪些资源,容易成为瓶颈?</h1>
|
||
|
||
<p>在第 02 课时,我们简单介绍了解决性能问题常用的一些切入点。本课时我将从计算机资源层面向你讲解,哪些系统组件容易出现性能瓶颈?以及如何判断该系统组件是否达到了瓶颈?</p>
|
||
|
||
<p>计算机各个组件之间的速度往往很不均衡,比如 CPU 和硬盘,比兔子和乌龟的速度差还大,那么按照我们前面介绍的木桶理论,可以说这个系统是存在着短板的。</p>
|
||
|
||
<p>当系统存在短板时,就会对性能造成较大的负面影响,比如当 CPU 的负载特别高时,任务就会排队,不能及时执行。而其中,CPU、内存、I/O 这三个系统组件,又往往容易成为瓶颈,所以接下来我会对这三方面分别进行讲解。</p>
|
||
|
||
<h1>CPU</h1>
|
||
|
||
<p>首先介绍计算机中最重要的计算组件中央处理器 CPU,围绕 CPU 一般我们可以:</p>
|
||
|
||
<ul>
|
||
|
||
<li>通过 top 命令,来观测 CPU 的性能;</li>
|
||
|
||
<li>通过负载,评估 CPU 任务执行的排队情况;</li>
|
||
|
||
<li>通过 vmstat,看 CPU 的繁忙程度。</li>
|
||
|
||
</ul>
|
||
|
||
<p>具体情况如下。</p>
|
||
|
||
<h2>1.top 命令 —— CPU 性能</h2>
|
||
|
||
<p>如下图,当进入 top 命令后,按 1 键即可看到每核 CPU 的运行指标和详细性能。</p>
|
||
|
||
<p><img src="assets/Ciqc1F8VRhOAOZZkAAIR0PdGn-M708.png" alt="Drawing 0.png" /></p>
|
||
|
||
<p>CPU 的使用有多个维度的指标,下面分别说明:</p>
|
||
|
||
<ul>
|
||
|
||
<li>us 用户态所占用的 CPU 百分比,即引用程序所耗费的 CPU;</li>
|
||
|
||
<li>sy 内核态所占用的 CPU 百分比,需要配合 vmstat 命令,查看上下文切换是否频繁;</li>
|
||
|
||
<li>ni 高优先级应用所占用的 CPU 百分比;</li>
|
||
|
||
<li>wa 等待 I/O 设备所占用的 CPU 百分比,经常使用它来判断 I/O 问题,过高输入输出设备可能存在非常明显的瓶颈;</li>
|
||
|
||
<li>hi 硬中断所占用的 CPU 百分比;</li>
|
||
|
||
<li>si 软中断所占用的 CPU 百分比;</li>
|
||
|
||
<li>st 在平常的服务器上这个值很少发生变动,因为它测量的是宿主机对虚拟机的影响,即虚拟机等待宿主机 CPU 的时间占比,这在一些超卖的云服务器上,经常发生;</li>
|
||
|
||
<li>id 空闲 CPU 百分比。</li>
|
||
|
||
</ul>
|
||
|
||
<p>一般地,我们比较关注空闲 CPU 的百分比,它可以从整体上体现 CPU 的利用情况。</p>
|
||
|
||
<h2>2.负载 —— CPU 任务排队情况</h2>
|
||
|
||
<p>如果我们评估 CPU 任务执行的排队情况,那么需要通过负载(load)来完成。除了 top 命令,使用 uptime 命令也能够查看负载情况,load 的效果是一样的,分别显示了最近 1min、5min、15min 的数值。</p>
|
||
|
||
<p><img src="assets/Ciqc1F8VRlWAKi0UAABUtUrc7Ec737.png" alt="Drawing 1.png" /></p>
|
||
|
||
<p>如上图所示,以单核操作系统为例,将 CPU 资源抽象成一条单向行驶的马路,则会发生以下三种情况:</p>
|
||
|
||
<ul>
|
||
|
||
<li>马路上的车只有 4 辆,车辆畅通无阻,load 大约是 0.5;</li>
|
||
|
||
<li>马路上的车有 8 辆,正好能首尾相接安全通过,此时 load 大约为 1;</li>
|
||
|
||
<li>马路上的车有 12 辆,除了在马路上的 8 辆车,还有 4 辆等在马路外面,需要排队,此时 load 大约为 1.5。</li>
|
||
|
||
</ul>
|
||
|
||
<p>那 load 为 1 代表的是啥?针对这个问题,误解还是比较多的。</p>
|
||
|
||
<p>很多人看到 load 的值达到 1,就认为系统负载已经到了极限。这在单核的硬件上没有问题,但在多核硬件上,这种描述就不完全正确,它还与 CPU 的个数有关。例如:</p>
|
||
|
||
<ul>
|
||
|
||
<li>单核的负载达到 1,总 load 的值约为 1;</li>
|
||
|
||
<li>双核的每核负载都达到 1,总 load 约为 2;</li>
|
||
|
||
<li>四核的每核负载都达到 1,总 load 约为 4。</li>
|
||
|
||
</ul>
|
||
|
||
<p>所以,对于一个 load 到了 10,却是 16 核的机器,你的系统还远没有达到负载极限。</p>
|
||
|
||
<h2>3.vmstat —— CPU 繁忙程度</h2>
|
||
|
||
<p>要看 CPU 的繁忙程度,可以通过 vmstat 命令,下图是 vmstat 命令的一些输出信息。</p>
|
||
|
||
<p><img src="assets/CgqCHl8VeDqAOXoUAAL5_mAD--A654.gif" alt="1111.gif" /></p>
|
||
|
||
<p>比较关注的有下面几列:</p>
|
||
|
||
<ul>
|
||
|
||
<li><strong>b</strong> 如果系统有负载问题,就可以看一下 b 列(Uninterruptible Sleep),它的意思是等待 I/O,可能是读盘或者写盘动作比较多;</li>
|
||
|
||
<li><strong>si/so</strong> 显示了交换分区的一些使用情况,交换分区对性能的影响比较大,需要格外关注;</li>
|
||
|
||
<li><strong>cs</strong> 每秒钟上下文切换(<strong>Context Switch</strong>)的数量,如果上下文切换过于频繁,就需要考虑是否是进程或者线程数开的过多。</li>
|
||
|
||
</ul>
|
||
|
||
<p>每个进程上下文切换的具体数量,可以通过查看内存映射文件获取,如下代码所示:</p>
|
||
|
||
<pre><code>[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="98eaf7f7ecd8f4f7fbf9f4f0f7ebec">[email protected]</a> ~]# cat /proc/2788/status
|
||
|
||
...
|
||
|
||
voluntary_ctxt_switches: 93950
|
||
|
||
nonvoluntary_ctxt_switches: 171204
|
||
|
||
</code></pre>
|
||
|
||
<h1>内存</h1>
|
||
|
||
<p>要想了解内存对性能的影响,则需要从操作系统层面来看一下内存的分布。</p>
|
||
|
||
<p><img src="assets/Ciqc1F8VdzuABJH4AAC4RXZgIoo881.png" alt="图片1.png" /></p>
|
||
|
||
<p>我们在平常写完代码后,比如写了一个 C++ 程序,去查看它的汇编,如果看到其中的内存地址,并不是实际的物理内存地址,那么应用程序所使用的,就是<strong>逻辑内存</strong>。学过计算机组成结构的同学应该都有了解。</p>
|
||
|
||
<p>逻辑地址可以映射到两个内存段上:<strong>物理内存</strong>和<strong>虚拟内存</strong>,那么整个系统可用的内存就是两者之和。比如你的物理内存是 4GB,分配了 8GB 的 SWAP 分区,那么应用可用的总内存就是 12GB。</p>
|
||
|
||
<h2>1. top 命令</h2>
|
||
|
||
<p><img src="assets/Ciqc1F8VRpyAJEDBAAGXn95jReA806.png" alt="Drawing 4.png" /></p>
|
||
|
||
<p>如上图所示,我们看一下内存的几个参数,从 top 命令可以看到几列数据,注意方块框起来的三个区域,解释如下:</p>
|
||
|
||
<ul>
|
||
|
||
<li><strong>VIRT</strong> 这里是指虚拟内存,一般比较大,不用做过多关注;</li>
|
||
|
||
<li><strong>RES</strong> 我们平常关注的是这一列的数值,它代表了进程实际占用的内存,平常在做监控时,主要监控的也是这个数值;</li>
|
||
|
||
<li><strong>SHR</strong> 指的是共享内存,比如可以复用的一些 so 文件等。</li>
|
||
|
||
</ul>
|
||
|
||
<h2>2. CPU 缓存</h2>
|
||
|
||
<p>由于 CPU 和内存之间的速度差异非常大,解决方式就是<strong>加入高速缓存</strong>。实际上,这些高速缓存往往会有多层,如下图所示。</p>
|
||
|
||
<p><img src="assets/Ciqc1F8Vd0qAZcrnAACrWqlXWc0083.png" alt="图片2.png" /></p>
|
||
|
||
<p>Java 有大部分知识点是围绕多线程的,那是因为,如果一个线程的时间片跨越了多个 CPU,那么就会存在同步问题。</p>
|
||
|
||
<p>在 Java 中,和 CPU 缓存相关的最典型的知识点,就是在并发编程中,针对 Cache line 的<strong>伪共享</strong>(False Sharing)问题。</p>
|
||
|
||
<p>伪共享指的是在这些高速缓存中,以缓存行为单位进行存储,哪怕你修改了缓存行中一个很小很小的数据,它都会整个刷新。所以,当多线程修改一些变量的值时,如果这些变量都在同一个缓存行里,就会造成频繁刷新,无意中影响彼此的性能。</p>
|
||
|
||
<p>CPU 的每个核,基本是相同的,我们拿 CPU0 来说,可以通过以下的命令查看它的缓存行大小,这个值一般是 64。</p>
|
||
|
||
<pre><code>cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
|
||
|
||
cat /sys/devices/system/cpu/cpu0/cache/index1/coherency_line_size
|
||
|
||
cat /sys/devices/system/cpu/cpu0/cache/index2/coherency_line_size
|
||
|
||
cat /sys/devices/system/cpu/cpu0/cache/index3/coherency_line_size
|
||
|
||
</code></pre>
|
||
|
||
<p>当然,通过 cpuinfo 也能得到一样的结果:</p>
|
||
|
||
<pre><code># cat /proc/cpuinfo | grep cache
|
||
|
||
cache size : 20480 KB
|
||
|
||
cache_alignment : 64
|
||
|
||
cache size : 20480 KB
|
||
|
||
cache_alignment : 64
|
||
|
||
cache size : 20480 KB
|
||
|
||
cache_alignment : 64
|
||
|
||
cache size : 20480 KB
|
||
|
||
cache_alignment : 64
|
||
|
||
</code></pre>
|
||
|
||
<p>在 JDK8 以上的版本,通过开启参数 -XX:-RestrictContended,就可以使用注解 @sun.misc.Contended 进行补齐,来避免伪共享的问题。具体情况,在 12 课时并行优化中,我们再详细讲解。</p>
|
||
|
||
<h2>3. HugePage</h2>
|
||
|
||
<p><img src="assets/CgqCHl8Vd1SAVNobAACyxjKDIxk345.png" alt="图片3.png" /></p>
|
||
|
||
<p>我们再回顾一下上文提到的这张图,上面有一个 TLB 组件,它的速度很快,但容量有限,在普通的 PC 机上没有什么瓶颈。但如果机器配置比较高,物理内存比较大,那就会产生非常多的映射表,CPU 的检索效率也会随之降低。</p>
|
||
|
||
<p>传统的页大小是 4KB,在大内存时代这个值偏小了,解决的办法就是增加页的尺寸,比如将其增加到 2MB,这样,就可以使用较少的映射表来管理大内存。而这种将页增大的技术,就是 <strong>Huge Page</strong>。</p>
|
||
|
||
<p><img src="assets/CgqCHl8Vd1-AVCnzAADX6l8HGwo687.png" alt="图片4.png" /></p>
|
||
|
||
<p>同时,HugePage 也伴随着一些副作用,比如竞争加剧,但在一些大内存的机器上,开启后在一定程度上会增加性能。</p>
|
||
|
||
<h2>4. 预先加载</h2>
|
||
|
||
<p>另外,一些程序的默认行为也会对性能有所影响,比如 JVM 的 -XX:+AlwaysPreTouch 参数。</p>
|
||
|
||
<p>默认情况下,JVM 虽然配置了 Xmx、Xms 等参数,指定堆的初始化大小和最大大小,但它的内存在真正用到时,才会分配;但如果加上 AlwaysPreTouch 这个参数,JVM 会在启动的时候,就把所有的内存预先分配。</p>
|
||
|
||
<p>这样,启动时虽然慢了些,但运行时的性能会增加。</p>
|
||
|
||
<h1>I/O</h1>
|
||
|
||
<p>I/O 设备可能是计算机里速度最慢的组件了,它指的不仅仅是硬盘,还包括外围的所有设备。那硬盘有多慢呢?我们不去探究不同设备的实现细节,直接看它的写入速度(数据未经过严格测试,仅作参考)。</p>
|
||
|
||
<p><img src="assets/Ciqc1F8VRxaAK34SAAHTZp7R44c733.png" alt="Drawing 8.png" /></p>
|
||
|
||
<p>如上图所示,可以看到普通磁盘的随机写与顺序写相差非常大,但顺序写与 CPU 内存依旧不在一个数量级上。</p>
|
||
|
||
<p><strong>缓冲区依然是解决速度差异的唯一工具</strong>,但在极端情况下,比如断电时,就产生了太多的不确定性,这时这些缓冲区,都容易丢。由于这部分内容的篇幅比较大,我将在第 06 课时专门讲解。</p>
|
||
|
||
<h2>1. iostat</h2>
|
||
|
||
<p>最能体现 I/O 繁忙程度的,就是 top 命令和 vmstat 命令中的 wa%。如果你的应用写了大量的日志,I/O wait 就可能非常高。</p>
|
||
|
||
<p><img src="assets/CgqCHl8VRzqALt_DAASmJQkN7Ro492.png" alt="Drawing 9.png" /></p>
|
||
|
||
<p>很多同学反馈到,不知道有哪些便捷好用的查看磁盘 I/O 的工具,其实 iostat 就是。你可以通过 sysstat 包进行安装。</p>
|
||
|
||
<p><img src="assets/CgqCHl8VR0KARZVeAAWCFxfk75s510.png" alt="Drawing 10.png" /></p>
|
||
|
||
<p>上图中的指标详细介绍如下所示。</p>
|
||
|
||
<ul>
|
||
|
||
<li><strong>%util</strong>:我们非常关注这个数值,通常情况下,这个数字超过 80%,就证明 I/O 的负荷已经非常严重了。</li>
|
||
|
||
<li><strong>Device</strong>:表示是哪块硬盘,如果你有多块磁盘,则会显示多行。</li>
|
||
|
||
<li><strong>avgqu-sz</strong>:平均请求队列的长度,这和十字路口排队的汽车也非常类似。显然,这个值越小越好。</li>
|
||
|
||
<li><strong>awai</strong>:响应时间包含了队列时间和服务时间,它有一个经验值。通常情况下应该是小于 5ms 的,如果这个值超过了 10ms,则证明等待的时间过长了。</li>
|
||
|
||
<li><strong>svctm</strong>:表示操作 I/O 的平均服务时间。你可以回忆一下第 01 课时的内容,在这里就是 AVG 的意思。svctm 和 await 是强相关的,如果它们比较接近,则表示 I/O 几乎没有等待,设备的性能很好;但如果 await 比 svctm 的值高出很多,则证明 I/O 的队列等待时间太长,进而系统上运行的应用程序将变慢。</li>
|
||
|
||
</ul>
|
||
|
||
<h2>2. 零拷贝</h2>
|
||
|
||
<p>硬盘上的数据,在发往网络之前,需要经过多次缓冲区的拷贝,以及用户空间和内核空间的多次切换。如果能减少一些拷贝的过程,效率就能提升,所以零拷贝应运而生。</p>
|
||
|
||
<p><strong>零拷贝</strong>是一种非常重要的性能优化手段,比如常见的 Kafka、Nginx 等,就使用了这种技术。我们来看一下有无零拷贝之间的区别。</p>
|
||
|
||
<p>(1)没有采取零拷贝手段</p>
|
||
|
||
<p>如下图所示,传统方式中要想将一个文件的内容通过 Socket 发送出去,则需要经过以下步骤:</p>
|
||
|
||
<ul>
|
||
|
||
<li>将文件内容拷贝到内核空间;</li>
|
||
|
||
<li>将内核空间内存的内容,拷贝到用户空间内存,比如 Java 应用读取 zip 文件;</li>
|
||
|
||
<li>用户空间将内容写入到内核空间的缓存中;</li>
|
||
|
||
<li>Socket 读取内核缓存中的内容,发送出去。</li>
|
||
|
||
</ul>
|
||
|
||
<p><img src="assets/CgqCHl8Vd26APrVIAAB4D39Hu4g101.png" alt="图片5.png" />
|
||
|
||
没有采取零拷贝手段的图</p>
|
||
|
||
<p>(2)采取了零拷贝手段</p>
|
||
|
||
<p>零拷贝有多种模式,我们用 sendfile 来举例。如下图所示,在内核的支持下,零拷贝少了一个步骤,那就是内核缓存向用户空间的拷贝,这样既节省了内存,也节省了 CPU 的调度时间,让效率更高。</p>
|
||
|
||
<p><img src="assets/Ciqc1F8Vd36AermYAACHMXg2gv0583.png" alt="图片6.png" />
|
||
|
||
采取了零拷贝手段的图</p>
|
||
|
||
<h1>小结</h1>
|
||
|
||
<p>本课时我们学习了计算机中对性能影响最大的三个组件:CPU、内存、I/O,并深入了解了观测它们性能的一些命令,这些方式可以帮我们大体猜测性能问题发生的地方。</p>
|
||
|
||
<p>但它们对性能问题,只能起到辅助作用,不能帮助我们精准地定位至真正的性能瓶颈,还需要做更多深入的排查工作,收集更多信息。</p>
|
||
|
||
<p>最后留一个思考题:磁盘的速度这么慢,为什么 Kafka 操作磁盘,吞吐量还能那么高?你可以先在留言区讨论,下一课时我会讲解。</p>
|
||
|
||
<p>不知你在实际的工作中是否还有其他的疑惑,欢迎留言讨论,我会一一解答~</p>
|
||
|
||
<p>在接下来的第 04 课时,我将介绍一系列更深入的工具,帮你获取性能数据,离“病灶”更近一步。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/02 理论分析:性能优化有章可循,谈谈常用的切入点.md">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/Java 性能优化实战-完/04 工具实践:如何获取代码性能数据?.md">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997124dc163d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
|
||
|
||
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
|
||
|
||
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
|
||
|
||
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
|
||
|
||
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
</html>
|
||
|