- 是唯一一个不会出现OutOfMemoryError的内存区域。
4.2 Java虚拟机栈
Java虚拟机栈也是线程私有的,它的生命周期与线程的生命周期同步,虚拟机栈描述的是Java方法执行的线程内存模型。每个方法被执行的时候,Java虚拟机都会同步创建一个内存块,用于存储在该方法运行过程中的信息,每个方法被调用的过程都对应着一个栈帧在虚拟机中从入栈到出栈的过程。
Java虚拟机栈有如下的特点:
-
局部变量表所需的内存空间在编译期间完成分配,进入一个方法时,这个方法需要在栈帧中分配的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
-
Java虚拟机栈会出现两种异常:StackOverflowError 和 OutOfMemoryError。
4.3 本地方法栈
本地方法栈与虚拟机所发挥的作用很相似,区别在于虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的本地方法服务。
4.4 Java堆
Java堆是虚拟机所管理的内存中最大的一块,Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。 此内存区域的唯一目的就是存放对象实例,java中“几乎”所有的对象实例都在这里分配内存。这里使用“几乎”是因为java语言的发展,及时编译的技术发展,逃逸分析技术的日渐强大,栈上分配、标量替换等优化手段,使java对象实例都分配在堆上变得不那么绝对。 Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法(G1之后开始变得不一样,引入了region,但是依旧采用了分代思想),Java堆中还可以细分为:新生代和老年代。再细致一点的有Eden空间、From Survivor空间、ToSurvivor空间等。从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,简写TLAB)。
OOM异常 Java堆的大小既可以固定也可以扩展,但是主流的虚拟机,堆的大小都是支持扩展的。如果需要线程请求分配内存,但堆已满且内存已无法再扩展时,就抛出 OutOfMemoryError 异常。比如:
/**
- VM Args:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapOOMTest {
public static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
List<Integer[]> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Integer[] ints = new Integer[2 * _1MB];
list.add(ints);
}
}
}
4.5 方法区
方法区和Java堆一样,是各个线程共享的内存区域,他用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
在 HotSpot JVM 中,永久代(永久代实现方法区)中用于存放类和方法的元数据以及常量池,比如Class和Method。每当一个类初次被加载的时候,它的元数据都会放到永久代中。永久代是有大小限制的,因此如果加载的类太多,很有可能导致永久代内存溢出,为此我们不得不对虚拟机做调优。
后来HotSpot放弃永久代(PermGen),jdk1.7版本中,HotSpot已经把原本放在永久代的字符串常量池、静态变量等移出,到了jdk1.8,完全废弃了永久代,方法区移至元空间(Metaspace)。比如类元信息、字段、静态属性、方法、常量等都移动到元空间区。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。
常用的JVM调参如下表:
| 参数 | 作用描述 |
| — | — |
| -XX:MetaspaceSize | 分配给Metaspace(以字节计)的初始大小。如果不设置的话,默认是20.79M,这个初始大小是触发首次 Metaspace Full GC 的阈值,例如 -XX:MetaspaceSize=256M |
| -XX:MaxMetaspaceSize | 分配给Metaspace 的最大值,超过此值就会触发Full GC,此值默认没有限制,但应取决于系统内存的大小。JVM会动态地改变此值。但是线上环境建议设置,例如-XX:MaxMetaspaceSize=256M |
| -XX:MinMetaspaceFreeRatio | 最小空闲比,当 Metaspace 发生 GC 后,会计算 Metaspace 的空闲比,如果空闲比(空闲空间/当前 Metaspace 大小)小于此值,就会触发 Metaspace 扩容。默认值是 40 ,也就是 40%,例如 -XX:MinMetaspaceFreeRatio=40 |
| -XX:MaxMetaspaceFreeRatio | 最大空闲比,当 Metaspace 发生 GC 后,会计算 Metaspace 的空闲比,如果空闲比(空闲空间/当前 Metaspace 大小)大于此值,就会触发 Metaspace 释放空间。默认值是 70 ,也就是 70%,例如 -XX:MaxMetaspaceFreeRatio=70 |
运行时常量池 运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译期间生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 方法区中存放:类信息、常量、静态变量、即时编译器编译后的代码。常量就存放在运行时常量池中。 当类被 Java 虚拟机加载后, .class文件中的常量就存放在方法区的运行时常量池中。而且在运行期间,可以向常量池中添加新的常量。如String类的intern()方法就能在运行期间向常量池中添加字符串常量。
4.6 直接内存
直接内存并不是虚拟机运行时数据区的组成部分,在 NIO 中引入了一种基于通道和缓冲的 IO 方式。它可以通过调用本地方法直接分配Java虚拟机之外的内存,然后通过一个存储在堆中的DirectByteBuffer对象直接操作该内存,而无须先将外部内存中的数据复制到堆中再进行操作,从而提高了数据操作的效率。
由于直接内存并非Java虚拟机的组成部分,因此直接内存的大小不受 Java 虚拟机控制,但既然是内存,如果内存不足时还是会抛出OutOfMemoryError异常。
下面是直接内存与堆内存的一些异同点:
-
直接内存申请空间耗费更高的性能;
-
直接内存读取 IO 的性能要优于普通的堆内存。
-
直接内存作用链: 本地 IO -> 直接内存 -> 本地 IO
-
堆内存作用链:本地 IO -> 直接内存 -> 非直接内存 -> 直接内存 -> 本地 IO
服务器管理员在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制,从而导致动态扩展时出现OutOfMemoryError异常。
5,简单介绍下Java的类加载器
Java的类加载器可以分为BootstrapClassLoader、ExtClassLoader和AppClassLoader,它们的作用如下。
-
BootstrapClassLoader:Bootstrap 类加载器负责加载 rt.jar 中的 JDK 类文