ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

进程完全指南:从原理到实战,CPU100%与杀不掉进程的排查

进程完全指南:从原理到实战,CPU100%与杀不掉进程的排查 说实话和“进程”打交道的这些年我印象最深的不是操作系统课本上的定义而是各种让人血压飙升的真实故障IDEA编译时把进程堆大小调到8000M照样报java.lang.OutOfMemoryError任务管理器里那个com.vortex.helper进程杀一个PID又冒一个MySQL启动时甩出一句“进程意外终止”错误码1067F盘提示被另一个进程锁定可你就是揪不出元凶。这些看似风马牛不相及的问题底层全拴在同一根绳上——进程。这篇内容我想换个方式讲不照搬教科书而是把进程从诞生到销毁、从单打到通信、从理论到实战讲透顺便把那些网上天天有人搜的“杀不掉的进程”“CPU占用100%”“进程资源图可化简”这些话题一并解释清楚。你要是开发、运维或者刚入行想搞明白任务管理器里那些乱七八糟的条目这篇应该够你用一阵子。1. 进程到底是个什么东西1.1 程序与进程菜谱和做菜的区别很多人把“程序”和“进程”搞混其实这两者的关系用厨房来类比最直观。程序是硬盘上那份静态的菜谱一行一行写着步骤你哪怕复制一万份它也不会自己变成菜。进程是厨师照着菜谱真刀真枪开火做菜的过程——有食材数据、有灶台内存、有颠勺的动作CPU指令执行。同一个菜谱能被多个厨师同时使用各做各的菜互不干扰同一个可执行文件也能被启动多次每次启动产生一个独立进程。比如你双击浏览器图标开了两个窗口背后就是两个进程在跑同一份代码只是各自的数据互不相干。所以进程的本质就是“程序的一次动态执行过程”。它不是一个文件不是一个ID而是一整套运行时状态当前执行到哪条指令、寄存器里是什么值、开了哪些文件、占了多少内存、和谁在通信。操作系统的内存管理、文件系统、CPU调度全部围绕进程展开。理解了这一点再看任务管理器里那一排排条目就不再是冷冰冰的名字和数字而是几十个“正在进行的烹饪现场”。我见过不少新手在排查问题时把一个进程文件删了、把进程杀了就以为万事大吉。其实程序文件只是静态的菜谱你删了菜谱锅里那盘菜还在炒着——进程只要有内存中的映像它就能继续跑。这也是为什么很多“清理工具”杀完进程还要强制删除文件、清理内存驻留的原因。1.2 进程的内在结构PCB、内存四区和那个谁都见过的PID每个进程在操作系统内核里都有一份专属档案叫PCB进程控制块正式点的说法是task_struct。你在任务管理器里看到的PID只是这份档案上最显眼的编号真正重要的是档案里记录的东西进程状态、程序计数器、寄存器上下文、内存限制、打开的文件描述符表、信号处理信息、父子进程关系等等。可以这么说PCB就是进程的“身份证病历本银行流水”三合一。进程的内存布局则分成四个主要区域这四块分工完全不同代码段存放机器指令只读谁也不能在执行中乱改自己的代码。数据段存放全局变量和静态变量进程启动时分配好生命周期和进程一致。堆动态分配的内存区域程序员用malloc、new申请的空间都在这里需要手动管理释放。栈函数调用时自动分配存放局部变量、函数参数、返回地址函数返回即自动销毁。打个比方进程像一家餐厅代码段是墙上那套规章制度谁来了都得照做数据段是仓库里固定的常备物资堆是后厨临时抓料的地方今天进多少货你自己说了算但要记得清理栈是服务员手里的点单记事本每接一单记一页接待完就撕掉。理解这四个区很重要因为后面讲Java的OutOfMemoryError、讲进程能创建多少线程全跟它们有关。PID这个东西值得一提它是进程的身份证号但绝不唯一稳定——操作系统会复用PID。你今天看到某个进程PID是1234过两天可能变成5678甚至刚杀掉一个进程新启动的进程马上占用同一个PID。所以排查问题时光记住PID没用要看PID对应的父进程、可执行文件路径、命令行参数。顺带说一句网上常有人搜“进程资源图可化简”这其实是操作系统里死锁检测的工具把进程和资源画成一幅有向图进程节点发申请边指向资源节点资源节点发分配边指向进程节点。如果存在一个顺序能让每个进程依次拿到所需资源、执行完毕、释放资源这个资源图就能化简说明当前没有死锁反之如果图上形成了一个谁也解不开的等待环那就是死锁化简不动了。你日常遇到两个进程互相锁着文件、谁都不放手画出来就是这么个环。2. 进程的一生从诞生到阴魂不散2.1 状态流转为什么越看系统卡住心里越有底进程从创建到终止不是在一条直线上跑而是在几个状态之间跳来跳去。理解状态流转是排查“系统为什么卡”的核心能力。核心状态就三个运行态进程正在CPU上执行指令此刻它是真的在干活。就绪态万事俱备只欠CPU。进程已经具备运行条件排队等着调度器给它分配CPU时间片。阻塞态进程在等待某个事件最常见的是等待I/O完成——比如磁盘读写、网络数据到达、用户输入。这种状态下进程不消耗CPU干等着。很多服务器卡顿的真相不是CPU不够而是大量进程阻塞在I/O上比如MySQL等磁盘、Tomcat等数据库返回。你打开top看到CPU占用率不高但系统响应很慢多半就是这种情况。反过来如果某个进程CPU占用率一直100%说明它长期处于运行态要么是真有密集计算要么是代码里死循环了。还有两个临界状态得补充一下创建态和终止态。创建态是进程正在被初始化内核分配PCB、加载代码和数据但还没就绪终止态是进程执行完或收到终止信号内核正在回收资源但还没完全清理干净——这个状态特别容易引出下面那个“阴魂不散”的话题。日常监控中你还会听到“前台进程”和“后台进程”的说法。前台进程是当前拥有终端焦点、能接收你键盘输入的进程后台进程一般是脱离终端或放在后台跑的。按下CtrlC时操作系统会向前台进程组发送中断信号而不是只发给一个进程。这也是为什么你用nohup或者setsid把服务放后台后关掉终端它还能活着——它已经不和你当前的前台会话挂钩了。2.2 进程等待wait与僵尸进程杀了一个PID另起一个的真实原因先解释搜索词里高频出现的“进程等待wait”。父进程创建子进程后经常需要知道子进程干得怎么样这时候Linux里用wait、waitpid系列系统调用父进程调用wait后会进入阻塞态直到某个子进程退出然后回收它的退出状态码。这就好比老板派员工出去办事老板坐在办公室等员工回来说结果等不到就一直等着。但问题就出在“回收”这两个字上。子进程退出后操作系统需要有人来收尸——读取退出状态、释放大部分资源内存、文件等。如果父进程死了没人收或者父进程自己忙得没空调用wait子进程退出后就会留下一个只包含PCB的空壳等着父进程来取那个不存在的“结果”。这种进程叫僵尸进程在ps输出里状态标记是Z。它不占内存不占CPU就占着内核里一个task_struct槽位但大量堆积会占满PID表导致系统无法创建新进程。更常见的场景是“杀了一个PID又换了一个”。很多朋友反馈任务管理器里有个com.vortex.helper进程杀掉没过几秒又冒出来PID还变了怀疑是不是遇到什么顽固病毒。其实真相往往很简单这个进程有一个父进程在当“守护者”它一死父进程立刻fork一个新实例顶上。你杀的是枪兵真正该处理的是召唤它的那个法师。正确做法是先查PID的父进程是谁Windows上可用Process Explorer或wmic命令Linux上用ps -ef --forest定位到父进程后先停掉父进程再清理子进程。同理网上很多人搜“未找到baidunetdiskhost进程”多半也是百度网盘的加速组件进程被安全软件清理后主程序仍尝试重新拉起结果组件路径都被删了就只剩一个“找不到进程”的尴尬状态。顺便把孤儿进程也讲了。父进程先挂了、子进程还没退出这种“没爹的孩子”叫孤儿进程。操作系统不会放任它流浪而是让它被init或systemd收养由这些1号进程周期性地替它收尸调用wait。所以Linux下孤儿进程一般是安全的不用太操心。2.3 守护进程与会话后台服务不关终端也能一直跑“守护进程”这个词在热搜里永远有位置和“会话”捆绑出现在一起。要理解守护进程得先明白会话是什么。会话Session是一个或多个进程组的集合。每次你打开终端登录系统会创建一个新会话这个会话有一个控制终端就是你那个终端窗口。普通前台进程依赖这个控制终端活着一旦终端关闭内核会向会话首进程发送SIGHUP挂断信号把整个会话里所有进程一起“带走”。服务器上程序跑着跑着你一关SSH窗口程序就死十有八九就是这个原因。守护进程要做的就是脱离这个会话。Linux下经典的守护化过程是调用setsid()本质是创建一个新会话成为新会话的首进程同时摆脱原控制终端。这样一来终端关闭、会话撤销都影响不了它。你平时用的nohup命令其实只是忽略SIGHUP信号和真正脱离会话还有差距systemd管理的服务则是完全由init进程接管根本不依赖任何用户会话。Windows上对应的概念是Windows服务由服务控制管理器SCM统一托管和桌面会话解耦。但守护进程不是永生的。它照样会被OOMOut Of Memory机制杀掉会因磁盘写满退出会因配置错误崩溃。这也是MySQL 1067和SQL Server“服务进程被异常终止”类故障的根源——服务托管只是解决“会话依赖”问题并不能保证进程本身健壮。关于这类故障的具体排查方法我放在第五部分详细展开。3. 进程和线程一张对比表解决问题3.1 进程是住的楼线程是楼里的住户“线程与进程”是操作系统领域长盛不衰的搜索关键词。我尽量用一张生活化的图来讲。进程是一栋楼有完整的独立地皮地址空间、独立的水电表资源所有权线程是楼里的住户共享这栋楼的电梯、水电、公共走廊进程内存、全局变量但每个住户有自己独立的私密空间线程栈、寄存器。进程是资源分配的基本单位线程是CPU调度的基本单位。一个进程至少要有一个主线程这个主线程死了整个进程通常也就结束了。核心区别写在下面这张表里看完基本心里有数对比维度进程线程资源拥有独立地址空间资源独占共享进程地址空间资源共用调度开销切换要换地址空间TLB可能失效开销大只需切换寄存器上下文开销小通信方式需要专门的IPC机制成本高共享内存即可读写但要注意同步容错性进程之间隔离一个崩了不影响别人线程崩溃如段错误可能拖垮整个进程创建成本需要分配独立PCB、地址空间成本高在进程基础上轻量创建成本低程序员最直观的感受是进程之间切换慢线程切换快得多。这也是为什么很多高性能服务用线程而不是多进程。但注意线程越多一定越好吗不是。每个线程都要独立的栈空间Linux默认线程栈8MBWindows默认1MB你无限制地开线程内存会先崩给你看。还要纠正一个常见的认知偏差Linux上进程和线程底层都对应一个task_struct创建线程用的是clone系统调用通过共享地址空间标志来区分。所以严格来说Linux里的线程叫“轻量级进程”更合适多线程程序在内核眼里就是一个线程组。这也是为什么你在一些老工具里看到一个个“进程”其实是线程的原因。3.2 为什么是Java进程OOM而不是线程OOM调整堆大小无效的真相热搜词里有这么一条“idea 编译时进程堆大小调整为8000还是报错java: java.lang.outofmemoryerror”。这个场景太典型了几乎每个Java程序员都踩过。先说第一个问题为什么是“Java进程OOM”而不是“线程OOM”。Java应用跑在一个JVM进程里JVM把内存划分成好几块区域堆放对象实例、元空间放类元数据、CodeCache放JIT编译后的机器码、线程栈每个线程独立。OutOfMemoryError本质上是JVM进程内部某一块区域满了最常见的是堆满了。线程栈满了不叫OOM会抛StackOverflowError。所以“进程OOM”其实是JVM这个进程里某一块内存区域达到上限后的结果和线程本身关系不大。再说第二个问题也是最坑的把堆调到8000M还报OOM大概率是调错进程了。IDEA本身是一个Java进程它的堆大小由idea.vmoptions里的-Xmx控制但你在IDEA里编译项目时真正干活的往往是另一个或另外几个独立的Java进程——Gradle Daemon、Kotlin Compiler Daemon、Maven本身。这些进程各有各的JVM配置它们的内存上限不是IDEA的-Xmx而是gradle.properties里的org.gradle.jvmargs等参数。你只调IDEA的堆等于只给老板加工资干活的员工工资一分没涨该OOM照样OOM。这背后还牵着一个更底层的道理进程有独立的地址空间和资源限制一个进程的堆调得再大也受两个硬约束——机器物理内存上限以及容器场景下的cgroup内存限制。如果物理内存是16G你给某个JVM进程分配-Xmx8000M它再叠加线程栈、元空间、CodeCache、GC开销很可能还没到堆上限整个进程就被操作系统的OOM Killer直接Kill了。这种时候你看到的甚至不是OutOfMemoryError而是进程退出码137被信号9杀死。所以调堆大小时先确认是哪一份进程配置在用再确认机器/容器总共富余多少内存。线程栈和进程的内存也有直接关系。一个进程能创建的线程数大致是“可用内存/线程栈大小”。比如你给JVM的-Xmx留了4G但线程栈默认1MB那最多也就几千个线程线程一多创建线程时就会报“unable to create new native thread”——看起来像内存不足其实是被栈空间给限制了。这部分调优经验我也是吃了不少亏才总结出来的。4. 进程之间的交流IPC的几种身法4.1 IPC家族管道、消息队列、共享内存、信号量进程和进程之间默认是“老死不相往来”的因为地址空间互相隔离。但现实业务中进程之间非要互相递消息不可这就有了进程间通信英文缩写IPC。它们的本质都是在隔离的进程之间凿一条数据通路。最常见的几种我按使用频率排一下管道半双工字节流数据是单向流动的。匿名管道只能用于父子进程之间使用起来最简单shell里的竖线“|”就是典型的管道。命名管道FIFO则允许任意两个进程通信。管道的容量有限Linux上通常几十KB写满了写方会阻塞读完了读方也会阻塞。它天然适合“一边生产、一边消费”的流水线模式。我见过很多新手拿管道传大文件结果卡在半路就是因为没搞懂“管道满了写端阻塞”这个特性。消息队列由内核维护的一个消息链表进程往里写入一条条带类型标记的消息其他进程按类型读取。好处是异步解耦发送方把消息丢进队列就能干别的坏处是数据需要在内核态和用户态之间多次拷贝在高频大量的小消息场景下性能并不理想。共享内存把同一块物理内存映射到多个进程的地址空间里多个进程直接读同一块“黑板”。这是最快的IPC方式因为不需要数据拷贝实时性最好。但快是要付出代价的——多个进程同时写就会出现数据错乱必须配合同步机制使用。信号量本质是一个计数器加两个原子操作P操作减一V操作加一用来控制多个进程对共享资源的访问。你可以把它理解成餐厅的厕所钥匙想上厕所先拿钥匙P操作没有钥匙就在门口排队上完归还钥匙V操作下一个才能进去。信号量本身不算“数据通信”但它是最重要的进程同步工具共享内存几乎离不开它。信号Signal这套机制用于异步通知比如kill -TERM给进程发终止信号。它传递的不是复杂数据而是“发生了什么事”这个事件本身。进程可以设置信号处理函数收到信号后执行清理动作。Socket严格说Socket不限于本机跨机器的进程通信靠它同一台机器上也能用Unix Domain Socket做高性能本地通信。很多数据库和中间件本机通信走的就是Unix Socket比TCP回环更快。4.2 实际工程里的选型与进程池选哪种IPC不是看哪个“高级”而是看数据量和实时性要求。我的经验大致是小数据量、低频率的异步任务用消息队列最合适高频、大流量的数据共享要用共享内存加信号量涉及跨机器的通信直接上TCP或Unix Domain Socket单纯想做资源互斥文件锁和信号量都能胜任。这里特别说一个容易混淆的点。有朋友问F盘被另一个进程锁定能不能用IPC来解决答案是方向反了。IPC解决的是“数据怎么传”而文件占用是“资源怎么互斥”。Windows上文件被占用本质是某个进程持有了该文件的内核句柄系统层面的锁机制在起作用。你要做的是找到持有句柄的进程释放或终止它而不是研究怎么和占用者通信。这个话题我在第五部分接着讲排查方法。另外搜热词里还有“进程池”一并说透。进程池的思路很简单预先创建一批工作进程放在池子里来了任务派一个出去处理处理完回收复用避免频繁创建销毁进程带来的fork开销和调度开销。但进程池不是越大越好每个进程都有独立地址空间和资源消耗进程数一旦超过CPU核数和内存承载能力光上下文切换就能把系统拖垮。我给你一个判断思路先估算单进程平均内存占用用系统可用内存除以它就是上限再结合CPU核数计算密集型的进程池数量控制在核数附近I/O密集型的可以适当放大但最好配合压力测试来定别拍脑袋设个几百上千。5. 从概念到实战那些被进程折腾的真实经历5.1 系统里那些“钉子户”进程msedgewebview2.exe、thunderplatform杀不掉的组件搜热词里有一批高频问题msedgewebview2.exe进程如何关闭thunderplatform占用进程一直出现com.vortex.helper这个进程。这些问题背后的逻辑高度统一——它们都是某个应用的“组件进程”由主程序负责拉起和守护所以你怎么杀都杀不干净。msedgewebview2.exe是Edge WebView2 Runtime的运行库进程。很多桌面软件微信、小米助手、各种客户端都拿它来嵌入网页渲染功能它本身不是病毒但确实经常被宿主程序悄悄拉起来。你杀了它宿主程序一检测到异常又会重新初始化一个。正确的关闭顺序是先退出宿主程序再确认WebView2相关的后台服务是否停止最后在程序设置里关闭“使用内置浏览器”“硬件加速”之类的选项。要是还不行用组策略或配置项禁用WebView2在特定应用里的自启动但别直接删掉系统级运行时否则依赖它的软件会连锁出问题。thunderplatform这类迅雷组件进程和百度网盘搜索词里的baidunetdiskhost是同一流派在你清理了它的可执行文件、或者安全软件拦截了它之后主程序还是执着地按照配置去拉起子进程于是出现“进程找不到但加载动作不停”的诡异现象。碰到这种我先打开任务管理器的“详细信息”标签页右键看“打开文件所在位置”和“命令行”确认它的来路然后打开服务管理器和启动项管理工具Windows自带的msconfig或者Sysinternals Autoruns把对应的计划任务、服务、启动项全禁掉再重启问题才算真正根除。只靠任务管理器里杀进程就像只拔草不除根野火烧不尽。5.2 定位进程占用与CPU 100%的排查套路“Windows cpu占用率一直100”“syatem进程占CPU高”“F盘被另一个进程锁定”……这些热搜词的背后其实是同一套排查方法论。我分平台分享一下我的实操流程。在Windows上我推荐按这个顺序来第一步先看进程树。任务管理器不够用直接上Sysinternals Process Explorer。它能清楚地显示每个进程的父进程PID、完整路径、命令行参数一眼就能看出谁是谁拉起来的。如果要纯命令行可以用wmicwmic process get name,processid,parentprocessid,executablepath,commandline或者新版PowerShell用Get-CimInstanceGet-CimInstance Win32_Process | Select-Object Name,ProcessId,ParentProcessId,ExecutablePath第二步查句柄和模块。F盘被另一个进程锁定打开Process Explorer用菜单里的Find Find Handle or DLL输入“F:\”或具体的文件名它直接告诉你哪个进程握着这个句柄。Linux上对应的是lsof命令lsof /mnt/fdata/locked.file lsof D /var/log第三步查网络和端口占用。netstat -ano列出所有连接和监听端口及对应PID然后按PID倒查进程netstat -ano | findstr :3306 tasklist /fi PID eq 7744如果是排查某个网络端口被谁抢占顺手还能用“协议栈进程排除”的思路——把疑似的进程从网络流量统计中临时剥离观察端口占用或流量是否消失从而确认“凶手”。这个操作在很多网络分析工具里都有属于排查技巧里比较实用的手段。第四步CPU 100%要看“是谁”的CPU。如果占用高的是system进程重点怀疑驱动问题、虚拟化组件、杀毒软件钩子如果是syatem这类伪装成系统进程的第三方进程先看路径是否在系统目录、是否有数字签名如果是一个业务进程用Process Explorer看它的线程列表哪个线程CPU占用高再双击看线程栈能看到具体卡在哪个模块里。Linux上就顺手多了一条命令看清楚进程树ps -ef --forest top -b -n 1 -o %CPU sudo perf top我举个例子有一回线上服务CPU飙到100%我先top找到PID再ps --forest看进程父子关系发现是WebServer的子进程在空转。进/proc/PID/status看进程状态然后通过gdb或perf attach到该线程看到栈上是一个正则回环匹配在死循环。整个定位过程不到十分钟比你瞎杀进程高效得多。5.3 MySQL 1067与SQL Server服务进程异常终止实录最后聊聊服务进程的“离奇猝死”。Windows上装MySQL的老用户对错误码1067应该不陌生——服务启动时报“进程意外终止”。SQL Server 2000也有类似的“服务进程被异常终止无法继续提供服务”。这些问题的排查核心原则就一条先看日志再看配置最后才考虑重装。MySQL 1067的排查顺序我的经验是打开MySQL数据目录下的.err日志文件看最后几十行。里面一般会直接写原因比如“unknown variable”表示配置项写错了“Cant start server: Bind on TCP/IP port”说明端口已被占用“Permission denied”说明目录权限不对。检查my.ini配置重点看basedir和datadir路径有没有写错、反斜杠有没有转义。不少朋友把data目录和安装目录搞混路径指错直接就起不来。检查3306端口是否被占用。如果被占但占用者不是mysqld先确认那个进程是什么再决定停哪个别误杀。检查磁盘空间和内存。磁盘满了MySQL写不了临时文件铁定起不来内存不够时服务进程也会被系统强制杀掉这在Windows事件日志里有记录。看Windows事件查看器里的“服务控制管理器”日志服务启动失败的原因会在那里留下线索。SQL Server 2000的“服务进程被异常终止”也是同一套路重点是SQL Server错误日志C:\Program Files\Microsoft SQL Server\MSSQL\LOG\ERRORLOG日志里如果出现类似“FlushCache: badly formatted log file”的提示多半是事务日志文件损坏如果出现内存分配错误多半是系统内存或SQL Server内存上限配得太低。SQL Server在2000时代对内存的占用策略又激进又脆弱只要物理内存吃紧它可能连缩都缩不回来直接死给你看。还有一个所有服务进程都逃不掉的杀手操作系统的OOM机制。Linux的OOM Killer挑选进程时不完全看谁内存占得多还要看oom_score默认模式下谁占得“过分”谁就中枪。MySQL即使成了守护进程照样可能被它选中。应对方法不复杂要么升级物理内存要么给关键进程配置oom_score_adj较低值要么在MySQL配置里严格限制buffer pool等内存参数把它的胃口压住。最后说几句踩坑换来的心得我一直觉得进程这个概念不是背完就丢的考点而是排查所有运行时问题的地图。最后分享一个我自己的工作习惯不管在Windows还是Linux上排查问题我头一件事永远是列进程树、看父子关系。Windows上开Process ExplorerLinux上先敲ps -ef --forest然后才去查句柄、端口和日志。这套习惯养成之后以前要折腾一晚上的“杀不掉的进程”“找不到元凶的文件占用”基本都能在几分钟内定位到根上。也有过不少次我以为遇到了什么高强度病毒冷静下来一查不过是个被父进程反复拉起的正常组件。记住这句话看到陌生PID先别急着杀先搞清楚它背后的爸爸是谁。很多人觉得这是糙话但道理是真道理——进程世界里的因果关系比大多数你以为的“灵异事件”要朴素得多。
返回列表