编译型语言:程序在执行之前需要一个专门的编译过程,把程序编译成为机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了。因此效率比较高。比如 C 语言。
解释型语言:程序不需要编译,程序在运行时才翻译成机器语言,每执行一次都要翻译一次。因此效率比较低。比如Basic语言,专门有一个解释器能够直接执行Basic程序,每个语句都是执行的时候才翻译。
C语言是编译型的。C程序——>机器语言(编译)
Java比较特殊,Java程序也需要编译,但是没有直接编译成机器语言,而是编译成字节码,然后用解释方式执行字节码。 Java程序—— >字节码(编译)—— >机器语言(解释)
JVM 主要由 ClassLoader 和 执行引擎 两子系统 组成,运行数据区分为五个部分: 方法区、堆、栈、程序计数器、本地方法栈。其中的方法区和堆是所有线程共享的,JVM将临时变量放在栈中,每个线程都有自己独立的栈空间和程序计数器。
任何一个Java类的main函数运行都会创建一个JVM实例,当main函数结束时,JVM实例也就结束了。JVM实例启动时默认启动几个守护线程,比如:垃圾回收的线程,而 main 方法的执行是在一个单独的非守护线程中执行的。只要母线程结束,子线程就自动销毁,只要非守护main 线程结束JVM实例就销毁了。
那么在Java类main函数运行过程中,JVM的工作原理如下:
(1) 根据系统环境变量,创建装载JVM的环境与配置;
(2) 寻找JRE目录,寻找jvm.dll,并装载jvm.dll;
(3) 根据JVM的参数配置,如:内存参数,初始化jvm实例;
(4) JVM实例产生一个引导类加载器实例(Bootstrap Loader),加载Java核心库,然后引导类加载器自动加载扩展类加载器(Extended Loader),加载Java扩展库,最后扩展类加载器自动加载系统类加载器(AppClass Loader),加载当前的Java类;
(5) 当前Java类加载至内存后,会经过验证、准备、解析三步,将Java类中的类型信息、属性信息、常量池存放在方法区内存中,方法指令直接保存到栈内存中,如:main函数;
(6) 执行引擎开始执行栈内存中指令,由于main函数是静态方法,所以不需要传入实例,在类加载完毕之后,直接执行main方法指令;
(7) main函数执行主线程结束,随之守护线程销毁,最后JVM实例被销毁;
类加载是Java程序运行的第一步,在java.lang包里有个ClassLoader类,ClassLoader 的基本目标是对类的请求提供服务,按需动态装载类和资源,只有当一个类要使用(1.Class.forName();2.调用类的静态方法;3.使用 new 关键字来实例化一个类)的时候,类加载器才会加载这个类并初始化。当我们自定义类加载器加载类文件时(继承自ClassLoader类,只需覆盖 findClass方法,即可),其类加载机制如下:
当自定义类加载器加载类时,会调用loadClass方法加载类,而由于类加载的双亲委托模式,会将类的加载代理给父类加载器:系统类加载器来完成,依次类推至最顶层引导类加载器加载,如果父类加载器没有加载到类,则最终返回由自定义类加载器加载类,通过双亲委托模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类。
需要说明一下Java虚拟机是如何判定两个Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。然而,类加载器又分初始类加载器和定义类加载器,由于类加载的代理模式,初始类加载器并不一定是定义类加载器,所以确切的说,判定两个 Java 类是否相同的, 哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。
JVM内存主要是指运行数据区,粗略的分为:堆、栈,细致区分五部分:方法区、堆、栈、程序计数器、本地方法栈。
(1)程序计数器: 线程私有 ,记录线程所执行的虚拟机字节码指令的地址;如果正在执行的是native方法,这个计数值则为空。唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
(2)Java虚拟机栈: 线程私有,描述Java方法执行的内存模型 : 每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用直至执行完成的过程,就对应这一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧是方法运行时的基础数据结构。如果线程请求栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常;如果虚拟机栈动态扩展时无法申请到足够的内存,抛出 OutOfMemoryError异常。
(3)本地方法栈: 线程私有,描述native方法执行的内存模型。有的虚拟机(如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。
(4)Java堆: 线程共享,存放对象实例及数组,是垃圾收集器管理的主要区域 ,采用分代收集策略,所以Java堆会细分为新生代和老年代。根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上连续的即可。如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出OutOfMemoryError异常。
(5)方法区: 线程共享,存储已被虚拟机加载的类信息、常量池、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫Non-Heap(非堆),目的应该是与Java堆区分开来。如果方法区无法满足内存分配需求,抛出OutOfMemoryError异常。对于HotSpot虚拟机,方法区被称为永久代,本质上两者不等价,仅仅是因为HotSpot虚拟机设计团队选择把GC分代收集扩至方法区,或者说使用永久代来实现方法区而已。
JVM堆一般又可以分为以下三部分:Young 新生代、Tenured 老年代、Perm 永久代;
Perm永久代主要保存class,method,filed对象,这部分的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError : PermGen space 的错误。
Tenured老年代主要保存生命周期长的对象,多次未被GC掉的对象;
Young新生代主要保存新生成对象,根据JVM的策略,在经过几次垃圾收集后,而没有被垃圾回收的对象将被移动到Tenured区间。有时候该区经常会遇到java.lang.OutOfMemoryError :Java heap space的错误。
-Xms :指定了JVM初始启动以后 初始化内存;
-Xmx:指定JVM堆得 最大内存,在JVM启动以后,会分配-Xmx参数指定大小的内存给JVM,但是不一定全部使用,JVM会根据-Xms参数来调节真正用于JVM的内存;
-Xmn:参数设置了新生代的大小;老年代等于-Xmx减去-Xmn;
-XX:Xss:参数设置了永久代的大小;
(6)直接内存: 不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁的使用,而且也可能导致OutOfMemoryError异常出现。例如:NIO类,引入了一种基于通道(Channel)与缓冲区(Buffer)的IO方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了Java堆和Native堆中来回复制数据。直接内存虽然不会受到Java堆大小的限制,但是,既然是内存,仍会受到本机总内存大小及处理器寻址空间的限制。
(7)JVM参数:-XX:+HeapDumpOnOutOfMemoryError:可让虚拟机在内存溢出异常时Dump出当前的内存堆转储快照以便事后分析;-Xss:设置线程栈大小;-XX:PermSize:设置永久代初始大小;-XX:MaxPermSize:设置永久代最大大小;-XX:MaxDirectMemorySize:设置直接内存大小,默认与Java堆最大值一样;
(1) 引用计数算法
存储对特定对象的所有引用数,也就是说,当应用程序创建引用以及引用超出范围时,JVM必须适当增减引用数。当某对象的引用数为0时,便可以进行垃圾收集。
优点:实现简单、效率高
缺点:很难解决对象之间相互引用问题
(2) 可达性分析算法
通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
可作为GC Roots的对象包括:虚拟机栈(栈帧的本地变量表)中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(即一般说的是native方法)引用的对象。
(1)强引用:只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
(2)软引用:对软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。
(3)弱引用:对弱引用关联着的对象,只能生存到下一次垃圾收集发生之前。
(4)虚引用:对象是否有虚引用,完全不会对其生存时间构成影响,也无法通过虚引用来取得对象实例。关联虚引用唯一目的就是能在对象被收集器回收时收到系统通知。
任何一个对象的finalize()方法都仅会被系统自动调用一次。如果对象面临下一次回收,它的finalize()方法不会被再次执行。建议避免使用该方法。
GC即垃圾收集机制是指JVM用于释放那些不再使用的对象所占用的内存。常用机制:
(1) 标记-清除算法【适用于老年代】
首先根据可达性分析算法,标记出所有需要回收的对象,在标记完成后统一回收所有标记的对象。
缺点:效率问题:标记、清除两个过程效率都低;空间问题:标记清除之后产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中,需要分配大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
(2) 复制算法【适用于对象存活率低的新生代】
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当一块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。【空间换时间】
优点:实现简单、效率高、无内存碎片;
缺点:内存使用率低,太浪费。
由于新生代中的对象98%是朝生夕死的,所以将内存分为一块较大的内存、两块较小的内存。每次使用一块大内存和一块小内存,当垃圾回收时,会将该大内存和小内存中存活的对象一次性的复制到另外一块小内存中,最后清理掉该大内存、小内存。但是如果对象的存活率较高,那么当复制对象至另一块小内存时,该小内存空间会不够用,则需要依赖其他内存(老年代)进行分配担保。
当对象的存活率较高时,复制算法要进行较多的复制操作,效率会变低。
(3) 标记-整理算法【适用于老年代】
标记过程与“标记-清除”算法一样,唯一区别是在后续步骤不是直接对内存进行清除,而是先让所有活着的对象都向一端移动,然后直接清除掉端边界以外的内存。
(4) 分代收集策略
一般是把Java堆分成新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
新生代中对象存活率低,采用复制算法,有老年代对它进行分配担保。
老年代中对象存活率高,无额外空间对它进行分配担保,必须采用“标记-清除”或“标记-整理”算法。
(5) 回收方法区(永久代)
很多人认为方法区(永久代)是没有垃圾收集的,Java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾收集,况且在方法区中进行垃圾收集性价比很低;
永久代的垃圾收集主要回收两方面: 废弃常量和无用的类;
1. 废弃常量:判断常量池的对象是否还存在任何引用;
2. 无用的类:(1)该类的所有实例都被回收;(2)该类的ClassLoader已被回收;(3)该类的Class对象没有任何引用;
(6) 垃圾收集器
JDK1.7 Update14之后的HotSpot虚拟机正式提供了G1收集器;
1. Serial收集器:单线程、Stop The Wold、Client模式默认 新生代收集器、 复制算法;
2. ParNew收集器:Serial多线程版、并行、Stop The Wold、Server模式默认 新生代收集器、只有该收集器能与CMS收集器配合、 复制算法;ParNew收集器也是使用 -XX:+UseConcMarkSweepGC选项后的默认新生代收集器,也可以使用 -XX:+UserParNewGC选项来强制指定它;可以使用 -XX:ParallelGCThreads参数来限制垃圾收集的线程数,默认开启的线程数与CPU的数量相等;
3. Parallel Scavenge收集器:多线程、并行、Stop The Wold、 新生代收集器、Server模式、 复制算法; CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而该收集器关注的是达到一个可控制的吞吐量;
所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),吞吐量与垃圾收集时间成反比;
停顿时间越短就越适合需要与用户交互的程序,而高吞吐量则可以高效率的利用CPU,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务;
-XX:MaxGCPauseMillis:设置最大垃圾收集停顿时间;
-XX:GCTimeRatio:设置吞吐量大小,大于0且小于100的整数,默认为99;
-XX:UseAdaptiveSizePolicy:设置打开GC自适应的调节策略,以达到最大的吞吐量;
4. Serial Old收集器:单线程、Stop The Wold、 老年代、 标记-整理算法、Client模式;作为CMS收集器的备选方案,在并发收集发生 Concurrent Mode Failure时使用;
5. Parallel Old收集器:多线程、Stop The Wold、 老年代、 标记-整理算法、Server模式、并行;适合与Parallel Scavenge配合使用;
6. CMS收集器:多线程、Stop The Wold、 老年代、 标记-清除算法、Server模式、并发; 缺点:内存碎片;
-XX:+UseCMSCompactAtFullCollection:默认开启,用于在CMS收集器顶不住进行Full GC时开启内存碎片的合并整理过程,内存整理的过程是无法并发,会导致停顿时间变长;
-XX:CMSFullGCsBeforeCompaction:用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的GC,默认为0,表示每次进行Full GC时都进行碎片整理;
7. G1收集器:多线程、 新老年代、 复制+标记-整理算法、并发、Server模式、GC整个堆; 特点:并行与并发、分代收集、空间整理、可预测的停顿;
G1虽然保存了新老年代的概念,但已经不是物理分割了,他们都是一部分Region(不需要连续)的集合;
args="-Dfile.encoding=UTF-8
-J-server
-J-Xss128k
-J-XX:ThreadStackSize=128
-J-XX:PermSize=64m -J-XX:MaxPermSize=256m
-J-verbose:gc -J-XX:+PrintGCDetails -J-XX:+PrintGCTimeStamps
-Djava.library.path=${RESIN_HOME}/libexec:/opt/j2sdk/lib:/usr/lib64 -Djmagick.systemclassloader=false -DNO_TIMEOUT "
args="$args -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=9090"
args="$args -Xmn5g -Xms10g -Xmx10g"
args="$args -J-XX:+UseParNewGC -J-XX:+UseConcMarkSweepGC"
Client模式:默认-XX:+UseSerialGC,Serial + Serial Old;
Server模式:默认-XX:+UseParallelGC,Parallel Scavenge + Serial Old;
-XX:+PrintGC 打印 GC信息
-XX:+PrintGCDetails 打印较为详细的 GC信息
- XX:+PrintGCTimeStamps 打印 GC 时间戳,相对于应用程序启动的时间
1) 跟 Java 堆大小相关的 JV M 内存参数
下面三个 JVM 参数用来指定堆的初始大小和最大值以及堆栈大小
-Xms 设置 Java 堆的初始化大小
-Xmx 设置最大的 Java 堆大小
-Xss 设置Java线程栈大小
2) 关于打印垃圾收集器详情的 JVM 参数
-verbose:gc 记录 GC 运行以及运行时间,一般用来查看 GC 是否是应用的瓶颈
-XX:+PrintGCDetails 记录 GC 运行时的详细数据信息,包括新生成对象的占用内存大小以及耗费时间等
-XX:+PrintGCTimeStamps 打印垃圾收集的时间戳
3) 设置 Java 垃圾收集器行为的 JVM 参数
-XX:+UseParallelGC 使用并行垃圾收集
-XX:+UseConcMarkSweepGC 使用并发标志扫描收集 (Introduced in 1.4.1)
-XX:+UseSerialGC 使用串行垃圾收集 (Introduced in 5.0.)
需要提醒的是,但你的应用是非常关键的、交易非常频繁应用时,应该谨慎使用 GC 参数,因为 GC 操作是耗时的,你需要在这之中找到平衡点。
4) JVM 调试参数,用于远程调试
-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
5) 用于修改 Perm Gen 大小的 JVM 参数
下面的这三个参数主要用来解决 JVM 错误:java.lang.OutOfMemoryError:Perm Gen Space.
-XX:PermSize and MaxPermSize
-XX:NewRatio=2 Ratio of new/old generation sizes.
-XX:MaxPermSize=64m Size of the Permanent Generation.
6) 用来跟踪类加载和卸载的信息
-XX:+TraceClassLoading 和 -XX:+TraceClassUnloading 用来打印类被加载和卸载的过程信息,这个用来诊断应用的内存泄漏问题非常有用。
7) 用于调试目的的 JVM 开关参数
-XX:HeapDumpPath=./java_pid.hprof Path to directory or file name for heap dump.
-XX:+PrintConcurrentLocks Print java.util.concurrent locks in Ctrl-Break thread dump.
-XX:+PrintCommandLineFlags Print flags that appeared on the command line.
如果你的应用追求低停顿,那G1现在已经可以作为一个可尝试的选择;如果你的应用追求吞吐量,那G1并不会为你带来什么特别的好处;
可达性分析必须在一个能确保一致性的快照中进行,一致性是指在整个分析过程中整个执行系统必须冻结,不可以出现分析过程中对象引用关系还在不断变化的情况,该点不满足的话可达性分析结果不准确。所以这点是导致GC进行时必须停顿所有线程的原因。
新生代GC:Minor GC,指发生在新生代的垃圾收集动作;
老年代GC:Major GC/Full GC,指发生在老年代垃圾收集动作,出现了Major GC,经常会伴随至少一次的Minor GC。会发生Stop The Wold。
(1)大对象:通过参数-XX:PretenureSizeThreshold=3145287,令大于这个设置值的对象直接在老年代分配。以避免大对象在新生代分配,从而触发新生代GC。
(2)多次GC仍存活的对象:当对象每”熬过“一次Minor GC,对象年龄就增加1岁,当对象年龄增加到一定程度(默认15岁),就会晋升到老年代中。对象晋升老年代的年龄阀值,可以通过参数-XX:MaxTenurigThreshold设置。
(3)动态对象年龄判定:虚拟机并不是永远地要求对象年龄必须达到MaxTenurigThreshold阀值才能晋升到老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenurigThreshold阀值。
(4)新生代—分配担保:由于新生代采用复制收集算法,当新生代Minor GC 时,如果 存活对象的大小大于Survivor,依据分配担保策略会将Survivor无法容纳的对象直接存入老年代。如果老年代仍不能够存放剩余的对象,则会发生Major GC/Full GC,就会Stop The Wold。
常量池其实就是方法区一个内存空间,虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和。以字符串为例,在Java源代码中的每一个字面值字符串,都会在编译成class文件阶段,形成标志号为 8(CONSTANT_String_info)的常量表 。当JVM加载 class文件的时候,会为对应的常量池建立一个内存数据结构,并存放在方法区中。
如下代码:
public class Test{
private String str="我们"。
}
将Test编译之后形成class文件,那么在class文件中"我们"会以一种CONSTANT_UTF8_info表的形式存在,字节序列如下:
1 0 6 230 136 145 228 187 172 1表示常量表的类型,0 6表示有6个字节的长度。后面6个字节是UTF-8编码的“我们”。
当JVM运行的时候会将这些常量池的信息加载进方法区。也就是说在运行过程中内存存储的"我们"是UTF-8编码的。
Java语言的一个非常重要的特点就是平台无关性。而使用JVM是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入JVM后,Java语言在不同平台上运行时不需要重新编译。Java语言使用JVM屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在JVM虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,把字节码解释成具体平台上的机器指令执行。
这个异常问题本质原因是我们创建了太多的线程,而能创建的线程数是有限制的,导致了异常的发生。能创建的线程数的具体计算公式如下:
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
MaxProcessMemory:指的是一个进程的最大内存
JVMMemory:JVM内存
ReservedOsMemory:保留的操作系统内存
ThreadStackSize:线程栈的大小
在java语言里, 当你创建一个线程的时候,虚拟机会在JVM内存创建一个Thread对象同时创建一个操作系统线程,而这个系统线程的内存用的不是JVMMemory,而是系统中剩下的内存(MaxProcessMemory - JVMMemory - ReservedOsMemory),可以下面方法解决问题:
(1) 通过设置 -Xmx512m 减少JVM Heap size;
(2) 通过设置 -Xss64k 减少线程占用的Stack size;
(3) 增加操作系统内存;
15. TCP 与 UDP
(1)网络层:IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议
传输层:TCP协议与UDP协议
应用层:FTP、HTTP、TELNET、SMTP、DNS等协议
HTTP是应用层协议,其传输都是被包装成TCP协议传输。可以用SOCKET实现HTTP。
Socket是一组接口,可以实现TCP,也可以实现UDP。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/UDP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
(2) TCP --- 传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。 理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求。
UDP --- 用户数据报协议,是一个无连接的简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
(3)TCP连接的三次握手:要了解TCP,一定要知道"三次握手,四次拜拜"所谓的三次握手,就是发送数据前必须建立的连接叫三次握手,握手完了才开始发的,这也就是面向连接的意思。
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
程序:是一段静态代码,它是应用软件执行的蓝本。
进程:是程序运行时的一个实例,对应了从代码加载、执行的一个完整过程,此过程也对应进程产生、发展到消亡的过程。
线程:比进程更小的执行单位,一个进程在其执行过程中,可以产生多个执行线程,即每个线程也有它的产生、发展到消亡的过程。是一个动态概念。
粒度层次不同,是两个不同层次上的概念。进程是由操作系统来管理的,而线程则是由进程来管理。
共享资源不同,不同进程的代码、内部数据和状态都是完全独立的,而一个进程内的多线程是共享进程空间资源的,有可能互相影响。
切换效率不同,线程本身的数据通常只有寄存器数据,以及一个进程执行时使用的堆栈,所以线程比进程切换负担小。
18. Thread 常用方法
(1) Thread:
public static Thread currentThread( ):返回当前线程对象,是一个静态的方法。
public static void sleep( long millis ):使当前线程进入睡眠状态,参数设定其等待时间,不会释放锁,静态方法。
public static void yield():使当前线程放弃执行,切换到其它线程,是一个静态方法。
(2) Thread Instance:
public void start():启动线程,JVM将调用此线程的run方法,结果是将同时运行两个线程,当前线程和执行run方法的线程。
public void run():Thread的子类应该重写此方法,内容应为该线程应执行的任务。
public void stop():停止线程运行,并退出可执行状态。 【已过时】
public void resume():将暂停的线程继续执行。【已过时】
public void suspend():使线程暂停执行,不退出可执行态。【已过时】
public void interrupt():中断线程。
public void join():在当前线程中加入调用join方法的线程A,直到线程A死亡才能继续执行当前线程。
public void join(long millis):在当前线程中加入调用join方法的线程A,直到到达参数指定的毫秒数或线程A死亡才能继续执行当前线程。
public void setPriority(int newPriority):设置线程优先级。
public void setDaemon(boolean on):设置是否为后台线程。如果当前运行线程均为后台线程则JVM停止运行。该方法必须在start()方法之前使用。
public final void checkAccess():判断当前线程是否有权力修改调用此方法的线程。
public boolean isAlive():判断线程是否处于执行状态。返回值true表示处于运行状态,false表示已停止。
(3) Object:
public void wait(): 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,使当前线程进入等待状态,会释放锁。
public void notify(): 唤醒在此对象监视器上等待的单个线程。
public void notifyAll():唤醒在此对象监视器上等待的所有线程。
主线程:Java应用程序总是从主类的main()方法开始执行。当JVM加载代码,发现main方法之后,启动的线程称作“主线程”,该线程负责执行main方法。
子线程:在main方法的执行中再创建的线程。
如果main方法中又创建了子线程,那么JVM就要在主线程和子线程之间轮流切换,main方法即使执行完最后的语句,JVM也不会结束程序,JVM一直要等到程序中的所有线程都结束之后,才结束我们的Java应用程序。
(1)Thread类:Thread 类的子类创建线程对象,子类重写Thread类中的run()方法
在Java程序中创建多线程的方法之一是继承Thread类
从Thread类派生一个子类,并创建这个子类的对象,就可以产生一个新的线程。这个子类应该重写Thread类的run方法,在run方法中写入需要在新线程中执行的语句段。这个子类的对象需要调用start方法来启动,新线程等待CPU调度将自动进入run方法。当前线程将同时继续往下执行。
当创建子类的多个线程对象时,其成员变量和run()方法中局部变量都是互不干扰的。
(2)Runnable接口:使用Thread类直接创建线程对象,但需要传入实现Runable接口的目标对象。
在编写复杂程序时相关的类可能已经继承了某个基类,而Java不支持多继承,在这种情况下,便需要通过实现Runnable接口来生成多线程。
使用Thread创建线程对象时,通常使用的构造方法是:Thread(Runnable target),该构造方法中的参数是一个Runnable类型的接口,因此,在创建线程对象时必须向构造方法的参数传递一个实现Runnable接口类的实例,该实例对象称作所创线程的目标对象。
当线程调用start方法后,一旦轮到它来享用CPU资源,目标对象就会自动调用接口中的run方法(接口回调)。
对于使用同一目标对象的线程,目标对象的成员变量自然就是这些线程的共享数据单元。不同run()方法中的局部变量互不干扰。
synchronized ——线程同步关键字
把需要修改数据的方法用关键字synchronized来修饰,用于指定需要同步的代码段或方法,也就是监视区。
当一个线程A使用一个synchronized修饰的方法时,其他线程想使用这个方法时就必须等待,直到线程A 使用完该方法 (除非线程A使用wait主动让出CUP资源)。 当被synchronized限定的代码段执行完,就释放对象锁(信号量)
为了更有效地协调不同线程的工作,需要在线程间建立沟通渠道,通过线程间的“对话”来解决线程间的同步问题。
java.lang.Object 类的一些方法为线程间的通讯提供了有效手段。
一个线程在使用的同步方法中时,可能根据问题的需要,必须使用wait() (挂起)方法使本线程等待,暂时让出CPU的使用权,并允许其它线程使用这个同步方法。其它线程如果在使用这个同步方法时如果不需要等待,那么它用完这个同步方法的同时,应当执行notify(), notifyAll()(恢复)方法通知所有的由于使用这个同步方法而处于等待的线程结束等待。
每个Java线程都有一个优先级,其范围都在1和10之间。 默认情况下,每个线程的优先级都设置为5。
在线程A运行过程中创建的新的线程对象B,初始状态具有和线程A相同的优先级。可在线程创建之后的任何时候,通过 setPriority(int priority)方法改变其原来的优先级。
基于线程优先级的线程调度:
(1) 具有较高优先级的线程比优先级较低的线程优先执行;
(2) 对具有相同优先级的线程,Java的处理是随机的;
(3) 底层操作系统支持的优先级可能要少于10个,这样会造成一些混乱。因此,只能将优先级作为一种很粗略的工具使用。最后的控制可以通过明智地使用yield()函数来完成;
假设某线程正在运行,则只有出现以下情况之一,才会使其暂停运行:
(1) 一个具有更高优先级的线程变为就绪状态(Ready);
(2) 由于输入/输出(或其他一些原因)、调用sleep、wait、yield方法使其发生阻塞;
(3) 对于支持时间分片的系统,时间片的时间期满;
通常,我们在一个线程内部插入yield()语句,这个方法会使正在运行的线程暂时放弃执行,这时具有同样优先级的线程就有机会获得调度开始运行,但较低优先级的线程仍将被忽略不参加调度;
挂起 有时候两个线程并不是同步的,即不涉及都需要调用一个同步方法,但线程也可能需要暂时的挂起。所谓挂起一个线程就是让线程暂时让出CPU的使用权限,暂时停止执行,但停止执行的持续时间不确定,因此不能使用sleep方法暂停线程。挂起一个线程需使用wait方法,即让准备挂起的线程调用 wait 方法,主动让出CPU的使用权限.
恢复 为了恢复该线程,其它线程在占有CUP资源期间,让挂起的线程的目标对象执行notifyAll()方法,使得挂起的线程继续执行;如果线程没有目标对象,为了恢复该线程,其它线程在占有CUP资源期间,让挂起的线程调用notifyAll()方法,使挂起的线程继续执行。
(1) 线性表结构一般分为三种: 顺序线性表、单链表、双链表 ;特征:1. 一个特定的线性表,应该是用来存放特定的某一个类型的元素的( 元素的“同一性”);2. 除第一个元素外,其他每一个元素 有且仅有一个 直接前驱;除最后一个元素外,其他每一个元素 有且仅有一个直接后继( 元素的“序偶性”);3. 元素在线性表中的“下标”唯一地确定该元素在表中的相对位置( 元素的“索引性”);
(2)二叉树是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树的形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。
二叉树(BinaryTree)是n(n≥0)个结点的有限集,它或者是空集(n=0),或者由一个根结点及两棵互不相交的、分别称作这个根的左子树和右子树的二叉树组成。这个定义是递归的。由于左、右子树也是二叉树, 因此子树也可为空树。
二叉树遍历:由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtlee)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为 先根遍历、中根遍历和后根遍历。
(3)队列是一个常用的数据结构,是一种 先进先出(First In First Out, FIFO)的结构,也就是说只能在表头进行删除,在表尾进行添加。栈是一种 后进先出(Last In First Out,LIFO)的数据结构。
jps : 只用于列出java的进程。
jstat :可用于观察Java应用程序运行时信息的工具。
jinfo :可用于查看正在运行的Java应用程序的扩展参数,甚至支持在运行时修改部分参数。
jmap :生成Java应用程序的堆快照和对象的统计信息。jmap -histo 2972 > heap.log
jhat :用于分析Java应用程序的堆快照内容文件。jhat heap.log
jstack : 用于导出Java应用程序的线程堆栈。 jstack -l 2348 > jstack.log
设计模式分为:
创建模式:简单工厂模式、工厂方法模式、抽象工厂模式、单例模式等;
结构模式:装饰模式、代理模式、门面模式、适配器模式、享元模式等;
行为模式:不变模式、策略模式、观察者模式、责任链模式、访问者模式等;
(1)简单工厂模式: 是工厂方法模式的特殊实现,就是由一个工厂类根据传入的参量决定创建出哪一种产品类型的实例。简单工厂模式的一般性结构如下图:
(2)工厂方法模式: 是把原来简单工厂里创建对象的过程延迟到了具体实现的子类工厂,这时的工厂从一个类变成了一个接口类型。
(3)抽象工厂模式: 是工厂方法里面最为抽象和最具一般性的形态,是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。
(4)单例模式: 是指确保一个类有且仅有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式的目的就是有且只提供一个实例,所以它有一下几个特点:
1、单例类只能有一个实例;
2、单例类必须自己创建自己惟一的实例;
3、单例类必须给所有其他对象提供这一实例;
(5)装饰模式: 通过装饰类 动态的给一个对象添加一些额外的职责。
(6)代理模式: 指目标对象给定代理对象,并由代理对象代替真实对象控制客户端对真实对象的访问。
(7)适配器模式: 把一个类的接口变成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
(8)门面模式: 为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
(9)享元模式: 通过共享技术以便有效的支持大量细粒度的对象。
(10)策略模式: 用意是定义一组算法,把它们一个个封装起来,并且使他们可以相互替换。策略模式可以独立于使用他们的客户端而变化。
(11) 观察者模式:是对象的行为模式,又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听(Source/Listener)模式或者从属(Dependents)模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
(12) 责任链模式: 责任链模式是对象的行为模式。使多个对象都有机会处理请求,从而避免请求的发送者和接受者直接的耦合关系。将这些对象连成一条链,沿着这条链传递该请求,直到有一个对象处理它为止。责任链模式强调的是每一个对象及其对下家的引用来组成一条链,利用这种方式将发送者和接收者解耦。
(1)静态代理:由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在编译期就确定了。
(2) 动态代理: 动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行期确定。
一种通信协议,它允许将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器。 一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。
HTTP协议的主要特点如下:
1. 支持客户/服务器模式,基于TCP实现;
2. 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、POST、DELETE、PUT。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快;
3. 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记;
4. 短连接:短连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
5. 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
CGI(Common Gateway Interface),通用网关接口,它是一段程序,运行在服务器上如:HTTP服务器,提供同客户端HTML页面的接口。利用程序的标准输入输出流,完成HTTP通信。 HTTP 是文本协议,每次请求的文本以标准输入流的形式进入服务器端 CGI 程序,创建进程;然后进程的标准输出流作为响应 。
CGI程序可以是Python脚本,PERL脚本,SHELL脚本,C或者C++程序等。 所有服务器端 HTTP 处理都是类 CGI 的原理,接受符合协议的标准输入文本流,从标准输出流输出同样符合协议的文本,也就是“请求”和“响应”。
在负载均衡的思路下,多台服务器为对等方式,每台服务器都具有同等的地位,可以单独对外提供服务而无须其他服务器的辅助。通过负载分担技术,将外部发送来的请求按一定规则分配到对称结构中的某一台服务器上,而接收到请求的服务器都独立回应客户机的请求。
常用负载均衡技术:反向代理负载均衡 (如Apache+JK2+Tomcat这种组合),使用代理服务器可以将请求转发给内部的Web服务器,让代理服务器将请求均匀地转发给多台内部Web服务器之一上,从而达到负载均衡的目的。这种代理方式与普通的代理方式有所不同,标准代理方式是客户使用代理访问多个外部Web服务器,而这种代理方式是多个客户使用它访问内部Web服务器,因此也被称为反向代理模式。
客户系统一般采用Apache httpd作为web服务器,即作为Tomcat的前端处理器,根据具体情况而定,有些情况下是不需要Apache httpd作为 web 服务器的,如系统展现没有静态页面那就不需要Apache httpd,那时可以直接使用Tomcat作为web服务器来使用。使用Apache httpd主要是它在处理静态页面方面的能力比Tomcat强多了。
会话亲和:就是表示来自同会话的所有请求都由相同的 Tomcat 实例来处理,这种情况下,如果Tomcat实例或所执行的服务器机器失效,也会丧失Servlet的会话数据。即使在集群系统中执行更多的Tomcat实 例,也永远不会复制会话数据。这样是提高集群性能的一种方案,但不具备有容错能力了。
会话复制:只配置负载均衡还不行,还要 session 复制,也就是说其中任何一个 tomcat 的添加的 session ,是要同步复制到其它 tomcat , 集群内的 tomcat 都有相同的 session,使用会话复制,则当一个Tomcat实例宕掉时,由于至少还有另一个Tomcat实例保有一份会话状态数据,因而数据不会丧失。但性能会 有所降低。
其实无论是分布式,数据缓存,还是负载均衡,无非就是改善网站的性能瓶颈,在网站源码不做优化的情况下,负载均衡可以说是最直接的手段了。其实抛开这个名词,放开了说,就是希望用户能够分流,也就是说把所有用户的访问压力分散到多台服务器上,也可以分散到多个 tomcat里,如果一台服务器装多个tomcat,那么即使是负载均衡,性能也提高不了太多,不过可以提高稳定性,即容错性。当其中一个主tomcat 当掉,其他的tomcat也可以补上,因为tomcat之间实现了Session共享。待tomcat服务器修复后再次启动,就会自动拷贝所有 session数据,然后加入集群。这样就可以不间断的提供服务。如果要真正从本质上提升性能,必须要分布到多台服务器。
CMOS:记录 各项硬件参数且嵌入在主板上面的存储器;
BIOS: 一个写入到主板上的一个韧体(韧体就是写入到硬件上的一个程序)。BIOS就是在开机的时候计算机系统会主动执行的第一个程序了。
(1) 首先加载BIOS程序,通过BIOS程序去 加载CMOS信息,取得主机的各项硬件配置,并开始进行开机自检, 依据设置取得第一个可启动的设备;
(2) 加载执行第一个启动设备内MBR的Boot Loader;
(3)依据Boot Loader的 设置加载Kernel,Kernel会开始检测硬件与加载驱动程序;
(4)在硬件驱动成功后, Kernel会主动调用init进程,而init会取得run-level信息;
(5)init执行 /etc/rc.d/rc.sysinit文件来准备软件执行的操作环境(如网络,时区等);
(6)init执行 run-level的各个服务启动(script方式);
(7)init执行 /etc/rc.d/rc.local文件;
(8)init执行 终端模拟程序mingetty来启动login进程,最后就等待用户登录;
top : 实时显示系统中 各个进程的资源占用状况。前半部分是系统统计信息,后半部分是进程占用资源状况信息。
sar : 周期性地对内存和CPU使用情况进行采样。查看CPU使用情况:sar -u 1 3;查看内存使用情况:sar -r 1 3;查看I/O使用情况:sar -b 1 3
vmstat : 周期性地统计CPU 内存 swap 使用情况等信息。vmstat 1 3
iostat : 周期性查看I/O信息。iostat 1 2
pidstat:功能强大的性能检测工具, 不仅可以监视进程的CPU, I/O ,内存资源,也可以监视线程的性能情况。
查看CPU使用情况: pidstat -p 1187 -u 1 3 【 -t 参数将系统性能的监控细化到线程级别 】
查看内存使用情况: pidstat -p 1187 -r 1 3 【-t 参数将系统性能的监控细化到线程级别】
查看I/O使用情况: pidstat -p 1187 -d 1 3 【-t 参数将系统性能的监控细化到线程级别】
netstat:用于查看各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多播成员 (Multicast Memberships) 等等。
netstat -na 来显示所有连接的端口并用数字表示。
mpstat:用于获取 CPU 相关统计信息。mpstat -P ALL 5 2 显示了系统中 CPU 的各种统计信息。–P ALL 选项指示该命令显示所有 CPU 的统计信息。参数 5 2 指示该命令每隔 5 秒运行一次,共运行 2 次。
pmap:report memory map of a process(查看进程的内存映像信息)。 pmap -d 1
-x extended Show the extended format. 显示扩展格式
-d device Show the device format. 显示设备格式
-q quiet Do not display some header/footer lines. 不显示头尾行
-V show version Displays version of program. 显示版本
参考:https://my.oschina.net/xianggao/blog/358205