《深入理解java虚拟机(第2版)》笔记(1)

声明:本文是对《深入理解java虚拟机(第2版)》的片段摘录,版权属于原作者。本文仅供学习交流使用,严禁用于商业用途。

敬请关注微信公众号:Java工程师成长日记(JavaEngineer777)

1 走近java

1.1展望java技术的未来

1.1.1 模块化

目前osgi已经成为java模块化事实上的标准。

Java SE 7发展初期,Sun公司再次提交了一个新的规范请求文档JSR-294Java编程语言中的改进模块性支持(Improved Modularity Support in the JavaProgramming Language),尽管这个JSR仍然没有通过,但是Sun公司已经独立于JCP专家组在OpenJDK里建立了一个名为Jigsaw(拼图)的子项目来推动这个规范在Java平台中转变为具体的实现。

1.1.2 混合语言

当单一的Java开发已经无法满足当前软件的复杂需求时,越来越多基于Java虚拟机的语言开发被应用到软件项目中,Java平台上的多语言混合编程正成为主流,每种语言都可以针对自己擅长的方面更好地解决问题。

1.1.3 多核并行

如今,CPU硬件的发展方向已经从高频率转变为多核心,随着多核时代的来临,软件开发越来越关注并行编程的领域。

1.1.4 进一步丰富语法

Java 5曾经对Java语法进行了一次扩充,这次扩充加入了自动装箱、泛型、动态注解、枚举、可变长参数、遍历循环等语法,使得Java语言的精确性和易用性有了很大的进步。Java 7(由于进度压力,许多改进已推迟至Java 8)中,对Java语法进行了另一次大规模的扩充。 Sun(已被Oracle收购)专门为改进Java语法在OpenJDK中建立了Coin子项目[1]来统一处理对Java语法的细节修改,如二进制数的原生支持、switch语句中支持字符串、<>操作符、异常处理的改进、简化变长参数方法调用、面向资源的try-catch-finally语句等都是在Coin项目之中提交的内容。除了Coin项目之外,在JSR-335LambdaExpressions for the Java TM ProgrammingLanguage)中定义的Lambda表达式[2]也将对Java的语法和语言习惯产生很大的影响,面向函数方式的编程可能会成为主流。

 

1.1.5 64位虚拟机

Java虚拟机也在很早之前就推出了支持64位系统的版本。Java程序运行在64位虚拟机上需要付出比较大的额外代价:运行于64位系统上的Java应用需要消耗更多的内存,通常要比32位系统额外增加10%30%的内存消耗;多个机构的测试结果显示,64位虚拟机的运行速度在各个测试项中几乎全面落后于32位虚拟机,两者大约有15%左右的性能差距。

但是随着硬件的进一步发展,计算机终究会完全过渡到64位的时代,这是一件毫无疑问的事情,主流的虚拟机应用也终究会从32位发展至64位,而虚拟机对64位的支持也将会进一步完善。

 

 2 Java内存区域与内存溢出异常

2.1 运行时内存区域

 《深入理解java虚拟机(第2版)》笔记(1)

 

2.1.1 程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

 

2.1.2 Java虚拟机栈

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame[1])用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

 

2.1.3 本地方法栈

本地方法栈(NativeMethod Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

 

2.1.4 Java堆

对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配[1],但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换[2]优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么绝对了。

 

2.1.5 方法区

方法区(MethodArea)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

 

2.1.6 运行时常量池

运行时常量池(RuntimeConstant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(ConstantPool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

 

2.1.7 直接内存

直接内存(DirectMemory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现,所以我们放到这里一起讲解。

JDK1.4中新加入了NIONewInput/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存(包括RAM以及SWAP区或者分页文件)大小以及处理器寻址空间的限制。服务器管理员在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。