AnthonyZero's Bolg

JVM-内存区域

JAVA虚拟机运行时会将JVM使用的内存划分为不同的区域,每个区域负责不同的功能,以及各个区域的创建,销毁都各不相同。

下图是JVM运行时内存数据区的划分

Alt text

程序计数器

每个线程都拥有一个独立的程序计数器,用于记录当前线程所要执行的字节码指令的行号指示器。当多线程运行时,每个线程切换后需要知道上一次所运行的状态、位置。由此也可以看出程序计数器是每个线程私有的。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是Native方法,这个计数器值则为空。

虚拟机栈

该区域也是线程私有的,并且与线程的生命周期相同。主要负责方法执行的内存部分,在每个方法执行时都会创建一个栈帧,同时存储局部变量表,操作数,动态链接,方法出口等相关信息。

局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置) 每一次方法的调用到完成,都对应一个栈针在虚拟机栈中出入栈的过程。

本地方法栈

和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一

方法执行完毕后相应的栈帧也会出栈并释放内存空间,也有可能出现 StackOverFlowError 和 OutOfMemoryError 两种异常

JAVA堆(JAVA Heap)

JAVA堆是JAVA虚拟机管理的内存,最大,最重要的部分,是所有线程共享的区域,也是GC(垃圾回收)的主要回收部分。几乎所有对象实例都在这里分配内存。唯一目的就是存放对象实例。

从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:再细致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
Alt text

在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)

这块内存属于线程共享区域。

方法区(JDK1.7)

方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。这块区域也被称为永久代

该区域会抛出OutOfMemoryError异常,主要是启动时需要加载的类,常量,静态变量等信息特别多,超过了该区域设置的内存空间,就会抛出异常

元数据区(JDK1.8)

在 JDK1.8 中已经移除了方法区(永久代),并使用了一个元数据区域进行代替(Metaspace)。

默认情况下元数据区域会根据使用情况动态调整,避免了在 1.7 中由于加载类过多从而出现 java.lang.OutOfMemoryError: PermGen。

运行时常量池

运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)

JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池

直接内存

直接内存又称为 Direct Memory(堆外内存),它并不是由 JVM 虚拟机所管理的一块内存区域。

有使用过 Netty 的朋友应该对这块并内存不陌生,在 Netty 中所有的 IO(nio) 操作都会通过 Native 函数直接分配堆外内存

该区域使用主要是在NIO的缓冲区的使用,会用到系统直接内存,因为是通过本地方法分配的内存,当本机无法分配更多的内存时就会抛出内存溢出异常

JVM 常见配置参数

  • -Xms64m 最小堆内存64m -Xmx128m 最大堆内存128m
  • -Xmn64m 来指定新生代分配的空间大小
  • -XX:+HeapDumpOnOutOfMemoryError 当发生内存溢出时生成dump文件
  • -Xss128k 线程栈容量128k
  • -XX:PermSize=10M -XX:MaxPermSize=20M 控制初始化方法区和最大方法区大小
  • -XX:+PrintHeapAtGC 当发生 GC 时打印内存布局。
  • -XX:MaxMetaspaceSize 来控制元数据区最大内存
  • -XX:NewSize=30m 新生代初始化大小为30m. -XX:MaxNewSize=40m 新生代最大大小为40m
  • -XX:NewRatio=2 设置新生代和老年代的比例,比如值为2,则老年代是新生代的2倍,即新生代占据堆内存的1/3(设置了Xmn的情况下,该参数不需要进行设置)
  • -XX:SurvivorRatio=8 设置的是Eden区与每一个Survivor区的比值,可以反推出占新生代的比值,Eden为8, 两个Survivor为2, 也就是说Eden占新生代的8/10,S0和S1各占新生代的1/10
  • -XX:MaxTenuringThreshold=15 晋升到老年代的对象年龄
  • -XX:PretenureSizeThreshold 对象超过多大字节是直接在老年代分配 无默认值