JAVA虚拟机运行时会将JVM使用的内存划分为不同的区域,每个区域负责不同的功能,以及各个区域的创建,销毁都各不相同。
下图是JVM运行时内存数据区的划分
程序计数器
每个线程都拥有一个独立的程序计数器,用于记录当前线程所要执行的字节码指令的行号指示器。当多线程运行时,每个线程切换后需要知道上一次所运行的状态、位置。由此也可以看出程序计数器是每个线程私有的。
如果线程正在执行的是一个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空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
这块内存属于线程共享区域。
方法区(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 对象超过多大字节是直接在老年代分配 无默认值