1、Java 运行原理
一、高级语言运行过程
在程序真正运行在CPU上以前,必需要让OS的kernel理解咱们在编辑器或者IDE里根据每种语言的语法规则敲入的源代码,kernel才能作出相关的调度,因此须要先将源代码转化成可执行的二进制文件,这个过程一般由编译器完成。有些编译器直接将源代码编译成机器码,载入内存后CPU能够直接运行。而机器码的格式与跟具体的CPU架构相关连,例如ARM CPU没法理解Intel CPU机器码。所以,一样的源代码须要根据不一样的硬件进行特定的编译。高级语言转换到低级语言的桥梁就是编译器。程序员写好源代码,编译器将源码编译成可执行的机码,而后CPU读取机器码,执行程序。
二、Java语言的执行过程
宽泛地讲,Java源代码(.java)通过java编译器(javac.exe)编译以后,并无直接转化为机器码,而是转化成一种中间格式——字节码(.class),字节码再通过Java虚拟机解释,转化成机器码,而后经由操做系统到达CPU运行。整个执行过程以下图所示:
Java的跨平台是基于JVM虚拟机这一中间物来实现的,Java源程序通过编译器编译后生成虚拟机可以理解的字节码(ByteCode——class文件的内容),虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定系统上的机器码,而后在特定的机器上运行。每一种平台的解释器是不一样的,可是实现的虚拟机是相同的。
三、JVM——Java Virtual Machine
JVM是一个虚构出来的计算机,经过在实际的计算机上仿真模拟各类计算机功能来实现的。JVM有本身完善的硬件架构,如处理器、堆栈、寄存器等,还具备相应的指令系统。JVM 的主要工做是解释本身的指令集(即字节码)并映射到本地的 CPU 的指令集或 OS 的系统调用。
3、 JVM的体系结构
Class Loader:类装载器,从入口处开始按需加载.class文件,填充这些数据到运行时数据区
Execution Engine:执行引擎,JVM的CPU,不断地取指令,JIT编译翻译执行字节码,或者执行本地方法
Runtime Data Areas:运行时数据区,核心区,运行的时候操做所分配的内存区,包括方法区、堆、java栈、PC寄存器、本地方法栈
一、类加载器
类加载器加载其实就是根据编译后的Class文件,将Java字节码载入JVM内存,并完成对运行数据处于的初始化工做,供执行引擎执行。
类加载过程:
装载——连接(验证,准备,解析)—— 初始化
1.Loading:,找到二进制字节码(Class文件)并加载至JVM内存中,标识一个被加载的类:类名+类所在的包名+Class Loader instance ID
2.Linking:java
Verifying:验证元数据,文件格式,字节码等,确保class文件包含的字节码信息符合JVM的规范,以避免危及JVM安全;程序员
Preparing:准备分配给类所须要内存的数据结构,指示在类中定义的字段、方法和接口;算法
Resolving:对类中的全部属性、方法进行验证,以确保其须要调用的属性、方法存在,以及具有应的权限;符号引用的转换等数组
3.Initialing:初始化执行类中的静态初始化代码、构造器代码以及静态属性
类装载器类型:
启动类装载器:JVM实现的一部分;
用户自定义类装载器:是Java程序的一部分,必须是ClassLoader类的子类。
类装载顺序:
Jvm启动时,由Bootstrap向User-Defined方向加载类;应用进行Class Loader时,由User-Defined向Bootstrap方向查找并加载类;
类加载采用父类委托制,子加载器能查询父加载器已缓存类,委托只能从下到上,反之不行。类加载器能够加载一个类,可是它不能卸载一个类。可是类加载器能够被删除或者被建立。一个类能够被不一样的类加载器加载。
Bootstrap ClassLoader
JVM的根ClassLoader,它是用C++实现的,在JVM启动的时候建立,负责装载$JAVA_HOME中jre/lib/rt.jar(Sun JDK的实现)中全部class文件,这个jar中包含了Java规范定义的全部接口以及实现。
Extension ClassLoader
装载除了基本的Java API之外的扩展类,它也负责装载其余的安全扩展功能。
System ClassLoader
负责加载应用程序类,加载启动参数中指定的Classpath中的jar包以及目录,在Sun JDK中ClassLoader对应的类名为AppClassLoader。
User-Defined ClassLoader
Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中的jar以及目录。
二、执行引擎
类加载器将.class文件载入内存以后,执行引擎以Java 字节码指令为单元,读取Java字节码;然后由解释器或者即时编译器(JIT Compiler)将字节码转化成平台相关的机器码。
JVM实现技术:
解释器:第一代JVM,一条一条地读取,解释而且执行字节码指令。由于它一条一条地解释和执行指令,因此它能够很快地解释字节码,可是执行起来会比较慢。这是解释执行的语言的一个缺点。字节码这种“语言”基原本说是解释执行的。
即时编译器(just-in-time compiler):第二代JVM,狭义来讲是当某段代码即将第一次被执行时进行编译,将class类文件解释成二进制文件后的结果缓存下来,当第二次执行时直接从缓存中取,所以JIT依赖更多内存缓存解释的结果。JIT编译是动态编译的一种特例。JIT编译一词后来被泛化,时常与动态编译等价;但要注意宽泛与狭义的JIT编译所指的区别。
自适应编译器(adaptive compiler):柔和第一代和第二代JVM,也是动态编译的一种,但它一般执行的时机比JIT编译迟,先让程序“以某种形式”先运行起来,收集一些信息以后再作动态编译,也就是说在全部执行过的代码里只寻找一部分来编译;而”收集信息”决定了编译哪部分代码,换个角度说“收集信息”就是在程序运行过程当中监控代码执行的频率,自动缓存利用率高的代码,这样的编译能够更加优化。这个”某种形式”能够称为“baseline execution“,能够由解释器或简单的JIT编译器承担。
HotSpot是一个JVM的实现,得名于它得混合模式执行引擎(包括解释器和自适应编译器),这个JVM最初由Longview/Animorphic实现,随着公司被Sun/JavaSoft收购而成为Sun的JVM,并于JDK 1.3.0开始成为Sun的Java SE的主要JVM。在Sun被Oracle收购后,如今HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是较新的JVM,用来代替JIT(Just in Time), Java原先是把源代码编译为字节码在虚拟机执行,这样执行速度较慢;而HotSpot将最须要编译的“热点”代码编译为本地(原生native)代码,若是已经被编译成本地代码的字节码再也不被频繁调用了,那么Hotspot VM会把编译过的本地代码从cache里移除,而且从新按照解释的方式来执行它,这样显着提升了性能。 HotSpot VM 参数能够分为规则参数(standard options)和非规则参数(non-standard options)。Hotspot VM分为Server VM和Client VM两种,这两种VM使用不一样的JIT编译器。
三、运行时数据区
当运行一个JVM Instance时,系统将分配给它一块内存区域(大小可设置),这一内存区域由JVM自行管理。从这一块内存中分出一块用来存储一些运行数据,例如建立的对象,传递给方法的参数,局部变量,返回值等等。这一块内存就称为运行数据区域。运行数据区域能够划分为6大块:Java栈、程序计数寄存器(PC寄存器)、本地方法栈(Native Method Stack)、Java堆、方法区域(包括运行常量池——Runtime Constant Pool)。其中每一个线程私有程序计数器,JVM栈,本地方法栈,方法区和堆则由JVM实例中的全部线程共享,在同一个实例中能够启用多个线程。
程序计数器
每一个线程私有,线程启动时建立,用来存放当前正在被执行的字节码指令(JVM指令)的地址,如该方法为native的,则PC寄存器中不存储任何信息。
JVM栈
每一个线程私有,线程启动时建立。存放着一系列的栈帧(Stack Frame),JVM只能进行压入(push)和弹出(pop)栈帧这两种操做。每当调用一个方法时,JVM就往栈里压入一个栈帧,方法结束返回时弹出栈帧。若是方法执行时出现异常,可用printStackTrace等方法来查看栈的状况。栈的示意图以下:
每一个栈帧包含三个部分:本地变量数组,操做数栈,方法所属类的常量池引用缓存
Local Variable Array:从0开始按顺序存放方法所属对象的引用、传递给方法的参数、局部变量。安全
Operand Stack:存放方法执行时的一些中间变量,JVM在执行方法时压入或者弹出这些变量。其实,操做数栈是方法真正工做的地方,执行方法时,局部变量数组与操做数栈根据方法定义进行数据交换。数据结构
Reference to Constant Pool:当JVM执行到须要常量池的数据时,就是经过这个引用来访问常量池的。栈帧中的数据还要负责处理方法的返回和异常。若是经过return返回,则将该方法的栈帧从Java栈中弹出。若是方法有返回值,则将返回值压入到调用该方法的方法的操做数栈中。另外,数据区中还保存中该方法可能的异常表的引用。多线程
本地方法栈
当程序经过JNI(Java Native Interface)调用本地方法(如C或者C++代码)时,就根据本地方法的语言类型创建相应的栈,此区域用于存储每一个native方法调用的状态。
堆(Heap)
堆中存放的是程序建立的对象实例以及数组值的区域,能够认为Java中全部经过new建立的对象的内存都在此分配。当堆中的空间没法知足新建对象所需的内存开销,会有溢出现象而致使程序崩溃,为了不溢出,当对象执行结束时,其占据的内存空间须要等待GC(Garbage Collection)进行回收,所以这个区域对JVM的性能影响很大。
注意: 堆是JVM中全部线程共享的,所以在其上进行对象内存的分配均须要进行加锁,致使了new对象的开销是比较大的
Sun Hotspot JVM为了提高对象内存分配的效率,对于所建立的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的状况计算而得,在TLAB上分配对象时不须要加锁,所以JVM在给线程的对象分配内存时会尽可能的在TLAB上分配,在这种状况下JVM中分配对象内存的性能和C基本是同样高效的,但若是对象过大的话则仍然是直接使用堆空间分配。
TLAB仅做用于新生代的Eden Space,所以在编写Java程序时,一般多个小的对象比大的对象分配起来更加高效。
方法区域
每一个线程共享的,启动一个JVM实例时被建立,它用于存运行放常量池、所加载的类的信息(域、方法、静态变量、final类型的常量)。开发人员在程序中经过Class对象中的getName、isInterface等方法获取的数据都来源于方法区域,在必定的条件下它也会被GC,当方法区域须要使用的内存超过其容许的大小时,会抛出Out Of Memory的错误信息。不一样的JVM实现方式在实现方法区域的时候会有所区别。Oracle的HotSpot称之为永久区域(Permanent Area)或者永久代(Permanent Generation)。
运行常量池
其空间从方法区域中分配,用来存放类、方法、接口的常量和域的引用信息,当一个方法或者域被引用的时候,JVM就经过运行常量池中的引用信息来查找方法和域在内存中的的实际地址。
4、JVM垃圾回收
Garbage Collection的基本原理:
将内存中再也不被使用的对象进行回收,GC中用于回收的方法称为收集器,因为GC须要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽量的缩短GC对应用形成的暂停。
垃圾回收算法
一、按照基本回收策略分为如下4种:
Reference Counting:引用计数,比较古老的回收算法;原理是此对象有一个引用,即增长一个计数,删除一个引用则减小一个计数。垃圾回收时,引用收集计数为0的对象。此算法最致命的是没法处理循环引用的问题。架构
Mark-Sweep:标记-清除,此算法执行分两阶段;第一阶段从引用根节点开始标记全部被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法须要暂停整个应用,同时,会产生内存碎片。
Copying: 复制,把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另一个区域中。每次只处理正在使用中的对象,所以复制成本比较小,同时复制过去之后还能进行相应的内存整理,不会出现“碎片”问题;可是此算法的缺点就是须要两倍内存空间。
Mark-Compact:标记-整理,结合了Mark-Sweep和Copying两个算法的优势;也分两阶段,第一阶段从根节点开始标记全部被引用对象,第二阶段遍历整个堆,把清除未标记对象而且把存活对象“压缩”到堆的其中一块,按顺序排放。避免了Mark-Sweep算法的碎片问题,同时也避免了Copying算法的空间问题。
二、按分区对待的方式分为如下2种并发
Incremental Collecting:增量收集,实时垃圾回收算法,即:在应用进行的同时进行垃圾回收。JDK5.0中的收集器没有使用这种算法的。
Generational Collecting:分代收集,基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不一样生命周期的对象使用不一样的算法(上述方式中的一个)进行回收。如今的垃圾回收器(从J2SE1.2开始)都是使用此算法的。
三、按系统线程分为如下3种
串行收集:串行收集使用单线程处理全部垃圾回收工做,由于无需多线程交互,实现容易,并且效率比较高。可是,其局限性是没法使用多处理器的优点,因此此收集适合单处理器机器。固然,此收集器也能够用在小数据量(100M左右)状况下的多处理器机器上。
并行收集:并行收集使用多线程处理垃圾回收工做,于是速度快,效率高。并且理论上CPU数目越多,越能体现出并行收集器的优点。
并发收集:相对于串行收集和并行收集而言,前面两个在进行垃圾回收工做时,须要暂停整个运行环境,而只有垃圾回收程序在运行,所以,系统在垃圾回收时会有明显的暂停,并且暂停时间会由于堆越大而越长。
处理碎片
因为不一样Java对象存活时间是不必定的,所以,在程序运行一段时间之后,若是不进行内存整理,就会出现零散的内存碎片。碎片最直接的问题就是会致使没法 分配大块的内存空间,以及程序运行效率下降。因此,在上面提到的基本垃圾回收算法中,“复制”方式和“标记-整理”方式,均可以解决碎片的问题。
对象建立和对象回收
垃圾回收线程是回收内存的,而程序运行线程则是消耗(或分配)内存的,一个回收内存,一个分配内存,从这点看,二者是矛盾的。所以,在现有的垃圾回收方式 中,要进行垃圾回收前,通常都须要暂停整个应用(即:暂停内存的分配),而后进行垃圾回收,回收完成后再继续应用。这种实现方式是最直接,并且最有效的解决两者矛盾的方式。
可是这种方式有一个很明显的弊端,就是当堆空间持续增大时,垃圾回收的时间也将会相应的持续增大,对应应用暂停的时间也会相应的增大。一些对相应时间要求很高的应用,好比最大暂停时间要求是几百毫秒,那么当堆空间大于几个G时,就颇有可能超过这个限制,在这种状况下,垃圾回收将会成为系统运行的一个瓶颈。 为解决这种矛盾,有了并发垃圾回收算法,使用这种算法,垃圾回收线程与程序运行线程同时运行。在这种方式下,解决了暂停的问题,可是由于须要在新生成对象的同时又要回收对象,算法复杂性会大大增长,系统的处理能力也会相应下降,同时碎片问题将会比较难解决。
5、JRE(Java Runtime Environment)和JDK(Java Development Kit)
JRE是指运行Java程序所必须的环境集合,包含JVM标准实现及Java核心类库。JDK 是 Java 语言的软件开发工具包,针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。若是运行Java程序,只需安装JRE就能够了。若是编写Java程序,须要安装JDK。OpenJDK则是包含了开发与运行的开源实现。最主流的JDK是Sun公司发布的JDK,除了Sun以外,还有不少公司和组织都开发了属于本身的JDK,例如IBM,阿里等。
根据应用领域的不一样,JDK可分为三种版本:
SE(Standard Edition)标准版,一般用的一个版本,从JDK 5.0开始,更名为Java SE
EE(Enterprise Edition)企业版,使用这种JDK开发J2EE应用程序,从JDK 5.0开始,更名为Java EE
ME(Micro Edition)微型版,主要用于移动设备、嵌入式设备上的Java应用程序,从JDK 5.0开始,更名为Java ME