做开发做到一定阶段,必要要对自己所用的工具有一些深入的了解,不仅要知其然,还要知其所以然,方能更上一层楼。近日得空,又回顾了一下JVM的内容,发现自己做总结要远比自己单纯看书要难的多,但是当总结完后,就会有更深刻的了解。
对于Java虚拟机这部分,要了解的东西其实有很多。个人认为,首先还是要先了解它的结构,尤其是内存模型,这样也可更方便自己进行后面内容的深入理解。
其内存模型中其实包括很多部分,主要的有程序计数器、虚拟机栈、堆等,有的也是只属于单独线程,并不是线程间共享的内存空间,所以,此次我按照是否是线程独享进行了目录分类进行理解和画图。另外,在最后也简单介绍了“直接内存”相关的内容。

线程独享
对于线程独享的内存空间,每个线程都会有自己独立的区域,互相不影响,这部分有程序计数器、虚拟机栈和本地方法栈。
程序计数器记录当前线程执行的情况;
虚拟机栈是Java方法执行的内存模型;
本地方法栈是Native方法执行的内存模型。
程序计数器

它一块比较小的内存空间,是当前线程执行的字节码的行号指示器,会记录程序执行到哪一行。字节码解释器会按照这个计数器的记录来选择要执行的字节码指令。
每个线程都有自己独立的程序计数器。Java虚拟机会按照线程的轮流切换,来给每个线程分配处理器的执行时间,进而实现多个线程的执行,但同一时刻,一个处理器也只能处理一条线程的指令。为了在切换线程的时候能够准确找到此线程上次执行的位置,一个程序的计数器就是必不可少的,每个线程都有自己独立的计数器来记录自己执行到了哪里。
- 线程执行Java方法时,计数器会记录字节码指令的地址;
- 线程执行Native方法是,计数器的值为空
注意:Java虚拟机规范中没有对程序计数器的内存区域做OutOfMemoryError规定
Java虚拟机栈

它是线程私有的,其生命周期与线程相同,用来描述Java方法执行的内存模型,每个方法在执行的时候都会创建一个栈帧。
栈帧
它是程序运行的基本方法单元。需要执行一个方法时,一个栈帧就会进入虚拟机栈,执行完毕后,就会出栈。它包括局部变量表、操作数栈、动态链接、方法出口等信息。其实,对于栈帧中的几个组成部分,我们平时谈论局部变量表比较多。
局部变量的组成:
1、基本数据类型
boolean、byte、char、short、int、float、long、double。
long和double占2个局部变量空间,其他占据1个局部变量空间。
2、对象引用
* 指向对象起始地址的引用指针
* 指向代表对象的句柄
* 指向与对象相关的其他内容
3、returnAddress
指向一条字节码指令的地址
异常
- StackOverflow
线程请求的栈深度大于虚拟机允许的深度,就会报此异常。
- OutOfMemoryError
在Java的虚拟机规范中允许设置虚拟机栈为动态扩展或固定长度。如果设置为动态扩展模式,但是又无法申请到足够的内存是就会报此异常。
本地方法栈

它与Java虚拟机栈作用类似,都是方法执行的基本单元,只不过本地方法栈是针对Native方法,而且也没有对这个栈中使用的语言、使用方式与数据结构做严格规定。有的虚拟机也会把本地方法栈和虚拟机栈合二为一。也会抛出StackOverflow和OutOfMemoryError的异常。
线程共享
此部分是所有线程共享的内存空间,各个线程均可以把自己的数据放到此处,但是并不会互相间混乱,这部分有Java堆和方法区。
Java堆

堆内存是虚拟机管理的内存中最大的一块,所有线程共享的区域,用于存放对象的实例。也是垃圾回收的重点管理区域,大体分为新生代和老年代进行管理,当然也就会有新生代和老年代的垃圾回收了。
因为多次执行垃圾回收以及最初分配的问题,此部分区域很有可能出现物理上不连续,但是逻辑上连续的情况。此部分区域可设计成固定大小,也可设计成可扩展的形式,如果堆内存不足并且也没法继续扩展时,就会抛出OutOfMemoryError的异常。
对于可扩展的模式,可以对其大小进行设置:
-Xms 最小堆内存
-Xmx 最大堆内存
方法区

方法区用于存储已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,是所有线程都可以访问的内存区域。此部分也经常和永久区联系在一起,jdk1.8之前都是永久区的概念,而在jdk1.8及以后就变为了元数据区。
此部分很少进行垃圾回收,也允许不进行垃圾回收,一旦进行垃圾回收,一般是进行常量池的回收和类型卸载。
此部分区域不够内存分配时,也会抛出异常OutOfMemoryError。
运行时常量池
此部分区域其实是方法区的一部分,用于存放类的常量池信息。常量池信息属于Class文件,用于存放编译器生成的各种字面量和符号引用。但是此区域的常量信息并不都是只有在编译期间生成的,也有运行期间动态进入的。
如果常量池无法申请到内存时,就会抛出异常OutOfMemoryError。
直接内存

直接内存并不是虚拟机运行时数据区的一部分,但是此部分内存也会被频繁使用,并且作用也很重要,NIO就用到了这一部分。
NIO会使用Native函数来分配Java堆内存之外的内存,把相关NIO用到的一些数据,尤其是Native函数会用到的一些数据放到此区域,然后在Java堆中用DirectByteBuffer对象做一个此内存区域的引用。
因为直接内存与Java堆内存并没有分配上的限制,所以其大小与堆内存无关,但是与物理内存有关,也依然有可能出现OutOfMemoryError的异常。
以上的个人总结是从读书和查资料而学习到的,里面也很有可能存在错误之处,还望看官们不吝赐教。