说明:作java开发的几乎都知道jvm这个名词,可是因为jvm对实际的简单开发的来讲关联的仍是很少,通常工做个一两年(固然不包括爱学习的及专门作性能优化的什么的),不多有人能很好的去学习及理解什么是jvm,以及弄清楚jvm的工做原理,我的认为这块仍是很是有必要去认真了解及学习的,特别是刚入门或入门不久的java开发来讲,这是java的基石。java
Java程序的跨平台特性主要是指字节码文件能够在任何具备Java虚拟机的计算机或者电子设备上运行,Java虚拟机中的Java解释器负责将字节码文件解释成为特定的机器码进行运行。所以在运行时,Java源程序须要经过编译器编译成为.class文件。众所周知java.exe是java class文件的执行程序,但实际上java.exe程序只是一个执行的外壳,它会装载jvm.dll(windows下,下皆以windows平台为例,linux下和solaris下其实相似,为:libjvm.so),这个动态链接库才是java虚拟机的实际操做处理所在。linux
JVM是JRE的一部分。它是一个虚构出来的计算机,是经过在实际的计算机上仿真模拟各类计算机功能来实现的。JVM有本身完善的硬件架构,如处理器、堆栈、寄存器等,还具备相应的指令系统。Java语言最重要的特色就是跨平台运行。使用JVM就是为了支持与操做系统无关,实现跨平台。因此,JAVA虚拟机JVM是属于JRE的,而如今咱们安装JDK时也附带安装了JRE(固然也能够单独安装JRE)。算法
粗略分来,JVM的内部体系结构分为三部分,分别是:类装载器(ClassLoader)子系统,运行时数据区,和执行引擎。windows
每个Java虚拟机都由一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类和接口),并赋予惟一的名字。每个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。JVM的两种类装载器包括:启动类装载器和用户自定义类装载器,启动类装载器是JVM实现的一部分,用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。数组
主要的执行技术有:解释,即时编译,自适应优化、芯片级直接执行其中解释属于第一代JVM,即时编译JIT属于第二代JVM,自适应优化(目前Sun的HotspotJVM采用这种技术)则吸收第一代JVM和第二代JVM的经验,采用二者结合的方式 。缓存
自适应优化:开始对全部的代码都采起解释执行的方式,并监视代码执行状况,而后对那些常常调用的方法启动一个后台线程,将其编译为本地代码,并进行仔细优化。若方法再也不频繁使用,则取消编译过的代码,仍对其进行解释执行。性能优化
堆:存放全部程序在运行时建立的对象微信
方法区:当JVM的类装载器加载.class文件,并进行解析,把解析的类型信息放入方法区。架构
Java栈和PC寄存器由线程独享jvm
JVM栈是线程私有的,每一个线程建立的同时都会建立JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址
本地方法栈:存储本地方法调用的状态
由于jvm运行时的数据区对咱们开发来讲仍是特别重要要掌握的知识因此单拎开来西说下。
方法区域(Method Area)
在Sun JDK中这块区域对应的为PermanetGeneration,又称为持久代。
方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中经过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在必定的条件下它也会被GC,当方法区域须要使用的内存超过其容许的大小时,会抛出OutOfMemory的错误信息。
堆(Heap)
它是JVM用来存储对象实例以及数组值的区域,能够认为Java中全部经过new建立的对象的内存都在此分配,Heap中的对象的内存须要等待GC进行回收。
堆是JVM中全部线程共享的,所以在其上进行对象内存的分配均须要进行加锁,这也致使了new对象的开销是比较大的。
Sun Hotspot JVM为了提高对象内存分配的效率,对于所建立的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的状况计算而得,在TLAB上分配对象时不须要加锁,所以JVM在给线程的对象分配内存时会尽可能的在TLAB上分配,在这种状况下JVM中分配对象内存的性能和C基本是同样高效的,但若是对象过大的话则仍然是直接使用堆空间分配。
TLAB仅做用于新生代的Eden Space,所以在编写Java程序时,一般多个小的对象比大的对象分配起来更加高效。
JavaStack(java的栈):虚拟机只会直接对Javastack执行两种操做:以帧为单位的压栈或出栈
每一个帧表明一个方法,Java方法有两种返回方式,return和抛出异常,两种方式都会致使该方法对应的帧出栈和释放内存。
帧的组成:局部变量区(包括方法参数和局部变量,对于instance方法,还要首先保存this类型,其中方法参数按照声明顺序严格放置,局部变量能够任意放置),操做数栈,帧数据区(用来帮助支持常量池的解析,正常方法返回和异常处理)。
ProgramCounter(程序计数器)
每个线程都有它本身的PC寄存器,也是该线程启动时建立的。PC寄存器的内容老是指向下一条将被执行指令的饿地址,这里的地址能够是一个本地指针,也能够是在方法区中相对应于该方法起始指令的偏移量。
若thread执行Java方法,则PC保存下一条执行指令的地址。若thread执行native方法,则Pc的值为undefined
Nativemethodstack(本地方法栈):保存native方法进入区域的地址
依赖于本地方法的实现,如某个JVM实现的本地方法借口使用C链接模型,则本地方法栈就是C栈,能够说某线程在调用本地方法时,就进入了一个不受JVM限制的领域,也就是JVM能够利用本地方法来动态扩展自己。
Sun的JVMGenerationalCollecting(垃圾回收)原理是这样的:把对象分为年青代(Young)、年老代(Tenured)、持久代(Perm),对不一样生命周期的对象使用不一样的算法。(基于对对象生命周期分析)
一般咱们说的JVM内存回收老是在指堆内存回收,确实只有堆中的内容是动态申请分配的,因此以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是以前提到的MethodArea,不属于Heap。
GC的基本原理:将内存中再也不被使用的对象进行回收,GC中用于回收的方法称为收集器,因为GC须要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽量的缩短GC对应用形成的暂停
(1)对新生代的对象的收集称为minor GC;
(2)对旧生代的对象的收集称为Full GC;
(3)程序中主动调用System.gc()强制执行的GC为Full GC。
不一样的对象引用类型, GC会采用不一样的方法进行回收,JVM对象的引用分为了四种类型:
(1)强引用:默认状况下,对象采用的均为强引用(这个对象的实例没有其余对象引用,GC时才会被回收)
(2)软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的状况下才会被GC)
(3)弱引用:在GC时必定会被GC回收
(4)虚引用:因为虚引用只是用来得知对象是否被GC
Young(年轻代)
年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的而且此时还存活的对象,将被复制年老区(Tenured。须要注意,Survivor的两个区是对称的,没前后关系,因此同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。并且,Survivor区总有一个是空的。
Tenured(年老代)
年老代存放从年轻代存活的对象。通常来讲年老代存放的都是生命期较长的对象。
Perm(持久代)
用于存放静态文件,现在Java类、方法等。持久代对垃圾回收没有显著影响,可是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候须要设置一个比较大的持久代空间来存放这些运行过程当中新增的类。持久代大小经过-XX:MaxPermSize=进行设置。
我有一个微信公众号,常常会分享一些Java技术相关的干货;若是你喜欢个人分享,能够用微信搜索“Java团长”或者“javatuanzhang”关注。