[JVM] Java内存分配

Java内存分配

程序计数器

程序计数器是一块较小的内存区域,作用可以看做是当前线程执行的字节码的位置指示器。分支、循环、跳转、异常处理和线程恢复等基础功能都需要依赖这个计算器来完成。

虚拟机栈

虚拟机栈也叫栈内存,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就 Over,所以不存在垃圾回收。

每个方法执行的同时创建栈帧(Strack Frame)用于存储局部变量表(包含了对应的方法参数和局部变量),操作栈(Operand Stack,记录出栈、入栈的操作),动态链接、方法出口等信息,每个方法被调用直到执行完毕的过程,对应这帧栈在虚拟机栈的入栈和出栈的过程。

局部变量表

局部变量表存放了编译期可知的各种基本数据类型(booleanbytecharshortintfloatlongdouble)、对象的引用(reference类型),不等同于对象本身,根据不同的虚拟机实现,可能是一个指向对象起始地址的引用指针,也可能是一个代表对象的句柄或者其他与对象相关的位置)和 returnAdress类型(指向下一条字节码指令的地址)。

局部变量表所需的内存空间在编译期间完成分配,在方法在运行之前,该局部变量表所需要的内存空间是固定的,运行期间也不会改变

栈帧

栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法 A 被调用时就产生了一个栈帧 F1,并被压入到栈中,A 方法又调用了 B 方法,于是产生栈帧 F2 也被压入栈,执行完毕后,先弹出 F2栈帧,再弹出 F1 栈帧,遵循“先进后出”原则。

对象的内存分配,就是在堆上分配。新的对象主要分配在新生代的Eden区上,少数情况下可能分配在老年代,分配规则取决于当前使用的垃圾收集器组合以及相关的参数配置。

对象优先在Eden分配

大多数情况下, 对象在新生代中Eden区中分配。当Eden区没有足够空间进行分配的时候,JVM将会进行一次Minor GC

  • Minor GC:回收新生代。(Eden区和Survivor区)。因为java对象大多数都具备朝生熄灭的特性,所以 Minor GC非常频繁,回收速度快。
  • Major GC/Full GC:回收老年代。出现了Major GC,经常会伴随着少量的Minor GCMajor GC的速度一般会比 Minor GC慢个10倍以上。

大对象进入老年代

大对象是指需要大量连续内存空间的 Java 对象,如很长的字符串或数据。

一个大对象能够存入 Eden 区的概率比较小,发生分配担保的概率比较大,而分配担保需要涉及大量的复制,就会造成效率低下。

虚拟机提供了一个 -XX:PretenureSizeThreshold 参数,令大于这个设置值的对象直接在老年代分配,这样做的目的是避免在 Eden 区及两个 Survivor 区之间发生大量的内存复制。(还记得吗,新生代采用复制算法回收垃圾)

长期存活的对象进入老年代

JVM 给每个对象定义了一个对象年龄计数器。当新生代发生一次 Minor GC 后,存活下来的对象年龄 +1,当年龄超过一定值时,就将超过该值的所有对象转移到老年代中去。

使用 -XXMaxTenuringThreshold 设置新生代的最大年龄,只要超过该参数的新生代对象都会被转移到老年代中去。

动态对象年龄判定

如果当前新生代的 Survivor 中,相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄 >= 该年龄的对象就可以直接进入老年代,无须等到 MaxTenuringThreshold 中要求的年龄。

空间分配担保

JDK 6 Update 24 之前的规则是这样的:

在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间, 如果这个条件成立,Minor GC 可以确保是安全的; 如果不成立,则虚拟机会查看 HandlePromotionFailure 值是否设置为允许担保失败, 如果是,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小, 如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的; 如果小于,或者 HandlePromotionFailure 设置不允许冒险,那此时也要改为进行一次 Full GC

JDK 6 Update 24 之后的规则变为:

只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行 Minor GC,否则将进行 Full GC

通过清除老年代中的废弃数据来扩大老年代空闲空间,以便给新生代作担保。

这个过程就是分配担保。

