1. 僵尸进程
① 僵尸进程概述
- 什么是僵尸进程?
- 在Linux系统中,任何一个子进程在调用
exit()
函数结束运行后,内核会释放该进程的所有资源,包括占用的内存和打开的文件等。 - 同时,也会留下一个叫做僵尸进程(
Zombie
)的数据结构,Zombie中存储了该进程的进程号、退出码、退出状态、使用的CPU时间等信息。即僵尸进程是早已死亡的子进程,但在进程表中占了一个位置(slot)。 - 子进程还会向父进程发送SIGCHLD信号,父进程调用
wait()
或者waitpid()
函数可以将僵尸进程释放(为它收尸)。 - 父进程在没有释放掉僵尸进程就提前结束了,僵尸进程则会由init进程接管。
init
进程(PID = 1
)会作为它的父进程,为它收尸。 - 但是如果父进程是一个循环,不会结束,却又没有为
SIGCHLD
信号绑定处理函数,或者没有调用wait()/waitpid()
函数为僵尸进程收尸,则该僵尸进程会一直在系统中存在。
- 僵尸进程的危害:
- 如果系统中存在很多僵尸进程,进程号会被它们一直占用。
- 这时,有限的进程号将会耗尽,使得系统无法创建新的进程。
② 如何杀死僵尸进程?
- 查看系统中是否存在僵尸进程
- Linux中的
top
命令可以实时显式系统中各个进程的资源占用情况。因此,可以先通过top
命令查看系统中是否存在僵尸进程。 - 输入
top
命令后的部分内容如下:其中zombie
前面的数字表示当前系统中存在的僵尸进程数。
Tasks: 337 total, 1 running, 327 sleeping, 0 stopped, 9 zombie
Cpu(s): 0.0%us, 0.0%sy, 0.0%ni, 54.3%id, 45.7%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 64417M total, 42611M used, 21806M free, 10924M buffers
Swap: 32764M total, 684M used, 32080M free, 28841M cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
9563 root 20 0 9040 1312 820 R 0 0.0 0:00.19 top
15216 root 20 0 0 0 0 S 0 0.0 5:45.61 kworker/22:1
1 root 20 0 10528 724 692 S 0 0.0 2:47.29 init
2 root 20 0 0 0 0 S 0 0.0 0:16.94 kthreadd
3 root 20 0 0 0 0 S 0 0.0 9:13.20 ksoftirqd/0
6 root RT 0 0 0 0 S 0 0.0 94846:34 migration/0
- 查看具体是哪些进程为僵尸进程
- 状态为Z,后面有
defunct
标记的进程就是僵尸进程。 - 可以通过
ps -ef | grep defunct
命令查看具体的僵尸进程。
- 上述命令返回的结果中,第三列就是该僵尸进程的父进程,可以通过
kill -9 PPID
杀死其父进程。之后,僵尸进程将由init进程接手,会被init进程释放。
③ 如何避免僵尸进程?
- 父进程收到
SIGCHLD
信号后,调用wait()
或者waitpid()
函数释放僵尸进程。但是,这样会导致父进程挂起。 - 如果父进程很忙,可以使用
signal
函数为SIGCHLD
信号安装handler,handler中会调用wait函数回收僵尸进程。子进程结束后,父进程收到SIGCHLD
信号后会执行handler。 - 父进程显式地表示自己对子进程的结束不感兴趣:
- 父进程如果不关心子进程什么时候结束,可以通过
signal(SIGCLD, SIG_IGN)
或者signal(SIGCHLD, SIG_IGN)
通知内核,自己对子进程的结束不感兴趣。 - 子进程结束后,内核会释放僵尸进程,并不在给父进程发送信号。
- fokr两次:
- 父进程fork一个子进程,然后继续工作
- 子进程fork一个孙进程后退出,孙进程将由
init
进程接管;孙进程结束后,init
进程会对其进程回收。 - 子进程的回收还是需要父进程自己去完成
2. 孤儿进程(orphan)
- 孤儿进程:
- 父进程退出,而它的一个或多个子进程还在运行,这些子进程将成为孤儿进程(
orphan process
)。 - 估计进程将会被
init
进程收养,并由init
进程完成对它们的状态收集工作。
- 由于孤儿进程会被
init
进程收养,因此孤儿进程调用exit()
结束运行时,也会由init
进程完成回收工作,而不会对系统造成危害。 - 孤儿进程与僵尸进程的区别:
- 孤儿进程: 子进程未运行结束,父进程却提前计数,这时子进程将会成为孤儿进程被init进程收养。
init
进程会完成对孤儿进程的回收工作,孤儿进程对系统没有危害。 - 僵尸进程: 子进程运行结束,父进程没有为
SIGCHLD
信号设置处理函数,或者调用wait()/waitpid()
函数对其进程回收,成为系统中的僵尸进程。僵尸进程会占用系统中有限的进程号,导致系统无法创建新进程。因此,僵尸进程对系统有危害。
3. 守护进程(Daemon)
① 什么是守护进程?
Linux Daemon
(守护进程)是运行在后台的一种特殊进程,独立于控制终端,并且周期性的执行某种任务或者等待处理某些发生的事件。- 守护进程不需要用户输入就能运行,它可以提供某种服务。并且不是对系统提供服务,就是对某个应用程序提供服务。
- Linux中大多数的服务器就是通过守护进程实现,如系统日志进程
syslogd
、数据库服务器mysqld
、web服务器httpd
等。 - 守护进程一般在系统启动时就开启了,除非强制终止,否则直到系统关机都保持运行。
- 因为需要使用特殊端口
1~1024
访问某些特殊的资源,守护进程经常以超级用户(root
)权限运行。 - 守护进程的父进程是init进程: 创建守护进程时,父进程在fork出子进程后就提前结束运行了。守护进程将会变成孤儿进程,由
init
进程收养。 - 守护进程是非交互式程序,没有控制终端,无论是标准输出设备
stdout
还是标准出错设备stderr
的输出都需要进行特殊处理。 - 守护进程的名称通常以d结尾,如sshd、xinetd、crond等。
② 如何创建守护进程?
- 父进程
fork()
出子进程,然后提前调用exit()
结束运行。 - 在子进程中调用
setsid()
函数创建新的会话。 - 再次fork出一个子进程并让父进程退出。
- 在子进程中调用
chdir()
函数,让根目录成为子进程的工作目录。 - 在子进程中调用
unmask()
函数,设置进程的文件权限码为0。 - 在子进程中关闭任何不需要的文件描述符。
4. Linux中的常见命令
- 用于文件操作的常见命令:
cp
(拷贝)、rm
(删除)、mkdir
(创建)、cd
(切换目录)、mv
(改名)、ls
(罗列文件/文件夹)、tar
(解压缩)、chmod
(更改权限)、chown
(更改所有者) - 用于系统进程操作的常见命令:
top/htop
(查看系统中所有进程实时运行情况)、ps
(列出系统中的进程)、lsof
(查看某个端口是否被占用)、kill
(杀死某个进程)、iotop
(监控磁盘I/O情况)、ifconfig
(查看本机IP)
5. 进程和线程的常见问题总结
1. 进程的状态转换?
- 五种或者带换出的六种
2. 进程和线程的区别?
- 总体区别: 进程是资源分配的基本单位,线程是CPU调度的基本单位(用工厂车间和生产线去讲解)
- 细分区别: 资源、调度、开销(创建和销毁的开销、上下文切换的开销)、通信方式
- Linux中: Linux中内核调度的基本单位
task_struct
,内核线程和用户线程
3. 进程间的通信方式
- 管道(半双工、要求亲缘关系)
- FIFO(半双工、不要求亲缘关系)
- 信号量(PV操作、互斥量,生产者-消费者问题)
- 信号
- 消息队列(无须考虑同步、有选择的接收消息)
- 共享内存(减少进程间的数据拷贝)
- socket(不同机器间的进程通信)
4. 进程同步中的临界区有什么处理方法?
- 使用互斥量
Mutex
或synchronized
关键字
5. 僵尸进程如何检测?
- 基础:
top
命令查看是否存在僵尸进程,ps -ef | grep defunct
查看僵尸进程具体信息 - 进阶:
kill
命令杀死其父进程 - 高阶: 僵尸进程的形成、危害、如何避免
5. 线程启动的方法
- 创建的方法: 继承
Thread
类、重写Runnable
接口、重写Callable
接口 - 启动的方法: 只有
start()
6. Linux进程管理的相关命令
top/htop
命令、ps
命令、lsof
命令、kill
命令
7. Java中哪些地方会发生OOM?一个进程有3个线程,如果一个线程抛出OOM,其他两个线程还能运行么?
- JVM栈和本地方法栈动态扩展扩展时无法申请到足够的内存,会抛出
OutOfMemoryException
异常。 - 堆和方法区无法满足内存分配需求时,会抛出
OutOfMemoryException
异常。 - 一个线程OOM,其他线程仍然可以运行;主线程OOM,其他线程仍然可以运行。
8. Java进程挂掉,Linux下如何处理?
- Java程序自身问题导致OOM?
- JVM或JDK自身的bug导致crash?
- 查看系统日志是否被Linux的OOM-killer杀掉,查看是Java程序内存占用过大,还是有其他进程占用了大量内存?
9. 进程和线程那个快?线程之间的通信?
- 线程更快:创建和销毁、上下文切换都是线程快
- 线程之间通过共享数据进行通信。