Java内存管理:Java内存区域 JVM运行时数据区
在内存管理领域,C/C++程序开发与Java程序开发有着完全不同的理念:
比较*,但多了些工作量,且容易出现内存泄露和内存溢出等问题;
如上图, Java虚拟机规范定义了字节码执行期间使用的各种运行时数据区,即JVM在执行Java程序的过程中,会把它管理的内存划分为若干个不同的数据区域,包括:
程序计数器、java虚拟机栈、本地方法栈、java堆、方法区、运行时常量池;
这些数据区域是在Java虚拟机启动时创建的,只有当Java虚拟机退出时才会被销毁;
这些数据区域是每个线程的"私有"数据区,每个线程都有自己的,不与其他线程共享;
上面图片展示的是JVM规范定义的运行时数据概念模型,实际上JVM的实现可能有所差别,下面在介绍各内存数据区时会给出一些HotSpot虚拟机实现的不同点和调整参数。
程序计数器(Program Counter Register),简称PC计数器;
每个线程都需要一个独立的PC计数器,生命周期与所属线程相同,各线程的计数器互不影响;
JVM字节码解释器通过改变这个计数器的值来选取线程的下一条执行指令;
JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的;
在任意时刻,一个线程只会执行一个方法的代码(称为该线程的当前方法(Current Method));
(A)、如果这个方法是Java方法,那PC计数器就保存JVM正在执行的字节码指令的地址;
(B)、如果该方法是native的,那PC计数器的值是空(undefined);
容量至少应当能保存一个returnAddress类型的数据或者一个与平台相关的本地指针的值;
唯一一个JVM规范中没有规定会抛出OutOfMemoryError情况的区域;
Java虚拟机栈(Java Virtual Machine Stack,JVM Stack),指常说的栈内存(Stack);
和Java堆指的堆内存(Heap),都是需要重点关注的内存区域;
描述的是Java方法执行的内存模型,与传统语言中(如C/C++)的栈类似;
每个方法从调用到执行结束,对应其栈帧在JVM栈上的入栈到出栈的过程;
每个方法执行时都会创建一个栈帧,随着方法调用而创建(入栈),随着方法结束而销毁(出栈);
栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息;
局部变量表(Local Variables Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
这些都是在编译期可知的数据,所以一个方法调用时,在JVM栈中分配给该方法的局部变量空间是完全确定的,运行中不改变;
一个方法分配局部变量表的最大容量由Class文件中该方法的Code属性的max_locals数据项确定;
操作数栈(Operand Stack)简称操作栈,它是一个后进先出(Last-In-First-Out,LIFO)栈;
在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容(任意类型的值),也就是入栈/出栈操作;
在方法调用的时候,操作数栈也用来准备调用方法的参数以及接收方法返回结果;
一个方法的操作数栈长度由Class文件中该方法的Code属性的max_stacks数据项确定;
每一个栈帧内部都包含一个指向运行时常量池的引用,来支持当前方法的执行过程中实现动态链接 (Dynamic Linking);
在 Class 文件里面,描述一个方法调用了其他方法,或者访问其成员变量是通过符号引用(Symbolic Reference)来表示的;
动态链接的作用就是将这些符号引用所表示的方法转换为实际方法的直接引用(除了在类加载阶段解析的一部分符号);
因为除了栈帧的出栈和入栈之外,JVM栈从来不被直接操作,所以栈帧可以在堆中分配;
JVM规范允许JVM栈被实现成固定大小的或者是根据计算动态扩展和收缩的:
如果JVM栈是固定大小的,则当创建新线程的栈时,可以独立地选择每个JVM栈的大小;
在动态扩展或收缩JVM栈的情况下,JVM实现应该提供调节JVM栈最大和最小内存空间的手段;
两种情况下,JVM实现都应当提供调节JVM栈初始内存空间大小的手段;
HotSpot VM通过"-Xss"参数设置JVM栈内存空间大小;
如果线程请求分配的栈深度超过JVM栈允许的最大深度时,JVM将会抛出一个*Error异常;
如果JVM栈可以动态扩展,当然扩展的动作目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈,那JVM将会抛出一个OutOfMemoryError异常;
该区域与方法执行的JVM字节码指令密切相关,这里篇幅有限,以后有时间会分析方法的调用与执行过程,再来详细介绍该区域。
本地方法栈(Native Method Stack)与 Java虚拟机栈类似;
Java虚拟机栈为JVM执行Java方法(也就是字节码)服务;
本地方法栈则为Native方法(指使用Java以外的其他语言编写的方法)服务;
JVM规范中没有规定本地方法栈中方法使用的语言、方式和数据结构,JVM可以*实现;
HotSpot VM直接把本地方法栈和Java虚拟机栈合并为一个;
Java堆(Java Heap)指常说的堆内存(Heap);
里面存储的这些对象实例都是通过垃圾收集器(Garbage Collector)进行自动管理,所以Java堆也称"GC堆"(Garbage Collected Heap);
(随JIT编译技术和逃逸分析技术发展,少量对象实例可能在栈上分配,详见后面介绍JIT编译的文章);
由于很多JVM采用分代收集算法,所以Java堆还可以细分为:新生代、老年代和永久代;
Java堆可能划分出每个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),减少线程同步;
HotSpot VM通过"-XX:+/-UseTLAB"指定是否使用TLAB;
和JVM栈一样,Java堆所使用的物理内存不需要保证是连续的,逻辑连续即可;
JVM规范允许Java堆被实现成固定大小的或者是根据计算动态扩展和收缩的:
两种情况下,JVM实现都应当提供调节JJava堆初始内存空间大小的手段;
在动态扩展或收缩的情况下,还应该提供调节最大和最小内存空间的手段;
目前主流的JVM都把Java堆实现成动态扩展的,如HotSpot VM:
通过"-Xms"或"-XX:InitialHeapSize"参数指定Java堆初始空间大小;
通过"-Xmx"或"-XX:MaxHeapSize"参数指定ava堆内存分配池的最大空间大小;
Parallel垃圾收集器默认的最大堆大小是当小于等于192MB物理内存时,为物理内存的一半,否则为物理内存的四分之一;
通过"-XX:MinHeapFreeRatio"和"-XX:MaxHeapFreeRatio"参数设置堆中各年代内存的占用空间与可用空间的比例保持在特定范围内;
"-XX:MinHeapFreeRatio=40":即一个年代(新生代或老年代)内存空余小于40%时,JVM会从未分配的堆内存中分配给该年代,以保持该年代40%的空余内存,直到分配完"-Xmx"指定的堆内存最大限制;
"-XX:MaxHeapFreeRatio=70":即一个年代(新生代或老年代)内存空余大于70%时,JVM会缩减该年代内存,以保持该年代70%的空余内存,直到缩减到"-Xms"指定的堆内存最小限制;
这两个参数不适用于Parallel垃圾收集器(通过“-XX:YoungGenerationSizeIncrement”、“-XX:TenuredGenerationSizeIncrement ”能及“-XX:AdaptiveSizeDecrementScaleFactor”调节);
通过"-XX:NewRatio":控制年轻代与老年代的大小比例;
默认设置"-XX:NewRatio=2"表新生代和老年代之间的比例为1:2;
换句话说,eden和survivor空间组合的年轻代大小将是总堆大小的三分之一;
通过"-Xmn"参数指定年轻代(nursery)的堆的初始和最大大小;
或通过"-XX:NewSize"和"-XX:MaxNewSize"限制年轻代的最小大小和最大大小;
通过"-XX:MaxPermSize(JDK7)"或"-XX:MaxMetaspaceSize(JDK8)"参数指定永久代的最大内存大小;
通过"-XX:PermSize(JDK7)"或"-XX:MetaspaceSize(JDK8)"参数指定永久代的内存阈值--超过将触发垃圾回收;
注:JDK8中永久代已被删除,类元数据存储空间在本地内存中分配;
详情请参考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html#sthref62
关于这些参数的调整需要垃圾收集的一些知识(以后文章会介绍),先来简单了解:
当使用某种并行垃圾收集器时,应该指定期望的具体行为而不是指定堆的大小;
除非你的应用程序无法接受长时间的暂停,否则你可以将堆调的尽可能大一些;
除非你发现问题的原因在于老年代的垃圾收集或应用程序暂停次数过多,否则你应该将堆的较大部分分给年轻代;
关于HotSpot虚拟机堆内存分代说明以及空间大小说明请参考:
http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/generations.html#sthref16
http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/sizing.html#sizing_generations
如果实际所需的堆超过了垃圾收集器能提供的最大容量,那Java虚拟机将会抛出一个OutOfMemoryError异常;
该部分的内存如何分配、垃圾如何收集,上面这些参数如何调整,将在以后的文章详细说明。
方法区(Method Area)是堆的逻辑组成部分,但有一个别名"Non-Heap"(非堆)用以区分;
为类加载器加载Class文件并解析后的类结构信息提供存储空间;
(A)、运行时常量池(Runtime Constant Pool)、字段和方法数据;
(B)、构造函数、普通方法的字节码内容以及JIT编译后的代码;
虽然方法区是堆的逻辑组成部分,但不限定实现方法区的内存位置;
使用永久代(Permanent Generation)实现方法区,这样就可以不用专门实现方法区的内存管理,但这容易引起内存溢出问题;
有规划放弃永久代而改用Native Memory来实现方法区;
不再在Java堆的永久代中生成中分配字符串常量池,而是在Java堆其他的主要部分(年轻代和老年代)中分配;
更多请参考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/enhancements-7.html
永久代已被删除,类元数据(Class Metadata)存储空间在本地内存中分配,并用显式管理元数据的空间:
类加载器从它的块中分配元数据的空间(一个块被绑定到一个特定的类加载器);
元数据使用由mmap分配的空间,而不是由malloc分配的空间;
通过"-XX:MaxMetaspaceSize" (JDK8)参数指定类元数据区的最大内存大小;
通过"-XX:MetaspaceSize" (JDK8)参数指定类元数据区的内存阈值--超过将触发垃圾回收;
详情请参考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html#sthref62
如果方法区的内存空间不能满足内存分配请求,那Java虚拟机将抛出一个OutOfMemoryError异常;
运行常量池(Runtime Constant Pool)是方法区的一部分;
是每一个类或接口的常量池(Constant_Pool)的运行时表示形式;
(A)、从编译期可知的字面量和符号引用,也即Class文件结构中的常量池;
(C)、还包括运行时可能创建的新常量(如JDK1.6中的String类intern()方法)
直接内存(Direct Memory)不是JVM运行时数据区,也不是JVM规范中定义的内存区域;
被频繁使用,且容易出现OutOfMemoryError异常;
因为避免了在Java堆中来回复制数据,能在一些场景中显著提高性能;
JDK1.4中新加入NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式;
它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java椎中的DirectByteBuffer对象作为这块内存的引用进行操作;
可以通过"-XX:MaxDirectMemorySize"参数指定直接内存最大空间;
不会受到Java堆大小的限制,即"-Xmx"参数限制的空间不包括直接内存;
这容易导致各个内存区域总和大于物理内存限制,出现OutOfMemoryError异常;
到这里,我们大体了解Java各内存区域是什么,有些什么特点了,但方法执行的JVM字节码指令如何在Java虚拟栈中运作的,以及Java堆内存如何分配、垃圾如何收集,如何进行JVM调优,将在以后的文章详细说明。
后面我们将分别去了解:方法的调用与执行、JIT编译--在运行时把Class文件字节码编译成本地机器码的过程、以及JVM垃圾收集相关内容……
1、《The Java Virtual Machine Specification》Java SE 8 Edition:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
2、《Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide》:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html
3、《Memory Management in the Java HotSpot™ Virtual Machine》:http://www.oracle.com/technetwork/java/javase/tech/memorymanagement-whitepaper-1-150020.pdf
4、HotSpot虚拟机参数官方说明:http://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html