JVM内存模型
# 1 JVM内存划分
# 2 分区
JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【JAVA 堆、方法区】、直接内存。
# 2.1 程序计数器
一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。 正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是 Native 方法,则为空。 这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。
# 2.2 虚拟机栈
是描述 java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、 方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
# 2.3 本地方法区
本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为Native 方法服务, 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个C栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。
# 2.4 堆-运行时数据区
是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、From Survivor(S0) 区和 To Survivor (S1)区)和老年代。
# 2.5 方法区
即我们常说的永久代(Permanent Generation), 用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. HotSpot VM把 GC分代收集扩展至方法区, 即使用Java堆的永久代来实现方法区, 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型的卸载, 因此收益一般很小)。 运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版 本、字段、方法、接口等描述等信息外,还有一项信息是常量池 。
注意
1.7之后已经移除了永久代,而是元空间
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
- -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
- -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
- -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
- -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
# 3 JVM基础
# 3.1 对象的创建
Java堆是被所有线程共享的一块内存区域,主要用于存放对象实例,为对象分配内存就是把一块大小确定的内存从堆内存中划分出来,通常有指针碰撞和空闲列表两种实现方式。
1.指针碰撞(Bump The Pointer)
内存规整的情况下,分配空间的工作只是将指针像空闲内存一侧移动对象大小的距离即可。
2.空闲列表(Free List):内存不规整的情况下;
这种适用于内存非规整的情况,这种情况下JVM会维护一个内存列表,记录哪些内存区域是空闲的,大小是多少。给对象分配空间的时候去空闲列表里查询到合适的区域然后进行分配即可。
# 3.2 对象内存布局
对象头(Mark Word):存储对象自身的运行时数据,如哈希码,GC分带年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳。 数据长度在32位和64位虚拟机中分别为32bit和64bit。另外一部分是类型指针,即对象指向它的类元数据的指针。
实例数据:真正的有效信息,定义的各种类型字段的内容,父类继承的也会记录。
对齐填充:不是必然存在的,HotSpot要求对象起始地址(对象大小)必须是8的倍数,对象头部分刚好是1倍或2倍,因此当实例数据部分没对齐时,就需要自动填充来补齐。
# 3.3 对象是否存活
引用计数法:给对象添加一个引用计数器,有一个引用加1,引用失效时减1,计数器为0则对象不再被使用。缺点无法解决循环引用的问题。
可达性分析法:通过一系列的“GC Roots”的对象作为起始点,往下搜索,当一个对象到达GC Roots没有任何引用链相连,则对象不可用,可回收。
GC Roots:
- 虚拟机栈(栈针中的本地变量表)中引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
# 3.4 对象的生或死
在可达性分析算法中的不可达对象,也不是非死不可,宣告一个对象的死亡,至少要经过两次标记过程,没有引用链的对象, 会进行第一次标记并且筛选,筛选条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者虚拟机已经执行过, 则视为没必要执行。finalize()只会被系统调用一次。对象有可能在finalize方法中被拯救。