虽然是老码农一枚,但是基本上没有写过文章(主要还是个人太懒了),最近开始决定把自己之前学的知识做一下总结,顺便也进行一下回顾。这里总结和回顾的是Jvm系列,是基于周志明写的《深入理解java虚拟机》进行的总结,本章是对jvm内存模型的整理。

知识点
jvm内存模型
image.png
依照书中所写,对这块内容画一个图,我们依照这幅图来讲。从图中我们可以看出主要分为6个部分,程序计数器、虚拟机栈、本地方法栈、方法区、堆、直接内存,一些区域为线程独占,一些区域为线程共享。

程序计数器

1. 这块区域比较简单~~~~,是一块较小的内存空间,存放线程当前执行的字节码指令(包括异常处理、跳转、分支等),因为操作系统的运作模式是一个cpu由多线程共享,当cpu处理了部分当前线程的逻辑之后可能会将时间片分给另一个线程进行逻辑处理,处理完另一个线程的逻辑之后又怎么回来呢?这时候程序计数器就起到了至关重要的作用。他会记录线程上一个处理的点,再回来的时候找到该点并继续处理。
2. 如果执行的是native方法,该计数器为空。
3. 唯一一个规范中没有规定任何oom的区域。
4. 线程私有。

虚拟机栈

1. 在执行方法的时候,会产生一个栈帧,其中就存放了该方法的局部变量、出口地址、操作数栈等信息。这里重点讲一个局部变量表,里面存放了编译期可知的所有数据类型,当一个方法运行的时候,其局部变量表就已经固定了,不会再变。
2. 此区域包含两种异常:线程请求的栈深度大于虚拟机允许的深度时会报stackoverflow异常;如果虚拟机栈动态扩展时申请不到足够内容则会报oom异常。
3. 在总内存一定的情况下,如果一个线程所需要的栈比较大,那么整体可分配线程就会变少,此时如果一定要再分配线程,可以通过参数减少其他区域的内存空间,或者修改代码降低线程所需栈大小来解决。
4. 线程私有。
大体模型如下:
image.png

本地方法栈

1. 机制和虚拟机栈差不多,顾名思义,区别在调native方法时候才会用到。一样是stackoverflow和oom异常。
2. 线程私有。

1. 这个区域比较重要,我们平时分配对象实例的时候(new xxx())都是在和这个区域打交道(当然也不是绝对,大家可以想想哪种情况下不在堆上分配对象(#^.^#)),平时遇到的oom也都是这个区域出现居多,包括我们所说的jvm垃圾回收、内存管理,大多数都是指该内存区域,一般来说,该区域也是所有区域中最大的区域。后面针对该区域,我们会再说一下对象是如何在内存中布局的。基于虚拟机规范,物理上内容可以不用连续,但是逻辑内存需要连续,所以总体是可扩展的(通过-Xmx、-Xms)。后面会再具体介绍下。
2. 我们平常所说的新生代、老年代都是在这个区域中,大概内存模型如下(默认值),后面介绍gc的时候会具体介绍:
image.png
3. 这个不用多说,异常就是oom。
4. 线程共享。对于并发编程中是如何解决该区域的线程共享问题,后面的系列文章会讲到。(感兴趣请持续关注)。

方法区

1. 该区域在jdk1.8之前称为永久代,1.8之后称为元数据空间。该区域存放类的元数据、常量、静态变量信息等。jdk1.8之前可通过(-XX:MaxPermSize)来设置大小,1.8之后用(-XX:MaxMetaspaceSize)这个设置元数据空间大小,后面试过会再更新。
2. 如果分配的时候空间不够报oom异常。
3. 元数据空间具体参数设置参照https://www.cnblogs.com/wzdnwyyu/p/11163033.html
4. 线程共享。

直接内存

1. 又称为堆外内存,不属于jvm管理,通过(-XX:MaxDirectMemorySize)设置大小,默认是和堆内存一样。堆外内存在很多目前主流的技术中用到,比如netty就是使用堆外内存来减少数据拷贝,从而提升性能。
2. 虽然不受jvm内存大小限制,但是受操作系统内存大小限制,不够同样会报oom。
3. 线程共享。

总结

以上主要介绍了jvm内存中6个运行时数据区,整体还是比较简单清晰的,后一篇会介绍对象的内存布局以及逃逸分析技术,敬请期待。
参考资料:周志明《深入理解Java虚拟机》


爱炒股的程序猿
50 声望4 粉丝

每天进步一点点