JVM笔记整理(第2章)

来源:互联网 发布:托福听力软件推荐 编辑:程序博客网 时间:2024/06/05 15:30

这一章主要有三部分内容:java虚拟机内存是如何划分的及划分后每部分都存放了哪些内容;其次,讲述了我们常用的虚拟机HotSpot,它里面是如何存储对象的;最后是,简单讲解了各个区域会产生的异常。

 

一、内存划分

总的来说,java虚拟机运行时,内存数据区分为2类,包括线程私有区域和线程公有区域。公有区域有2个,包括:方法区、堆;私有区域有3个,包括:虚拟机栈、本地方法栈、程序计数器。

接下来依次说一下各个区域的功能和存放内容。

 

★程序计数器

1、功能:程序计数器(ProgramCounter Register),是当前线程执行的字节码指令的行号指示器。(组成原理中曾讲到过程序计数器pc,用来存放下一条指令的地址。这一点二者是类似的,都是用来标识指令的,只不过一个是字节码指令,一个是计算机指令系统的指令)。

2、存放内容:如果线程执行的是一个java方法,则这个计数器存放的是虚拟机字节码指令的地址;如果线程执行的是Native方法,则计数器值为空。

3、占用内存空间大小:在内存空间中占用很小的区域。

4、异常:此区域是唯一一个java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

 

★java虚拟机栈

1、存放内容:栈帧。栈帧存放了:局部变量表、操作数栈、动态链接、方法出口等信息。这些名词在我正在看的这本书的第8章有完整介绍。到那里我再继续整理与之相关的内容。

2、功能:其功能有存放内容决定,即为栈帧决定。每个java方法都对应一个栈帧。每个java方法的执行都对应着一个栈帧从入栈到出栈的过程。

3、生命周期:与线程相同。很好理解,因为java虚拟机栈是线程私有的,自然和线程的生命周期相同。

4、常说的“栈内存”就是说的虚拟机栈,或者是说该栈中的局部变量表部分。但是人们常把java内存划分为“堆内存”和“栈内存”的方式是比较粗力度的。这种说法只能说明了在java内存中,大多数程序员最关注的,与对象内存分配最密切的是这两块。

5、异常:此区域包括两种异常情况:a、如果线程请求的栈深度大于虚拟机允许的栈深度,讲抛出StackOverflowError异常。b、如果虚拟机可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemeoryError异常。

 

★本地方法栈

1、功能:和虚拟机栈功能类似,只是对象不一样。虚拟机栈服务对象为java方法,而本地方法栈服务对象为Native方法。

2、异常:同java虚拟机栈。

 

 ★  java堆

1、功能:存放实例对象。这也是这块区域的唯一作用。所有的实例对象和数组都要在此处分配内存。但随着JIT编译器的发展,所有对象的分配都在堆上也没有那么“绝对”了,但是只有微小变化。所以只能说不绝对,但不是很少遵循这个约定了。

2、”GC堆”:java堆是垃圾收集器管理的主要区域。

3、java堆是否在内存上是连续的:在物理上可以不连续,只要逻辑连续即可。当前的主流虚拟机都是可扩展的。

 

★  方法区

1、功能:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。

2、与堆的关系:java虚拟机规范规定:方法区是堆的一个逻辑部分。但是他还有一个别名“Hon-Heap”(非堆),目的是应该与堆区分开来。

3、“永久代”:很多人把方法区称为“永久代”,但本质上二者不等价。这样称呼的原因是:HotSpot虚拟机设计团队用永久代实现了方法区,以便垃圾收集器可以像管理java堆一样管理方法区。

4、方法区是否在内存上是连续的:不需要。大小可固定也可可扩展。还可以选择不实现垃圾收集。

5、内存回收目标:常量池的回收、类型对卸载。当然,在JDK1.7后,字符串常量池已经从方法区移出。

 

补充概念:

a、运行时常量池

*属于方法区的一部分。

*功能:存放编译期生成的各种字面量和符号引用。一般来说,除了保存Class文件描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。

*重要特征:动态性。Java语言不要求常量一定在编译期产生,运行期间也可以将新的常量放入到常量池中。

 

b、直接内存

*不是虚拟机的一部分。也不是虚拟机规范定义中的内存区域。

*属于堆外内存。使用Native函数直接分配内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用,进行操作。这样能在一些特殊场景中显著提高性能。

 

二、HotSpot虚拟机

1、对象创建

其实第7章有很详细的类加载的讲述。此处只是简单的介绍了通用的一些逻辑。到后面再做详细整理。

*类加载检查

*为对象分配内存

这里有2种方式分配内存。一种是“指针碰撞”;另一种是“空闲列表”。选择哪种方式由java堆是否规整决定,而java堆是否规整由所采用的垃圾收集器是否带有压缩管理有关。

*初始化分配的内存空间

这保证了实例字段不需要赋初值就能使用。

*进行对象设置

比如,这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码等等。这些信息存放在对象的对象头中。

到此为止,从虚拟机的角度讲,一个新对象已经产生。

接下来会执行<init>方法,真正执行程序员写的初始化方法了。

 

2、对象的内存布局

对象在内存中存储的布局包括3部分:对象头、实例数据、对齐填充。

*对象头:存储了2部分内容。

a、存储对象自身的运行时数据,如哈希码等。

b、类型指针。即为对象指向它的类元数据指针,虚拟机通过这个指针确定这个对象是哪个类的实例。

*实例数据:程序代码中定义的各种字段内容。

*对齐填充:非必然存在,仅仅是起占位符的作用。

 

3、对象的访问定位

java程序需要通过栈上的reference数据来操作java堆上的具体对象。

*对象访问方式目前主流的有2种:使用句柄、直接指针。

*使用句柄:java堆中会有一块内存区域专门作为句柄池。

  好处:reference中存储的是句柄地址,在对象被移动时,只会改变句柄中的实例指针,而reference本身不用改。

*直接指针:reference中存储的就是对象地址。

  好处:访问对象速度快,因为它节省了一次指针定位的时间开销。

*HopSpot使用的访问定位方式:直接指针

 

 

三、异常分类

比较简单,这里就不写了。