java jvm内存模型

之前一直听说jvm里面的一些知识,自己一直没有学习过,最新在看《深入理解java虚拟机》,将其中的一些知识点记录下来。

今天来看一下 jvm的内存模型,本来想用书中的图,无奈我手机拍下来几次,黑白图片显得效果很差,只能网上找图。

下图是我找到最好的一张图了,借花献佛给大家。

从图上可以看出来 根据线程私有分为 程序计数器,虚拟机栈,本地方法栈。

线程公有分为 方法区和 堆。(运行时常量池是方法区的一部分。在HotSpot虚拟机中,方法区也是GC时候的永久代。GC后面有时间会分享出来。)

接下来分开讲一下上面的几部分。

程序计数器(线程私有)

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

Java虚拟机的多线程就是通过线程轮流切换并分配处理器执行时间的方式来实现的,任何一个确定的时刻,一个处理器只会执行一条线程中的指令。所以为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器

每个线程都有自己单独的程序计数器,互不影响这个属于“线程私有”的内存。

如果线程正在执行的是一个Java方法,这个程序计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空
此内存区域是唯一 一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。(只后会介绍除了程序计数器,其他几个内存上面的 OOM情况)。那么 程序计数器为什么不会出现OOM呢?
因为 程序计算器仅仅只是一个运行指示器,它所需要存储的内容仅仅就是下一个需要待执行的命令的地址,无论代码有多少,最坏情况下死循环也不会让这块内存区域超限,因为程序计算器所维护的就是下一条待执行的命令的地址,所以不存在OOM。

Java虚拟机栈(线程私有)

首先也是线程私有的,生命周期就与线程相同。 虚拟机栈里面描述的是 JAVA方法执行的内存模型。每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。

局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

这块区域存在两种异常情况:

1. 如果线程请求的栈深度大于虚拟机允许的深度,抛出StackOverflowError异常;

2. 如果虚拟机栈可以动态扩展,且扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。

本地方法栈(线程私有)

本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务,与虚拟机栈一样,本地方法栈也会抛出 StackOverflowError异常和 OutOfMemoryError异常。

java堆(线程公有)

堆,是java虚拟机所管理的内存中最大的一块,要说的很多,可我反而不知道怎么来说....,先提一点,之后要是有时间写gc的时候,再多写一些堆得内容。

是被所有线程共享的一块区域,在虚拟机启动的时候创建。此内存区域的唯一目的就是 存放对象实例,几乎所有的对象实例以及数组都要在堆上分配。

Java堆是垃圾收集器管理的主要区域。从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代和老年代;新生代再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。
如果堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

根据 java 虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中。

方法区(线程公有)

它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。这一块在HotSpot虚拟机中,被称为 永久代。  这个区域的内存回收目标主要是针对常量池和对类型的卸载。 虚拟机规范堆方法区限制很宽松, 除了和堆一样,不需要连续的内存和可以选择固定大小或 可扩展外,还可以选择不实现垃圾收集。
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

上面就是这五大部分。可以看出和理解 程序计数器 是唯一不会发生 OOM的区域。

最后再说一下 运行时常量池吧。

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

它具有动态性,怎么说? java并不要求常量一定只有在编译期才能产生,也就是运行期间也可以将新的常量放入常量池中。用的比较多的 便是 String类里面的intern方法。 public native String intern(); 印象中 jdk1.6前后是不一样的,曾经为这个问题想不清楚过一次。 帖子在此,有兴趣的可以 看看。https://bbs.csdn.net/topics/392445515

还有个是本地内存,这一块不属于虚拟机的一部分,也不属于虚拟机规范中定义的内存区域,但是这一部分也会出现OOM,在这里提一下即可。

具体每一块内存对应的OOM在下篇文章给出。链接如下:https://blog.csdn.net/cyberHerman/article/details/94052972

今天的分享就到这里。谢谢。

猜你喜欢

转载自blog.csdn.net/cyberHerman/article/details/94045291