内存管理(一)
上面的图片是Java虚拟机运行时数据区。
1.程序计数器:
是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器,改变这个计数器的值来选取下一条需要执行的字节码指令。
关键字解读:
1)“当前线程”代表着每个线程都有自己的程序计数器,互不影响,独立存储。所以这类内存区域为线程私有的内存。
2)字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器完成。
线程在执行Java方法,计数器记录正在执行的虚拟机字节码指令的地址
线程在执行Native方法,计数器值为空
此内存区域是唯一一个在Java虚拟机规范中没有规定OutOfMemoryError的区域
2.虚拟机栈:
1)也是线程私有,生命周期与线程相同。
2)虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息;每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
3)若单个线程请求的栈深度大于虚拟机允许的深度,则会抛出StackOverflowError(栈溢出错误)。JVM会为每个线程的虚拟机栈分配一定的内存大小(-Xss参数),因此虚拟机栈能够容纳的栈帧数量是有限的,若栈帧不断进栈而不出栈,最终会导致当前线程虚拟机栈的内存空间耗尽,抛出异常。
4)不同于StackOverflowError,OutOfMemoryError指的是当整个虚拟机栈内存耗尽,并且无法再申请到新的内存时抛出的异常。
JVM未提供设置整个虚拟机栈占用内存的配置参数。虚拟机栈的最大内存大致上等于“JVM进程能占用的最大内存(依赖于具体操作系统) - 最大堆内存 - 最大方法区内存 - 程序计数器内存(可以忽略不计) - JVM进程本身消耗内存”。当虚拟机栈能够使用的最大内存被耗尽后,便会抛出OutOfMemoryError,
3.本地方法栈:
与虚拟机栈发挥的作用非常相似。为虚拟机使用的Native方法服务。本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点以及都能抛出StackOverflowError和OutOfMemoryError异常。不同的是,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法。
有的虚拟机把虚拟机栈和本地方法栈合二为一。也有虚拟机栈的异常。
4.堆:
对大多数应用程序来说,堆是Java虚拟机管理的内存中最大的一块。
Java堆是被所有线程共享的一块数据区域,在虚拟机启动时创建,这一内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也逐渐变得不是那么“绝对”。
堆中可细分为新生代和老年代,在细分可以分为Eden空间、Form Survivor空间、to Survivor空间。
Java堆是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”。
Java的普通对象存活在堆中,与栈不同,堆的空间不会随着方法调用结束而清空。因此,在某个方法中创建的对象,可以在方法调用结束之后,继续存在堆中。这带来的一个问题是,如果我们不断的创建新的对象,内存控件将会最终消耗殆尽。
OutOfMemoryError:堆中没有内存完成实力分配,堆也无法扩展
5.方法区:
与Java堆一样,是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译之后的代码等数据。
注:JDK8之前,方法区由永久代实现,主要存放类的信息、常量池、方法数据、方法代码等;JDK8之后,取消了永久代,提出了元空间,并且常量池、静态成员变量等迁移到了堆中;元空间不在虚拟机内存中,而是放在本地内存中
OutOfMemoryError:当方法区无法满足内存分配需求时,抛出异常
6. 运行时常量池:
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行常量池中存放。
运行时常量池是具备动态性,Java语言并不要求常量一定只有编译器才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多是String类的intern()方法。
OutOfMemoryError:受方法区内存的限制,常量池无法再申请到内存时
7.直接内存:
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。
在JDK 1.4中新加入了NIO类,引入了一种基于通道(Channel)与缓存区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
作用:NIO中可以使用Native函数库直接分配堆外内存,然后通过一个存储在堆中的DirectByteBuffer对象作为内存的引用进行操作。
OutOfMemoryError:设置虚拟机参数时可能忽略直接内存,使得各个内存区域总和大于物理内存限制,动态扩展时出现这个异常