打算给咱们部门弄个内部分享。发现你们对一些底层知识的认知停留在一句一句的,好比据说JVM使用-XX:-UseBiasedLocking取消偏向锁能够提升性能,由于它只适用于非多线程高并发应用。使用数字对象的缓存-XX:AutoBoxCacheMax=20000比默认缓存-128~127要提升性能。对于JVM和linux内核,操做系统没有系统的概念,遇到实际问题每每没有思路。因此个人内部分享,主要分为linux部分,jvm部分和redis部分。这篇是linux篇。学习思路为主,知识为辅。我也是菜鸟一枚~~不过是个钻石心的菜鸟,不怕别人知道我有多菜。java
先说为何我要去学习linux内核。我在上家公司负责整个公司的搜索引擎。有一次很熟练的在一台虚拟机上新搭建了一套,压测到8000,额,报了一个NIO异常,说是:too many open files。当时查了一下,那台机器太破,和不少服务公用,内存快满了。因此换了台好点的机器就没有这个问题了。可是句柄超限究竟是个什么东西呢?先来看看linux内核的一些基本概念。linux
大局观嘛,先来看看unix的体系结构。redis
简单解释一下:任何计算机系统都包含一个基本的程序集合,它控制计算机硬件资源,提供程序运行环境。称为操做系统。在这个集合里,最重要的程序被称为内核,在系统启动时被装载。由于它相对较小,并且位于环境的核心。内核的接口被称为系统调用(system call)。公用函数库构建在系统调用接口之上,也可以使用系统调用。shell是一个特殊的应用程序,为运行其余应用程序提供一个接口。shell
一些操做系统容许全部的用户程序直接与硬件部分进行交互,如MS-DOS。可是类Unix操做系统在胡勇应用程序前把与计算机物理组织相关的全部底层细节隐藏了。当程序想使用硬件资源时,必须向操做系统发出一个请求,内核对这个请求进行评估,若是容许使用这个资源,内核表明应用程序与相关的硬件部分进行交互。为了实施这种机制,现代操做系统依靠特殊的硬件特性来禁止用户程序直接与底层硬件部分打交道,或者直接访问任意的物理地址。硬件为CPU引入了至少两种不一样的执行模式:用户程序的非特权模式和内核的特权模式。Unix把他们分别称为用户态(User Mode)和内核态(Kernel Model)。编程
咱们平时敲的一些linux命令,实际上都是对应的内核的C语言函数。好比cat xxx | grep 'x'。这里面两个命令用|链接起来,这个叫作“管道”。先用男孩纸惯用的职业一点的语言介绍一下:管道是一个普遍应用的进程间通讯手段。其做用是在具备亲缘关系的进程之间传递消息,所谓有亲缘关系,是指有同一个祖先。能够是父子,兄弟或者祖孙等等。反正只要共同的祖先调用了pipe函数,打开的管道文件会在fork以后,被各个后代所共享。其本质是内核维护了一块缓冲区与管道文件相关联,对管道文件的操做,被内核转换成对这块缓冲区内存的操做。分为匿名管道和命名管道。缓存
这里面包含了一些概念。进程的概念你们都应该很清楚:程序的执行实例被称为进程。UNIX系统确保每一个进程都有一个惟一的数字表示符,称为进程ID(process ID),它是一个非负数。linux不少命令都会将其显示出来。有3个用于进程控制的主要函数:fork,exec和waitpid。其中fork函数用来建立一个新进程,此进程是调用进程的一个副本,称为子进程。fork对父进程返回新的子进程的进程ID(一个非负整数),对子进程则返回0。由于fork建立一个新进程,因此说它被调用一次,但返回两次。服务器
一个进程内的全部线程共享同一地址空间,文件描述符,栈以及进程相关的属性。由于它们能访问同一存储区,因此各线程在访问共享数据时须要采起同步措施以免不一致性。说到这里你们都应该多少有些概念了:为何进程开销大,线程涉及锁。网络
匿名管道是一个未命名的,单向管道,经过父进程和一个子进程之间传输数据。只能实现本地机器上两个进程之间的通讯,而不能实现跨网络的通讯。经常使用的好比linux命令。数据结构
命名管道是进程间单向或双向管道,创建时指定一个名字,任何进程均可以经过该名字打开管道的另外一端,可跨网络通讯。多线程
这是一个jvisualvm调试的截图,蓝框部分就至关于一个命名管道。
好,如今来回答一个问题:用户进程间通讯主要哪几种方式?
刚才说的匿名管道和命名管道都算一种。除此以外,还有:信号,消息队列,共享内存,信号量和套接字。不用头疼,看到最后你极可能会有豁然开朗的感受,学的东西终于能够串在一块儿了。
信号(signal):实际上是软中断信号的简称。用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求是同样的。信号是进程间通讯机制中惟一的异步通讯机制,一个进程没必要经过任何操做来等待信号的到达。
收到信号的进程对各类信号有不一样的处理方法,主要是三类:
1>相似中断的处理程序,对于须要处理的信号,进程能够指定处理函数,由该函数来处理。
2>忽略某个信号,对该信号不作任何处理。
3>对该信号的处理保留系统的默认值,这种缺省操做,对大部分的信号的缺省操做是让进程终止。进程经过系统调用signal来指定进程对某个信号的处理行为。
下面是window的信号列表
linux也是用kill -l命令:
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
我在用gdb命令运行调试C语言程序的时候常常能够看到这些信号量。
再来看消息队列。消息队列提供了一种从一个进程向另外一个进程发送一个数据块的方法。每一个数据块都被认为含有一个类型,接收进程能够独立的接收含有不一样类型的数据结构。能够经过发送消息来避免命名管道的同步和阻塞问题。可是消息队列和命名管道同样,每一个数据块都有一个最大长度的限制。
共享内存就是容许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种很是有效的方式。不一样进程之间共享的内存一般安排为同一段物理内存。进程能够将同一段共享内存链接到他们本身的地址空间中,全部进程均可以访问共享内存中的地址。
信号量:为了防止出现因多个程序同时访问一个共享资源而引起的一系列问题,咱们须要一种方法,它能够经过生成并使用令牌来受权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码须要独占式的执行。而信号量就能够提供这样的一种访问机制。让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来协调对共享资源访问的。
套接字:这种通讯机制使得客户端/服务器的开发工做既能够在本地单机上进行,也能够跨网络进行。它的特性有三个属性肯定:域(domain),类型(type)和协议(protocol)。简单的说:源IP地址和目的IP地址以及源端口号和目的端口号的组合成为套接字。
下面介绍一下通讯过程,里面涉及一些C语言的函数,不用怕,眼熟便可。若是你学习过nio,你会发现这些是很常接触的。
要想使不一样主机的进程通讯,就必须使用套接字,套接字是用socket()函数建立,若是须要C/S模式,则须要把server的套接字与地址和端口绑定起来,使用bind(),当上述操做完成后,即可使用listen()来监听这个端口,若是有其余程序来connect,那么server将会调用accept()来接受这个申请并为其服务。client是调用connect()来创建与server之间的链接,这时会使用三次握手来创建一条数据连接。当链接被创建后,server与client即可以通讯了,通讯可使用read()/write(),send()/recv(),sendto()/recvfrom()等函数来实现,可是不一样的函数做用和使用位置是不一样的。当数据传送完后,能够调用close()来关闭server与client之间的连接。
到此,本篇文章的主要内容就没有了,基本就在介绍一个东西:linux内核的进程通讯。这是学习任何高级编程语言nio部分的基础。下面引入一些辅助理解的概念。
文件句柄:在文件I/O中,要从一个文件读取数据,应用程序首先要调用操做系统函数并传送文件名,并选一个到该文件的路径来打开文件。该函数取回一个顺序号,即文件句柄(file handle),该文件句柄对于打开的文件是惟一的识别依据。一个句柄就是你给一个文件,设备,套接字(socket)或者管道的一个名字,以便帮助你记住你证处理的名字,并隐藏某些缓存等的复杂性。说白了就是文件指针啦。
文件描述符:内核利用文件描述符来访问文件。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也须要使用文件描述符来指定待读写的文件。文件描述符形式上是非负整数,实际上它是一个索引值,指向内核为每个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者建立一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写每每会围绕着文件描述符展开。可是文件描述符每每值适用于unix,linux这样的操做系统。习惯上,标准输入的文件描述符是0,标准输出是1,标准错误是2.
`/letv/apps/jdk/bin/java -DappPort=4 $JAVA_OPTS -cp $PHOME/conf:$PHOME/lib/* com.letv.mms.transmission.http.VideoFullServerBootstrap $1 $3 > /dev/null 2>&1 &`
本身部署过java后台程序的话,对上面的shell命令应该都能理解。 /dev/null 2>&1 这里面的2就是文件描述符,这个是将错误输出到文件。
这两个概念比较绕,不用过多区分,能够当成一回事来理解。打开文件(open files)包括文件句柄但不只限于文件句柄,因为lnux全部的事务都以文件的形式存在,要使用诸如共享内存,信号量,消息队列,内存映射等都会打开文件,但这些不会占用文件句柄。查看进程容许打开的最大文件句柄数的linux命令:ulimit -n
好了,今天的概念都介绍完了,回到最初的问题:too many open files。 当时的机器破,内存快满了。因此搜索引擎走的是索引文件,有不少的IO操做,共享内存和内存映射这块的文件确定是供不上的,报错了。萦绕在心头两年的问题稍微有点认知了。
跑题时间:
每当我打喷嚏的时候,我就在想究竟是谁在想我了。虽然明知道打喷嚏的缘由是刚进了一间有浮尘的屋子,或者是空中飘着的柳絮。ねえ、わたしのこと、おぼえてる?