一、简介
说起Java,目前可以说是最为热门的编程语言之一。无论是曾经的C++霸主,还是后来的Py,Go等新起之秀,Java在设计上有着绝对的优势。开发者可以从复杂的语言本身中解脱开来,将更多的精力放在业务本身之上。我们不过多讨论Java语言和其他语言的优劣好坏,把目光放在Java语言的本身上,Java语言能够实现编译一次到处运行,这是离不开背后最大的平台,也就是Java虚拟机平台。
从事Java开发相关工作的同学一定对Java虚拟机并不陌生,这是运行Java必不可少的东西。1995年,Sun公司正式发布Java和HotJava产品,Java首次登场。1996年1月23日,Sun推出了JDK1.0。这个版本包含运行环境(JRE)和开发环境(JDK)。在运行环境中包含了核心API,用户界面API,发布技术,JVM几个部分,开发环境包含了编辑Java的编译器(即javac)。在JDK1.0时代,Java使用一款叫做Classic的虚拟机解释执行Java字节码。
1997年2月18日发布JDK1.1版本,这时候支持AWT、内部类、JDBC、RMI、反射等特性。同年收购了Longview Technologies公司,获得Hotspot虚拟机。98年发布JDK1.2版本,支持智能卡和小型设备,也能支持大型服务器,此时Sun 发布JSP/Servlet、EJB规范,将Java分为J2EE、J2SE、J2ME。标志Java向着企业,桌面应用和移动设备3大领域的进军。
2000-2002年,随着JDK1.3和1.4版本的发布,Hotspot虚拟机成为Java默认虚拟机,Classic退出历史舞台。03年底,Java平台发布Scala,同年Groovy加入Java。04年推出JDK1.5,1.5是个大跨度版本,Java开始支持泛型,注解,自动拆封箱,枚举,foreach等功能。
06年JDK1.6发布,Java开源并建立OpenJDK。07年迎来新伙伴Clojure,08年Oracle收购BEA,得到了JRockit虚拟机,09年Twitter宣布后台程序大部分从Ruby迁移Scala,这也是Java程序一次大规模应用。
10年Oracle收购了Sun公司,得到了Hotspot虚拟机。11年JDK1.7发布,正式启用了新的垃圾回收器G1,支持64位系统的压缩指针,以及NIO2.0。14年发布JDK1.8,全新的Lamdba语法是一大特色。至此,Java1.8成为当下最为流行的版本之一。
二、虚拟机的基本结构
类加载子系统
Java虚拟机结构如图中所示,类加载子系统负责从文件系统或者网络中加载Class信息,加载的类信息存放于一块称为方法区的内存空间。
Java堆
Java堆在虚拟机启动时候建立,它是Java程序最主要的内存工作区域,Java对象实例都存放于Java堆中,堆空间是所有线程共享的。这在我们工作中开发需要高度关注,非常容易出问题。Java的NIO库允许Java程序使用直接内存,直接内存是指堆外内存,直接从系统申请的内存区间。堆外内存不受Xmx指令控制,速度优于Java堆。在频繁读写的场合可以考虑使用直接内存。Java堆和堆外内存总和由取决于当前操作系统能给予的最大内存。
PC寄存器
PC寄存器,每个线程启动时候,都会创建一个PC(程序计数器)寄存器。PC寄存器里存储着当下执行的JVM指令地址,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转等等。
由于Java虚拟机是多线程切换来执行指令的,需要保证切换后指令位置的正确,每条线程会有自己的单独程序计数器,互相之间不影响。这类内存区域为“线程私有“的内存。
如果正在执行的是Native方法,这个计数器的值则为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
Java虚拟机栈
Java虚拟机栈也是线程私有的,生命周期通所在线程一致。每个方法被执行时候,都会创建一个栈帧,存储着局部变量表、操作栈、动态链接、方法出口等信息。
局部变量表存放编译期可知的各种基本数据类型,八大基本类型。64位长度的long和double类型数据会占用两个局部变量空间,其他只占一个。对象引用(reference类型)和returnAddress类型(指向字节码指令地址)。
这个内存区域有两种异常。如果线程请求栈深度大于虚拟机所允许深度,将抛出StackOverflowError;如果虚拟机栈可以动态扩展,当无法申请到足够的内存时,会抛出OutOfMemoryError异常。
本地方法栈
本地方法栈和虚拟机栈的作用极其相似,区别为虚拟机栈执行Java方法服务,本地方法栈为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、方式和数据结构没有强制规定。虚拟机可以自由实现。如Sun HotSpot虚拟机直接把本地和虚拟机方法栈一起实现。本地方法栈异常也同虚拟机栈一样。
方法区
方法区与Java堆一样,是各个线程共享的内存区域。它用于存储被虚拟机加载的类信息、常亮、静态变量、即时编辑器编译后的代码等数据。它还有个别名叫Non-heap(非堆)。虚拟机规范对这个区域的描述很宽松,和堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。垃圾收集行为很少会在这个区域出现,但并非数据进了方法区就永远存在了,一般针对这个区域的内存回收目标主要为常量池的回收和对类型的卸载。
垃圾回收系统
垃圾回收系统是Java虚拟机的重要部分,回收器可以针对方法区、Java堆和直接内存进行回收。重点回收的对象是Java堆,Java的垃圾回收器是有虚拟机来接管的,也就是说Java不像C和C++一样,需要手动释放内存。对于不再使用的垃圾对象,垃圾回收器会在后台默默工作,进行查找,标记和回收对象。
以上就是关于Java虚拟机的基本结构介绍,也就是内存模型。后面我们将会深入到虚拟机内部中,进一步看看堆栈的结构,以及垃圾回收器的种类介绍,包括参数调优等等。