本文为网络资料和个人理解的整合,主要参考以下博文:
http://www.importnew.com/1486.html
http://zhuanlan.zhihu.com/hllvm/19977592
http://www.zhihu.com/question/26913901/answer/35303563
http://blog.csdn.net/bingduanlbd/article/details/8332664
http://www.open-open.com/lib/view/open1408453806147.html
http://blog.csdn.net/zhoudaxia/article/details/26454421
http://blog.csdn.net/zhyhang/article/details/17233251
一、Java的起源
最早大概可追溯至1991年四月份,Sun MicroSystems公司的一个工程师Patrick Naughton被Sun公司自己开发的C++和C语言编译器搞得焦头烂额,他大声抱怨,并威胁要离开Sun转投当时在Steve Jobs领导之下的NeXT公司。领导层为了留住他,给他一个机会,启动Stealth(秘密行动)的项目。随着James Gosling等人的加入,这个项目更名为Green。Sun 公司预料未来科技将在家用电器领域大显身手,于是瞄准用在TV机顶盒等小型系统的程序计。
工程师们一起在加利福尼亚州门罗帕克市沙丘路的一个小工作室里面研究开发新技术,工作小组最初考虑使用C++语言,但是由于使用的是内嵌类型平台,可用的资源极其有限,团队的很多成员发现C++和可用的API在某些方面存在很大问题。根据可用的资金,Sun总裁McNealy提议公司的工程师应该在C++的基础上,开发一种面向对象的环境。于是Gosling试图修改和扩展C++的功能开发一门新语言,最初,他自己称这种新语言 为C++ ++ –,意思是C++加上一些好东西,减去一些坏东西,但是后来Gosling注意到自己办公室外一棵茂密的橡树Oak,就将这个新语言命名为Oak。
到了1992年9月,Oak语言连同Green OS和一些应用程序一起发布在称做Start 7的小设备上,从而使之有了第一次精彩的亮相。随后,Sun开了一家名为FirstPerson的公司,整个团队被转移到这家公司里研发机顶盒,以投标时代华纳公司的一个项目。团队开发出了一个高交互性的设备,但是Active TV太过超前,有线电视业界认为该设备给予用户过多地控制权,因此该项目没有获得时代华纳公司和有线电视服务商的青睐。随后Sun公司沮丧地关闭了FirstPerson,召回了整个团队,因而早期的Java也就中途夭折了,但OaK语言却得到了Sun总裁McNealy的赏识。
直至 1994年下半年,由于Internet的迅猛发展和环球信息网 WWW的快速增长,第一个全球信息网络浏览器Mosaic诞生了;此时,工业界对适合在网络异构环境下使用的语言有一种非常急迫的需求;Games Gosling看见Oak在互联网上应用的前景,对Oak进行了小规模的改造,但Oak已经被另外一个公司注册了,由于众多程序设计师钟爱印度尼西亚爪哇岛的香浓咖啡,于是Java在命名征集会上脱颖而出,于1995年5月正式发布。Java语言中的许多库类名称,多与咖啡有关,如JavaBeans(咖啡豆)、NetBeans(网络豆)、ObjectBeans (对象豆)等等,Java的Logo也正是一杯正冒着热气的咖啡。
二、Java 运行原理
1、高级语言运行过程
在程序真正运行在CPU上之前,必须要让OS的kernel理解我们在编辑器或者IDE里根据每种语言的语法规则敲入的源代码,kernel才能做出相关的调度,所以需要先将源代码转化成可执行的二进制文件,这个过程通常由编译器完成。有些编译器直接将源代码编译成机器码,载入内存后CPU可以直接运行。而机器码的格式与跟具体的CPU架构相关连,例如ARM CPU无法理解Intel CPU机器码。因此,同样的源代码需要根据不同的硬件进行特定的编译。高级语言转换到低级语言的桥梁就是编译器。程序员写好源代码,编译器将源码编译成可执行的机码,然后CPU读取机器码,执行程序。
2、Java语言的执行过程
宽泛地讲,Java源代码(.java)经过java编译器(javac.exe)编译之后,并没有直接转化为机器码,而是转化成一种中间格式——字节码(.class),字节码再经过Java虚拟机解释,转化成机器码,然后经由操作系统到达CPU运行。整个执行过程如下图所示:
Java的跨平台是基于JVM虚拟机这一中间物来实现的,Java源程序经过编译器编译后生成虚拟机能够理解的字节码(ByteCode——class文件的内容),虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定系统上的机器码,然后在特定的机器上运行。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。
3、JVM——Java Virtual Machine
JVM是一个虚构出来的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM 的主要工作是解释自己的指令集(即字节码)并映射到本地的 CPU 的指令集或 OS 的系统调用。
三、 JVM的体系结构
Class Loader:类装载器,从入口处开始按需加载.class文件,填充这些数据到运行时数据区
Execution Engine:执行引擎,JVM的CPU,不断地取指令,JIT编译翻译执行字节码,或者执行本地方法
Runtime Data Areas:运行时数据区,核心区,运行的时候操作所分配的内存区,包括方法区、堆、java栈、PC寄存器、本地方法栈
1、类加载器
类加载器加载其实就是根据编译后的Class文件,将Java字节码载入JVM内存,并完成对运行数据处于的初始化工作,供执行引擎执行。
类加载过程:
装载——链接(验证,准备,解析)—— 初始化
1.Loading:,找到二进制字节码(Class文件)并加载至JVM内存中,标识一个被加载的类:类名+类所在的包名+Class Loader instance ID
2.Linking:
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以及目录。
2、执行引擎
类加载器将.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编译器。
3、运行时数据区
当运行一个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等方法来查看栈的情况。栈的示意图如下:
每个栈帧包含三个部分:本地变量数组,操作数栈,方法所属类的常量池引用
本地方法栈
当程序通过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就通过运行常量池中的引用信息来查找方法和域在内存中的的实际地址。
四、JVM垃圾回收
Garbage Collection的基本原理:
将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停。
垃圾回收算法
1、按照基本回收策略分为以下4种:
Reference Counting:引用计数,比较古老的回收算法;原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,引用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。
Mark-Sweep:标记-清除,此算法执行分两阶段;第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。
Copying: 复制,把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题;但是此算法的缺点就是需要两倍内存空间。
Mark-Compact:标记-整理,结合了Mark-Sweep和Copying两个算法的优点;也分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。避免了Mark-Sweep算法的碎片问题,同时也避免了Copying算法的空间问题。
2、按分区对待的方式分为以下2种
3、按系统线程分为以下3种
处理碎片
由于不同Java对象存活时间是不一定的,因此,在程序运行一段时间以后,如果不进行内存整理,就会出现零散的内存碎片。碎片最直接的问题就是会导致无法 分配大块的内存空间,以及程序运行效率降低。所以,在上面提到的基本垃圾回收算法中,“复制”方式和“标记-整理”方式,都可以解决碎片的问题。
对象创建和对象回收
垃圾回收线程是回收内存的,而程序运行线程则是消耗(或分配)内存的,一个回收内存,一个分配内存,从这点看,两者是矛盾的。因此,在现有的垃圾回收方式 中,要进行垃圾回收前,一般都需要暂停整个应用(即:暂停内存的分配),然后进行垃圾回收,回收完成后再继续应用。这种实现方式是最直接,而且最有效的解决二者矛盾的方式。
但是这种方式有一个很明显的弊端,就是当堆空间持续增大时,垃圾回收的时间也将会相应的持续增大,对应应用暂停的时间也会相应的增大。一些对相应时间要求很高的应用,比如最大暂停时间要求是几百毫秒,那么当堆空间大于几个G时,就很有可能超过这个限制,在这种情况下,垃圾回收将会成为系统运行的一个瓶颈。 为解决这种矛盾,有了并发垃圾回收算法,使用这种算法,垃圾回收线程与程序运行线程同时运行。在这种方式下,解决了暂停的问题,但是因为需要在新生成对象的同时又要回收对象,算法复杂性会大大增加,系统的处理能力也会相应降低,同时碎片问题将会比较难解决。
五、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可分为三种版本:
六、Java环境配置
http://download.oracle.com/otn-pub/java/jdk/8u60-b27/jdk-8u60-linux-i586.tar.gz?AuthParam=1445261134_7d9969800c307edbc9ad1d713948cf68
1、解压下载好的压缩包到新建目录/usr/local/java,方便管理,然后配置环境变量
2、java配置参数:
-XX:+< option >: 开启此参数指定的功能;
-XX:-< option >:关闭功能
-XX:< option >=< value >:给option指定的选项赋值;
java -XX:+PrintFlagsFinal:显示java支持的配置参数及默认值
通过管道计数可以发现其所支持的参数大约有一半为布尔型,默认为false,将其设置为+即可启用,其他非布尔型的参数需要赋予其相应的数值来调用;此外其他参数多与垃圾回收器相关
[root@DQ jdk1.8.0_60]# java -XX:+PrintFlagsFinal |grep bool |wc -l
Usage: java [-options] class [args...]
(to execute a class)
or java [-options] -jar jarfile [args...]
(to execute a jar file)
where options include:
-d32 use a 32-bit data model if available
-d64 use a 64-bit data model if available
-server to select the "server" VM
The default VM is server.
-cp <class search path of directories and zip/jar files>
-classpath <class search path of directories and zip/jar files>
A : separated list of directories, JAR archives,
and ZIP archives to search for class files.
-D=
set a system property
-verbose[:class|gc|jni]
enable verbose output
-version print product version and exit
-version:
require the specified version to run
-showversion print product version and continue
-jre-restrict-search | -no-jre-restrict-search
include/exclude user private JREs in the version search
-? -help print this help message
此处略去一部分。。。。
See http://www.oracle.com/technetwork/java/javase/documentation/index.html for more details.
325
3、Sun JDK监控和故障处理工具:
jconsole:可视化工具,Java的监控与管理控制台
jps:JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程的列表信息
jstat:JVM Statistics Monitoring Tool,收集并显示HotSpot虚拟机各方面的运行数据
jinfo:显示正在运行的某HotSpot虚拟机配置信息
jmap:生成某HotSpot虚拟机的内存转储快照;