为什么Java程序需要运行在虚拟机上
因为Java在设计之初的跨平台特性,我们知道Java程序是运行在Java虚拟机上的。如果你要问为什么Java程序要运行在虚拟机上,我可以反问你几个问题。
为什么买来的电器插上电就能直接使用?你可能会说,因为电是基础设施。电源有统一的标准,电器有统一的标准,所以买来的电器插上电就能用。
不同的电器需要的电源标准不同(台灯和电饭煲的功率),为什么我们不能给不同的电器配置不同的电源呢?因为太麻烦了,虽然这样我们能让每个电器都达到最适应的效率,但是代价未免太大了些。想一想,别人需要用你家的电器时首先要先配一个合适的发电机。
所以电力基础设施需要把不同的发电方式发出的电接入同一个电网, 电力公司负责调度分配电力,并且在使用的时候提供相同标准的输出。虽然这个过程损失了一部分电力,但是大大提高了我们使用电器的便利。
那么回过头来看Java虚拟机。为什么Java程序要运行在虚拟机上,这是因为我们需要实现一次编译到处运行(统一的电源标准)。那么为什么C++不是跨平台的呢,因为C++编译时会转换为与机器关联的机器码,这导致了C++编译的程序只能在特定的机器上运行(依赖发电机)
Java的内存区域
说回Java虚拟机,我们知道C++程序员是需要手动清理内存的,Java程序不需要手动清理,其内存管理由虚拟机负责。那么Java是如何管理内存的呢?
Java虚拟机会把它管理的内存划分成几个不同的数据区。如下图所示:
1.程序计数器(PC寄存器)
程序计数器时当前线程所执行的字节码的行号指示器。因为Java虚拟机的多线程轮换是通过线程轮流切换分配处理器执行时间来实现的,所以每个线程都会有一个程序计数器。
另外,Java程序既有Java方法也有本地方法。假如线程执行的是Java方法,那么程序计数器记录的是当前执行的字节码指令的地址。如果是本地方法,则计数器值为空。此区域是Java虚拟机规范中唯一的没有规定OutOfMemoryError的区域。
2.Java虚拟机栈
Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链表、方法出口等信息。当退出当前执行的方法时,不管是正常返回还是异常返回,Java虚拟机均会弹出当前线程栈帧并舍弃。
3.本地方法栈
本地方法栈跟Java虚拟机栈的作用基本相同,本地方法栈其实是Native方法执行过程中存储方法的数据结构的栈空间。
4.Java堆
Java堆(Java Heap)是Java虚拟机管理内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的时存放对象实例。另外Java堆也是垃圾回收器管理的主要区域,因此很多时候也成为「GC堆」。
5.方法区
方法区和Java堆一样,是各个线程共享的内存区域,它是用来存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
其中常量会单独放置在运行时常量池(JDK8中放到了堆中)中。运行时常量池具备动态性,也就是在程序运行期间也可以将新的常量放入池中,这种特性使用较多的事String类的intern()方法。
6.直接内存
直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存的频繁使用也可能导致OutOfMemoryError
异常出现。
JDK 1.4中新加入了NIO(New Input/Output)
类,引入了一种基于通道与缓冲区的I/O方式,它可以使用本地函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer
对象作为这块内存的引用进行操作。在一些常见中这种操作能显著提高性能。
总结
本篇我们了解了为什么Java虚拟机需要运行在虚拟机上,用了一个电源和电器的例子。Java虚拟机将内存分成两种,线程共享和线程独占内存区域。其中线程独占的区域有程序计数器、Java虚拟机栈、本地方法栈。线程共享区域有Java堆、方法区(包括运行时常量池)。在Java虚拟机运行时数据区之外还有直接内存,此区域可以使用堆外内存来提升I/O的速度。