JVM运⾏时数据区域也叫内存布局,但需要注意的是它和Java内存模型((JavaMemoryModel,简 称JMM)完全不同,属于完全不同的两个概念,它由以下5⼤部分组成:
1. 堆(线程共享)
堆的作⽤:程序中创建的所有对象都在保存在堆中。
我们常⻅的JVM参数设置-Xms10m最⼩启动内存是针对堆的,-Xmx10m最⼤运⾏内存也是针对堆 的。
ms是memorystart简称,mx是memorymax的简称。
堆⾥⾯分为两个区域:新⽣代和⽼⽣代,新⽣代放新建的对象,当经过⼀定GC次数之后还存活的对象 会放⼊⽼⽣代。新⽣代还有3个区域:⼀个Endn+两个Survivor(S0/S1)。

垃圾回收的时候会将Endn中存活的对象放到⼀个未使⽤的Survivor中,并把当前的Endn和正在使 ⽤的Survivor清楚掉。
2. Java虚拟机栈(线程私有)
Java虚拟机栈的作⽤:Java虚拟机栈的⽣命周期和线程相同,Java虚拟机栈描述的是Java⽅法执⾏ 的内存模型:每个⽅法在执⾏的同时都会创建⼀个栈帧(StackFrame)⽤于存储局部变量表、操作数 栈、动态链接、⽅法出⼝等信息。咱们常说的堆内存、栈内存中,栈内存指的就是虚拟机栈。
Java虚拟机栈中包含了以下 4 部分:

-
局部变量表:存放了编译器可知的各种基本数据类型(8⼤基本数据类型)、对象引⽤。局部变量表 所需的内存空间在编译期间完成分配,当进⼊⼀个⽅法时,这个⽅法需要在帧中分配多⼤的局部变 量空间是完全确定的,在执⾏期间不会改变局部变量表⼤⼩。简单来说就是存放⽅法参数和局部变 量。
-
操作栈:每个⽅法会⽣成⼀个先进后出的操作栈。
-
动态链接:指向运⾏时常量池的⽅法引⽤。
-
⽅法返回地址:PC寄存器的地址。
什么是线程私有?
由于JVM的多线程是通过线程轮流切换并分配处理器执⾏时间的⽅式来实现,因此在任何⼀个确定的 时刻,⼀个处理器(多核处理器则指的是⼀个内核)都只会执⾏⼀条线程中的指令。因此为了切换线程后 能恢复到正确的执⾏位置,每条线程都需要独⽴的程序计数器,各条线程之间计数器互不影响,独⽴ 存储。我们就把类似这类区域称之为"线程私有"的内存。
3. 本地⽅法栈(线程私有)
本地⽅法栈和虚拟机栈类似,只不过Java虚拟机栈是给JVM使⽤的,⽽本地⽅法栈是给本地⽅法使 ⽤的。
4. 程序计数器(线程私有)
程序计数器的作⽤:⽤来记录当前线程执⾏的⾏号的。
程序计数器是⼀块⽐较⼩的内存空间,可以看做是当前线程所执⾏的字节码的⾏号指⽰器。
如果当前线程正在执⾏的是⼀个Java⽅法,这个计数器记录的是正在执⾏的虚拟机字节码指令的地 址;如果正在执⾏的是⼀个Native⽅法,这个计数器值为空。
程序计数器内存区域是唯⼀⼀个在JVM规范中没有规定任何OOM情况的区域!
5. ⽅法区(线程共享)
⽅法区的作⽤:⽤来存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 的。
在《Java虚拟机规范中》把此区域称之为"⽅法区",⽽在HotSpot虚拟机的实现中,在JDK7时此 区域叫做永久代(PermGen),JDK8中叫做元空间(Metaspace)。
永久代(PermGen)和元空间(Metaspace)是HotSpot中对《Java虚拟机规范》中⽅法区的 实现,它们三者之间的关系就好⽐,对于⼀辆汽⻋来说它定义了⼀个部分叫做"动能提供装置",但 对于不同的汽⻋有不同的实现技术,⽐如对于燃油⻋来说,它的"动能提供装置"的实现技术就是汽 油发动机(简称发动机),⽽对于电动汽⻋来说,它的"动能提供装置"的实现就是电动发动机(简 称电机),发动机和电机就相当于永久代和元空间⼀样,它是对于"制动器"也就是⽅法区定义的实 现。
JDK1.8元空间的变化
-
对于HotSpot来说,JDK8元空间的内存属于本地内存,这样元空间的⼤⼩就不在受JVM最⼤内 存的参数影响了,⽽是与本地内存的⼤⼩有关。
-
JDK8中将字符串常量池移动到了堆中。
运⾏时常量池
运⾏时常量池是⽅法区的⼀部分,存放字⾯量与符号引⽤。
字⾯量:字符串(JDK8移动到堆中)、final常量、基本数据类型的值。
符号引⽤:类和结构的完全限定名、字段的名称和描述符、⽅法的名称和描述符。

6. 内存布局中的异常问题
①Java堆溢出
Java堆⽤于存储对象实例,只要不断的创建对象,并且保证GCRoots到对象之间有可达路径来避免来 GC清除这些对象,那么在对象数量达到最⼤堆容量后就会产⽣内存溢出异常。
上节中已经讲到了,可以设置JVM参数-Xms:设置堆的最⼩值、-Xmx:设置堆最⼤值。下⾯我们来看⼀ 个Java堆OOM的测试,测试以下代码之前先设置Idea的启动参数,如


PS:JVM参数为:-Xmx20m-Xms20m-XX:+HeapDumpOnOutOfMemoryError
Java堆内存的OOM异常是实际应⽤中最常⻅的内存溢出情况。当出现Java堆内存溢出时,异常堆栈信 息"java.lang.OutOfMemoryError"会进⼀步提⽰"Javaheapspace"。当出现"Javaheapspace"则很明确的告知我们,OOM发⽣在堆上。
此时要对Dump出来的⽂件进⾏分析,以MAT为例。分析问题的产⽣到底是出现了内存泄漏(Memory Leak)还是内存溢出(MemoryOverflow)
内存泄漏:泄漏对象⽆法被GC
内存溢出:内存对象确实还应该存活。此时要根据JVM堆参数与物理内存相⽐较检查是否还应该把JVM 堆内存调⼤;或者检查对象的⽣命周期是否过⻓。
以上是我们处理Java堆内存的简单⽅法,处理具体这类问题需要的⼯具以及知识我们放到下⾯第四⼩ 节具体来说。
②虚拟机栈和本地⽅法栈溢出
由于我们HotSpot虚拟机将虚拟机栈与本地⽅法栈合⼆为⼀,因此对于HotSpot来说,栈容量只需要 由-Xss参数来设置。
关于虚拟机栈会产⽣的两种异常:
• 如果线程请求的栈深度⼤于虚拟机所允许的最⼤深度,会抛出StackOverFlow异常
• 如果虚拟机在拓展栈时⽆法申请到⾜够的内存空间,则会抛出OOM异常