本书的第一部分主要是介绍部分,正文主要从本部分开始。本部分的核心内容如标题所示:自动内存管理机制。引用一段作者的话:
Java与C++之间有一堵由动态内存分配和垃圾护手技术所围成的“高墙”,墙外面的人想出去,墙里面的人却想出来。
Java与C++最直观的区别就是:Java的内存是自动分配和回收的。一方面,这为内存管理提供了极大的便利性,不要像C++那样对每一片内存都悉心呵护;另一方面,要是coder对JVM一无所知的话,编程时的灵活性就大大降低,当系统出现性能问题和故障时,就会一无所措,在设计方案时也往往无法具体问题具体分析。而本部分的目的就是讲述Java自动内存管理机制,通过增加对JVM内存管理的了解,从而对性能优化和故障处理等问题做到有的放矢。这样,让JVM做个贴心的大总管,管理所有的细节——内存分配和回收,自己只要负责控制大局——指定各区内存大小,指定垃圾回收器,制定服务器架构方案等即可。
本部分由四个章节构成,其中第二章和第三章是重中之重。第四章讲了JDK工具集:只要注意其中两个可视化工具即可,之前的7个命令行工具已经集成在可视化工具中,这两个工具可以在jdk的bin文件夹下找到。第五章讲述了几个调优案例的分析和作者本人对自己的Eclipse进行运行调优的过程。本人将对第二章和第三章进行整理,本篇博文主要关注第二章。
本章主要涉及三个话题:一是JVM运行时内存分区,二是HotSpot JVM在Java堆中进行对象创建、布局和访问全过程,三是内存溢出异常测试。
JVM运行时内存分区主要分五块两种:一种是线程私有的,一种是各线程间共享的。
线程私有的内存分为三种:程序计数器,本地方法栈和Java虚拟机栈。
程序计数器标志当前线程所执行的字节码程序的执行位置,用以保证程序按顺序执行。需要注意的是:程序计数器只在Java方法中起作用,执行本地方法时计数器值为空。本内存区不会抛出异常。
Java虚拟机栈是帧栈入栈和出栈的场所。栈帧是每个方法在执行时创建的用来存储局部变量表、操作数栈、动态链接、方法出入等信息的。本内存区域可能抛出StackOverFlow异常和OutOfMemory异常。
本地方法栈与Java虚拟机栈除了服务对象外,完全一致:Java虚拟机栈为Java方法服务,本地方法栈为Native方法服务。
各线程共享的内存分为两种:Java堆和方法区。
Java堆是JVM管理的内存中最大的一块,被所有线程共享,在VM启动时创建。此区域只用于存放对象实例,且几乎所有的对象实例都存放于此(除了涉及JIT和逃逸分离技术,不懂。。)。Java堆是垃圾管理器管理的主要区域(还有就是下述的方法区),也被称为GC堆。Java堆本身也分很多区:包括新生代的Eden区和Survivor区,老年代,分配缓冲区等。Java堆可能抛出OutOfMemory异常。
方法区用以存储已被虚拟机加载的类信息、常量、静态变量和JIT编译后的代码等数据。HosSpot中以永久代的方式设计方法区。该区域内存回收的目标主要是对常量池的回收和对类型的卸装。本区域会抛出OutOfMemory异常。
最后讲下直接内存,也叫堆外内存。由名字可知,该内存与JVM无关。需要注意的是,直接内存也受总内存大小影响,在设计JVM内存的时候不要忘记直接内存的存在,否则可能会引起OutOfMemory异常。JDK中提供了DirectBuffe类,其对象在直接内存中存放。
从虚拟机的角度看,当遇到new指令到对象创建完成需要经过四步:
此时,从VM的角度看,对象已经创建完毕;但从Java程序的角度看,对象创建刚开始。
HotSpot虚拟机中,对象在内存中的布局主要是对象头和实例数据,还有就是用于对齐填充的部分。
对象头包括两部分信息:第一部分用以存储对象自身的运行时数据,其长度与VM位数相同。第二部分为类型指针,用以指向对象的类元数据,虚拟机通过该指针来确定该对象对应的类。此外,若对象是一个Java数组,对象头中还需要有一块记录数组长度的数据。
实例数据是对象真正存储的有效数据,其存储顺序与字段在类中的定义顺序有关。另外,为了优化,可能会将较窄的数据插入到前面的空隙中,减少对齐填充。
Java程序通过栈上的reference数据来操作堆上的具体对象。该引用定位访问堆中对象位置的方式由JVM自定,主要有句柄访问和直接指针两种方法。其中,HotSpot中采用直接指针法。这部分具体内容可以看书,书上图文并茂,讲的很好。
这里需要说明的是,关于内存溢出异常分为OutOfMemory异常和StackOverflow异常。只有虚拟机栈和本地方法栈会产生StackOverflow异常;Java堆,方法区,虚拟机栈,本地方法栈和直接内存可能会产生OutOfMemory异常;程序计数区不会产生内存异常。
可以根据以上说明自己设计异常测试或使用书中示例。