进入公司不久,一同事问我,你知道怎么查看JVM的内存溢出吗?当时我就蒙了,怎么查看呢?笔者之前一直使用的是 JProfiler去找的,但是真要说,笔者真说不出来。说起内存溢出,笔者发现笔者似乎对JVM都不太了解。之前虽然有写《从HelloWorld看Class Loader》 ,但是对于整个JVM的内存体系还是不是了解的。所以笔者今天整理了下关于JVM这块的相关内容。作为入门级别的。首先自然是要了解下JVM的内存体系的。

一张图走进JVM内存体系

大家先看这张图:

这个是JVM里面的一个整体的执行流,但我们这边不讨论ClassLoad,只看我们的 运行时数据区,看下面这张图:
jvm_location

在JVM中,内存一般分成三大块: 堆 , 方法区 , 栈。 我们可以根据这个分类来逐步分析JVM的内存体系。

JVM中的堆,我个人习惯性称为类实例区,这么说就好理解了,也就是说这里面放的是实例。也就是我们的Objects。可以说,几乎所有的对象实例都在这里分配内存空间。Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。堆里面一般分为新生代和老年代。数组也是在这里。

新生代

新生代,我之前看资料的时候,也有人称为年轻代,其实这个只是一个翻译问题是吧。在我们的新生代里面会分为三个空间,分别是: Eden空间From Survivor 空间To Survivor空间。通常情况下,这个分配比例是8:1:1来的。

老年代

关于老年代,通俗的理解就是新创建的对象先进入新生代,新生代用完之后,没用了,那就要进入老年代。所以说老年代属于那种创建久而且还未被回收的对象的必然归宿。俗话说得好,落叶归根,魂归故里。所以呢,早老年代一般都会提到他的GC算法,但我们今天只说这个内存结构,不闲聊。

我从网上到了一张图,大家可以参考这张图:

方法区

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

当然很多人愿意称方法区为永久代。但是笔者也看到有人说这两者存在着本质的区别。仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。

Java8 与元数据

在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间
的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native
memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由
MaxPermSize 控制, 而由系统的实际可用空间来控制。

程序计数器

这个区域不是用来计数的。但是作用和计数差不多。程序计数器是一块很小的内存空间,它的作用可以认为是当前线程执行的字节码的行号的指示器。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
大家注意,每一个线程都会有自己的专属的计数器,也就是说,这个是私有的,并不是共享的。同样,如果程序执行的是 Native 方法,那这个计数器为空,也就是 Undefined

JVM栈(JVM Stacks)

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

本地方法栈(Native Method Stacks)

本地方法栈则是为虚拟机使用到的Native方法服务

总结

到这里,JVM里面的基本的内容,可以说是入门的基础算是结束了,接下来要关心的问题就是这里面的堆里面的具体内容了,这里面是线程共享区域,也是新生代老年代区域,那就伴随着垃圾回收。下一篇文章会重点看看这里面的算法,这块笔者还需要找找资料。

对于本篇文章,笔者简单的绘制一张图,这张图基本上可以帮助我们入门JVM的内存结构了。
一张图入门JVM