我们说的 JVM 内存是什么

来源:互联网 发布:亚洲顶尖婚礼网络 编辑:程序博客网 时间:2024/04/29 12:58

我们这里讲的 JVM是 HotSpot 虚拟机,也是我们大多数时候所讨论和使用的 JVM。除了 HotSpot 外,还有比如 IBM J9 VM 和著名的商用 Zing VM。J9 主要是 IBM 的产品中使用,Zing VM 则是商业付费的。

另外,本篇的讨论背景是 jdk 1.7 和 jdk 1.8 版本,只是概括性的介绍 JVM 内存分布模型,具体针对每一个区域的说明会在以后的文章中介绍。

JVM 做为 Java 跨平台的基石,制定了其特有的规范,凡是符合 JVM 规范标准的语言实现,都可在 JVM 中运行,例如 Jython 、Scala 、Groovy、Kotlin 等。JVM 规范了类文件格式、运行时、编译器等,今天主要说的是 JVM 内存模型以及 HotSpot 虚拟机的具体实现。

下面这张图大概是逃不过的,翻开任意一本讨论 JVM 的书,或者讲 JVM 的在线文章,总能看到下面这张大同小异的图。


下面一一介绍各个区域的功能。

程序计数器(PC 寄存器)

1、程序计数器是线程独占的,每个线程都有自己的程序计数器;

2、它是用来记线程当前需要执行的指令的地址;

3、它所占空间大小是固定的,所以不会发生内存溢出异常;

虚拟机栈

1、虚拟机栈是线程独占的;

2、表示方法执行的内存模型,每调用一个 Java 方法就会为此方法在虚拟机栈中生成栈帧;

3、存储局部变量表、操作数栈、动态链接、方法出口等信息;

4、方法的调用和完成,对应栈帧在虚拟机栈上入出栈过程;

5、如果线程申请请求的栈深度大于虚拟机栈所允许的最大栈深度时,会抛出 StackOverflowError 异常,常见于递归调用;

6、如果虚拟机栈可以动态拓展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成拓展,或者在建立新线程的时候没有足够的内存去创建对应的虚拟机栈,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异常,一般不容易出现;

本地方法栈

1、本地方法栈是区别于虚拟机栈的,当调用的方法是本地方法(例如 C 语言实现的方法)时,会用到本地方法栈;

2、同样会抛出 StackOverflowError 和 OutOfMemoryError 异常;

3、在HotSopt虚拟机中直接就把本地方法栈和虚拟机栈合二为一;

1、堆是所有线程共享的;

2、所分配绝大多数对象实例和数组都存在堆上;

3、Java 堆也是垃圾回收的主要区域,因此也被称作 GC 堆;

4、堆分为新生代和老年代,新生代又分为 Eden 区和两个 Suvivor 区 s0 和 s1;

5、当在堆上申请不到足够的空间时,会抛出 OutOfMemoryError 异常;

方法区

1、方法区是所有线程共享的;

2、用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译(JIT)后的代码等数据;

另外,方法区在 Java 7 和 Java 8 中还有一些区别

Java 7 中的方法区

JDK 1.7 中,很多人习惯将方法区称为“永久代”,是因为 HotSpot 虚拟机以永久代来实现方法区,这样一来 JVM 的垃圾收集器可以像管理堆区一样管理这部分区域,从而不需要专门为这部分设计垃圾回收机制,其他的例如 J9 、IBM VM 是没有永久代的概念的。

可以这样理解:方法区是 JVM 规范中定义的一个区域,但规范只做理论指导,而永久代是 HotSpot 虚拟机团队在此规范上的具体实现方式。

顺便说一句,方法区有一部分内容叫做运行时常量池,在 JDK 1.7版本中已从永久带移除,放到了 Java 堆中。

如果永久代被占满,将会抛出 java.lang.OutOfMemoryError: PermGen space 异常,一般发生在产生大量动态生成类的情景中,例如使用 Spring MVC、MyBatis 等技术框架的时候。

Java 8 中的方法区

到了 JDK 1.8,HotSpot 虚拟机已经完全移除永久代,也就是说不使用永久代的方式来实现方法区,也就是 JDK1.8 中不存在永久代的概念了,取而代之的是 Metaspace(元空间),它也是对方法区的一个实现方式,相比于永久代方式,元空间方式解决了永久代方式存在的一些问题。

元空间数据分配在本地内存中,也就是系统可用内存。所以默认情况下,不会发生 OOME 问题,但还是可以通过参数XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 来限制元空间的初始大小和最大占用空间。

堆外内存

除了上面提到的 Java 堆和永久代外(永久代逻辑上属于堆),还可以在程序里直接分配堆外内存,也就是数据不在 Java 堆上分配,而是直接在本地内存分配,这部分内存被称作堆外内存。

对于开发者来说,主要是通过 java.nio.DirectByteBuffer 分配的内存,如果不选择主动垃圾回收的方式(例如调用相关清理 API 或者调用 system.gc() ),那么堆外内存只能在 full gc 时被回收。所以堆外内存还是要慎用。

如果有使用 NIO 技术,例如 Netty 等框架的时候,有可能发生本地内存被耗尽的情况。

欢迎关注公众号:gushidefengzheng  「古时的风筝」


也欢迎加qq群交流:463662022