七、堆
Java堆是Java虚拟机所管理的内存中最大的一块,其唯一的目的是存放对象实例。java堆是被所有线程所共享的一块内存区域(TLAB区除外),在虚拟机启动时创建,几乎所有对象的实例都存储在堆中,所有的对象和数组都要在堆上分配内存。
-
堆和方法区针对一个 JVM 进程来说是唯一的,也就是一个进程只有一个 JVM ,但是进程包含多个线程,他们是共享同一堆和方法区空间的,每个线程各自包含一套程序计数器、本地方法栈和虚拟机栈。
-
一个 JVM 实例只存在一个堆内存,堆也是 Java 内存管理的核心区域,是 JVM 管理的最大一块内存空间。Java 堆在 JVM 启动的时候即被创建,其空间大小也就确定了。Java堆可以被实现成固定大小的,也可以是可扩展的。
-
《Java虚拟机规范》中对 Java 堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。更合理的应该是“几乎”所有的对象实例都在这里分配内存,因为还有一些对象是在栈上分配的【逃逸】。
-
java堆是垃圾收集器(GC)管理的主要区域。在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集(GC)的时候才会被移除。
7.1 堆结构划分
《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。但是从不同的角度,可以对堆进行不同的划分。无论从什么角度,无论如何划分,都不会改变 Java 堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将Java堆细分的目的只是为了更好地回收内存,或者更快地分配内存。
7.1.1 回收内存的角度
根据不同类型的垃圾收集器,堆可以被划分为不同的分区。
- 基于分代收集理论划分:新生代、老年代分区
- 基于非分代收集理论划分:Region分区【G1垃圾收集器】
(1)基于分代收集理论划分
存储在 JVM 中的 Java 对象可以被划分为两类:
- 生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速,生命周期短的,及时回收即可
- 生命周期非常长,在某些极端的情况下还能够与 JVM 的生命周期保持一致的对象
由于现在垃圾收集器大部分都是基于分代收集理论设计的,所以就会将堆内存逻辑上分为三部分:
- 新生代:在新生代中,每次垃圾收集时都会发现有大量对象死去,而每次回收后存活的少量对象,将会逐步晋升到老年区中存放;
- 老年代
- 永久区/元空间(JDK8及以后)
元空间与永久代之间最大的区别在于:
==永久代使用的JVM的堆内存,但是java8以后的元空间并不在虚拟机中而是使用本机物理内存。==因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由MaxPermSize 控制, 而由系统的实际可用空间来控制。
IBM研究表明,新生代中的对象有98%熬不过第一轮收集【朝生夕灭】,因此不需要按照1:1的比例来划分新生代的内存空间。新生代与老年代的默认内存分配(1:2)如下:
from/to区:这两个分区的位置是相对的,是不固定的。新生代中默认指定幸存者区中的空区域为to区。
为什么要把 Java 堆分代?不分代就不能正常工作了吗?经研究,不同对象的生命周期不同。70%-99%的对象是临时对象。
- 新生代:有 Eden 、两块大小相同的 Survivor(又称为 From/To,S0/S1)构成,To 总为空。
- 老年代:存放新生代中经历多次 GC 仍然存活的对象。
其实不分代完全可以,分代的唯一理由就是优化