什么情况下会触发Full GC

  • System.gc() 方法的调用

    此方法的调用是建议 JVM 进行 Full GC,注意这只是建议而非一定,但在很多情况下它会触发 Full GC,从而增加 Full GC 的频率。通常情况下我们只需要让虚拟机自己去管理内存即可,我们可以通过 -XX:+ DisableExplicitGC 来禁止调用 System.gc()

  • 老年代空间不足。

    老年代空间不足会触发 Full GC 操作,若进行该操作后空间依然不足,则会抛出如下错误:java.lang.OutOfMemoryError: Java heap space

  • 永久代空间不足(方法区内存不足)。

    JVM 规范中运行时数据区域中的方法区,在 HotSpot 虚拟机中也称为永久代(Permanet Generation),存放一些类信息、常量、静态变量等数据,当系统要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,会触发 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出如下错误信息:java.lang.OutOfMemoryError: PermGen space

  • CMS GC中出现。

    promotion failedconcurrent mode failure promotion failed,就是上文所说的担保失败,而 concurrent mode failure 是在执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足造成的。

  • 统计得到的 Minor GC 晋升到老年代的平均大小大于老年代的剩余空间。

本地方法栈

本地方法栈则为JVM提供使用native 方法的服务。

方法区

Object Class Data(加载类的类定义数据) 是存储在方法区的。

除此之外,常量静态变量JIT(即时编译器)编译后的代码也都在方法区。

正因为方法区所存储的数据与堆有一种类比关系,所以它还被称为 Non-Heap(非堆)。

方法区也可以是内存不连续的区域组成的,并且可设置为固定大小,也可以设置为可扩展的,这点与堆一样。

垃圾回收在这个区域会比较少出现,这个区域内存回收的目的主要针对常量池的回收和类的卸载。

运行时常量池

方法区内部有一个非常重要的区域,叫做运行时常量池(Runtime Constant Pool,简称 RCP

在字节码文件(Class文件)中,除了有类的版本、字段、方法、接口等先关信息描述外,还有常量池(Constant Pool Table)信息,用于存储编译器产生的字面量和符号引用。这部分内容在类被加载后,都会存储到方法区中的RCP

值得注意的是,运行时产生的新常量也可以被放入常量池中,比如 String 类中的 intern() 方法产生的常量。

常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)对其他类型、方法、字段的符号引用。

直接内存区

直接内存区并不是 JVM 管理的内存区域的一部分,而是其之外的。该区域也会在 Java 开发中使用到,并且存在导致内存溢出的隐患。如果你对 NIO 有所了解,可能会知道 NIO 是可以使用 Native Methods 来使用直接内存区的。

扩展

方法区、永久代、元数据区的关系

方法区是JVM定义的一种规范,所有虚拟机都需要遵守的约定

永久代和元数据都是实际某个虚拟机针对方法区的一种实现。

永久代是 JDK 1.7 之前 Hotspot虚拟机对方法区的实现,元数据是 JDK 1.8之后Hotspot虚拟机对方法区的一种实现。

元空间和永久代最大的区别在于,元空间并不在虚拟机中,而用的是本地内存。默认情况下元空间的大小仅受本地内存的限制,但是可以通过参数来设置元空间的大小。

PermGen space和MetaSpace的区别

不管是PermGen space 还是 MetaSpace 他们都是Hotspot针对方法区的一种实现,两者最大的区别在于PermGen space 是在JVM虚拟机中分配内存的,而Metaspace则是在虚拟机之外的系统本地分配内存。

因为很多类是在运行期间加载的,这部分类加载的空间不可控,如果这部分内存是在JVM内存里分配的话,永久代分配太大那么JVM其他区域(比如说堆)的内存就会变小,反之如果设置太小,就容易出现方法区内存溢出,因为本身存储的类信息属于不确定大小,类信息在我们运行的时候可以动态加载。

所以jdk1.8中选择把Metaspace内存分配在本地内存,如果这样做的好处是Metaspace空间的大小不会受限于虚拟机分配的内存大小,只会受限于机器内存,可分配的内存大了那么就不会那么容易出现内存溢。

posted @   knqiufan  阅读(266)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示
今天是妇女节