java语言能大受好评,获得广泛认可的原因有很多,比如说它摆脱了硬件平台的约束,实现了“一次编写,到处运行”的理想。它提供了一个相对安全的内存管理和访问机制。它也实现了对热点代码进行检测和运行时编译和优化,这使得java应用能随着运行时间的增加而获得更高的性能,这些功能的实现都离不开java虚拟机JVM的支持。
JVM的作用非常简单一句话概括就是执行字节码文件,提到字节码文件就不得不讲一下计算机是如何识别高级语言的。
首先高级语言经过编译变成汇编语言,汇编语言再转化成机器指令即一串机器能识别的01二进制数然后交由机器执行。同样的执行java语言也需要类似的这样的一个过程。
java源代码经过Java编译器编译形成字节码文件,每一个字节码文件对应着一个类,字节码文件交给JVM去执行JVM首先通过类加载器加载相应的类,再经过字节码校验器校验变量是否在使用之前进行初始化,方法调用与对象引用类型是否匹配,访问私有数据和方法的规则有没有被违背,对本地变量的访问落在运行时堆栈里,运行时堆栈是否溢出。然后再交给执行引擎将字节码转化成机器指令给操作系统执行。其中翻译字节码主要是对字节码指令进行逐行的执行,JIT编译器我们可以将经常需要执行的热点代码编译成机器指令后缓存起来放在方法区,下次可以直接调用,JIT编译器提升了程序的性能。
得益于强大的虚拟机JAVA语言就有跨平台性。Java通过编译生成字节码之后交给JVM执行,不同平台只要安装了JVM就都可以执行java程序字节码并不关心自己在哪被执行,它只关心被谁执行。你写好了代码之后既可以在windows操作系统下运行也可以在Linux下运行,甚至可以在移动终端上运行。
JVM的强大还体现在它可以跨语言编程,当单一的java开发已经无法满足当前软件的需求时,越来越多的基于JVM的语言开发被应用到软件项目中,Java平台上的多语言混合编程正成为主流每种语言都可以针对自己擅长的方面更好地解决问题。比如在一个项目当中并行处理用Clojure语言,展示层用JRuby/Rails,中间层用JAVA,每个应用层都将使用不同的编程语言来完成,而且,接口对每一层的开发都是透明的各种语言间的交互不存在任何困难,就像是使用自己语言的原生API一样方便,因为他们都运行在同一虚拟机上。JVM根本不关心程序到底是用什么语言实现的他只关心字节码文件。
类加载子系统:负责将class文件加载到JVM的内存模型中。
程序计数器:线程私有,是一块较小的内存空间,他可以看作是当前线程所执行的字节码的行号指示器。
虚拟机栈:线程私有,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型。栈帧包括局部变量表,操作数栈,动态链接,方法出口等信息。
本地方法栈:线程私有,描述的是Java本地方法执行的内存模型,栈帧同虚拟机栈。
堆:所有线程共享,唯一目的是存放对象实例,几乎所有的对象实例都在这里分配内存。
方法区:各个线程共享的内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器后的代码等数据。
执行引擎:高级语言到机器语言的翻译者,解释器,JIT编译器,垃圾回收器。
本地方法接口:Java有时要和操作系统或其他硬件设备进行交互那就需要调用C/C++代码通过本地方法接口来调用本地方法。
基于栈式的指令集架构的特点:
1.设计和实现更简单,适用于资源受限的系统;
2.避开了寄存器的分配难题:使用零地址指令方式分配。(零地址指令是没有地址只有操作数,一地址指令是比如3为操作数,2为他的地址只有一个地址,二地址指令就是一个操作数两个地址)因为栈只需要执行入栈出栈操作,只对栈顶数据进行操作所以不需要地址。
3.不需要硬件支持,可移植性更好,更好地实现跨平台。
基于寄存器的指令集架构:
缺点:1.指令集架构完全依赖于硬件,可移植性差。
优点:1.性能优秀,效率高。(因为他是直接在缓冲区中运行)2.花费更少的指令完成操作。
下面通过代码演示一下,基于栈式的指令集架构的特点。
public class StackStruTest { public static void main(String[] args) { int i = 2; int j = 3; int k = i + j; } }
我们对这段代码反编译。
综上:由于Java要支持跨平台,所以Java的指令都是针对栈来设计的。不同平台CPU架构不同所以不能设计成基于寄存器的。优点是跨平台指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。
Java虚拟机的启动是通过引导类加载器创建的一个初始类来完成的,这个类是有虚拟机的具体实现指定的。
一个运行中的Java虚拟机有一个清晰的任务执行Java程序。程序开始执行时他才运行,程序结束时他才停止。执行一个所谓的Java程序的时候,真真正正的执行的是一个叫做Java虚拟机的进程。
比如:
public static void main(String[] args){ int i = 2; int j = 3; int k = i + j; try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hello"); }
为了观察现象,在程序执行过程中我先让他睡6秒然后再结束。
我们可以清晰的看出在程序运行过程中有一个端口号为12796的进程,等程序运行结束后该进程结束。此时jvm正常退出。
虚拟机的退出有如下几种情况:
1.程序正常执行结束。
2.程序在执行过程中遇到了异常或错误而异常终止。
3.由于操作系统出现错误而导致Java虚拟机进程终止。
4.某线程调用Runntime类或System类的exit方法,或Runtime类的halt方法并且java安全管理器也允许这次exit或halt操作(其实exit和halt本质上都是一样的)。
//System.exit方法 public static void exit(int status) { Runtime.getRuntime().exit(status); }
public void exit(int status) { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkExit(status); } Shutdown.exit(status);//点进去之后发现他调用了Shutdown中的exit方法 }
static void exit(int status) { boolean runMoreFinalizers = false; synchronized (lock) { if (status != 0) runFinalizersOnExit = false; switch (state) { case RUNNING: /* Initiate shutdown */ state = HOOKS; break; case HOOKS: /* Stall and halt */ break; case FINALIZERS: if (status != 0) { /* Halt immediately on nonzero status */ halt(status);//点进去之后他又调到了halt方法 } else { /* Compatibility with old behavior: * Run more finalizers and then halt */ runMoreFinalizers = runFinalizersOnExit; } break; } } if (runMoreFinalizers) { runAllFinalizers(); halt(status); } synchronized (Shutdown.class) { /* Synchronize on the class object, causing any other thread * that attempts to initiate shutdown to stall indefinitely */ sequence(); halt(status); } }
static void halt(int status) { synchronized (haltLock) { halt0(status);//最后是调到了这个方法 } } static native void halt0(int status);//这个方法又是本地方法,与操作系统直接进行交互
来看一下Runtime类的halt方法
public void halt(int status) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkExit(status); } Shutdown.halt(status);//Runtime中也有一个这样的halt方法 }
点进去之后发现殊途同归仍然是调用halt0这个本地方法与操作系统进行交互
static void halt(int status) { synchronized (haltLock) { halt0(status); } }
5.除此之外本地方法接口JNI也规范的描述了JNI Invocation API类来加载或卸载Java虚拟机时,Java虚拟机的退出情况。