3 自动内存管理机制(二)JVM内存分配和回收

自动内存管理机制(二)JVM内存分配和回收

本文要求读者对Java虚拟机(JVM)内部结构比较熟悉,了解虚拟机运行时有哪些数据区域,垃圾回收算法以及垃圾回收器。在此基础上本文对JVM内存分配和回收的相关知识进行梳理和总结。

程序计数器、虚拟机栈、本地方法栈的内存分配与回收

程序计数器、虚拟机栈、本地方法栈三个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。
每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此这几个区域的内存分配和回收都具备确定性。
这三个区域不需要过多的考虑回收的问题,因为方法结束或线程结束时,内存自然就随着回收了。


Java堆和方法区的内存分配与回收

Java堆中存储的是实例数据(即对象),主要是成员变量及其值。
方法区中存储的是类型数据,主要是类信息、常量、静态变量、即时编译器编译后的代码等。
一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,JVM只有在程序运行期间才能知道会创建哪些对象,因此Java堆和方法区的内存分配和回收都是动态的,是需要重点讨论的地方。

类型数据和实例对象的内存分配

什么时候为类型数据分配内存?分配到哪儿?

  • 类加载过程包含加载、验证、准备、解析和初始化五个步骤。其中加载、验证、准备和初始化顺序是固定的,解析既可能在初始化之前也可能在初始化之后。
  • “加载”会通过类全限定名将类的class文件(二进制字节流)代表的静态存储结构转化为方法区的运行时数据结构,并在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。可见 “加载”会为类型数据分配内存到方法区
  • JVM规范没有强制约束什么时候进行“加载”,但是严格规定了什么时候会“初始化”,而加载、验证和准备自然在此之前开始。
    • 虚拟机启动时会初始化包含main()方法的主类。
    • 初始化一个类时,若其父类未初始化则先初始化父类。
    • 使用java.lang.reflect包的方法进行反射调用时,若类未初始化则先初始化。
    • new关键字实例化对象、读取或设置类静态字段及调用类静态方法时。
  • 以上四种触发类初始化的情形又称为对类的主动引用。其他对类的引用都不会触发类的初始化。如:
    • 子类引用父类的静态字段,不会导致子类的初始化。
    • 定义某个类的数组时,不会触发该类的初始化。
    • 访问某个类的常量时,不会触发该类的初始化。

什么时候为实例对象分配内存?分配到哪儿?

  • 新生的对象会随之为之分配内存:

    • 类的“加载”过程会生成一个代表这个类的 java.lang.Class 对象;
    • new关键字实例化对象
  • 新生的对象分配到哪儿呢?

    • 新生对象 ——> 新生代Eden区
      • 新生的对象一般在新生代Eden区分配内存。
    • 新生对象 ——> 老年代
      • 若新生的对象太大在新生代Eden区分配失败,且该对象是一个不含任何引用的数组,则直接在老年代分配,从而避免一次 Minor GC。
      • 若新生的对象比较大,超过了 PretenureSizeThreshold 参数的设置值,则直接在老年代分配内存,从而避免了 Minor GC 时对大对象的复制。(PretenureSizeThreshold 默认值为0,意味着无论对象多大都只在新生代Eden区分配内存)
    • 新生对象 ——> 本地线程分配缓存
      • 若启动了本地线程分配缓存(Thread Local Allocation Buffer, TLAB),则优先在TLAB上为对象分配内存。
    • 新生对象 ——> 虚拟机栈
      • 若虚拟机检测到一个对象在运行期间的作用范围不会超过一个方法或一个线程的作用域(即进行逃逸分析),会把这个对象拆解成其内包含的若干成员变量(即进行标量替换),从而在虚拟机栈上分配内存,节省了Java堆的空间。以上过程是即时编译技术(Just In Time, JIT)的一种优化情形。
  • 补充:新生代中的对象——>老年代

    • 若新生代中对象的年龄(经历 Minor GC 的次数)达到 MaxTenureThreshold 参数设置值,该对象会被移到老年代。
    • Minor GC时,若From Survivor中相同年龄对象达到或超过该区域容量的一半,该区域中大于等于该年龄的对象即使年龄没有达到 MaxTenureThreshold 参数设置值,也会直接移到老年代。
    • Minor GC失败时,会触发空间分配担保(见附录),若担保成功,也会有新生代中的对象移到老年代。
  • 注意

    • 新生代包括Eden区、From Survivor区和To Survivor区。
    • 新生代Eden区、老年代和TLAB都在Java堆中。
类型数据和实例对象的内存回收

方法区中的废弃常量和无用的类是通过Full GC进行回收的。
Java堆中的无用对象是通过Minor GC、Major GC和Full GC进行回收的。

方法区中废弃常量的判定

  • 方法区中的常量在运行时常量池中,这些常量包括:字面量,类、接口、方法和字段的符号引用。
  • 若没有任何一个对象引用这些常量时,称为废弃常量。
  • 在下次Full GC时会顺便回收掉方法区中的废弃常量。

方法区中无用的类的判定

  • 无用类的判定需要同时满足以下3个条件:
    • 该类的所有实例都已经被回收。
    • 加载该类的ClassLoader已经被回收。
    • 该类的java.lang.Class对象没有在任何地方被反射调用。
  • 在下次Full GC时会顺便回收掉方法区中无用的类。

Java堆中无用对象的判断

  • 无用对象的判断常用的方法又两个,引用计数法和根搜索法。

  • 引用计数法在对象的引用为0时判定该对象无用。缺陷是难以解决对象之间相互循环引用的问题。Python语言便采用该方法。

  • 根搜索法通过一系列称为“GC Roots”的对象作为起始点向下搜索产生引用链,当GC Roots到某个对象没有任何引用链,则该对象无用。主流商用语言,如Java和C#都是采用该方法。Java语言里可作为GC Roots对象包括:

    • 虚拟机栈(栈帧本地变量表)中引用的对象。
    • 本地方法栈中JNI(Java Native Interface,Java本地接口,用来调用其他语言的方法,即本地方法)中引用的对象。
    • 方法区中类静态属性引用的对象。
    • 方法区中的常量引用的对象。

Minor GC

  • 介绍:回收Java堆新生代区域无用对象,新生代回收器采用复制算法。
  • 触发:当Eden区没有足够的空间分配新产生的对象时,虚拟机将发起一次 Minor GC。
  • 过程:Minor GC 触发后会将新生代Eden区和From Survivor区中存活的对象,复制到To Survivor区。
  • Minor GC失败:To Survivor区剩余容量小于新生代Eden区和From Survivor区中存活的对象总容量时,Minor GC失败,会触发空间分配担保(见附录)。

Major GC

  • 介绍:回收Java堆老年代区域无用对象,老年代回收器大多采用标记-整理算法,而CMS回收器采用标记-清除算法。
  • 触发:大多数老年代回收器在老年代区域被填满时会触发 Major GC,CMS回收器是达到 CMSInitiatingOccupancyFraction 参数设置的占用率时触发 Major GC。
  • 过程:老年代回收器大多采用标记-整理算法(CMS回收器采用标记-清除算法)清理老年代区域无用对象。

Full GC

  • 介绍:全面回收Java堆新生代、老年代和方法区(永久代)的无用数据。
  • 触发
    • 在程序中直接调用System.gc。
    • 方法区(非堆)空间用完时。
    • CMS收集器无法处理浮动垃圾而导致“Concurrent Mode Failure”时。
    • 空间分配担保(见附录)过程中可能出现Full GC。
  • 过程:新生代回收器和老年代回收器分别对新生代区域和老年代区域清理无用对象,JVM清除方法区中的废弃常量并卸载无用的类。

附录

空间担保分配过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rg7YKpwc-1593533253443)(index_files/_u7A7A_u95F4_u5206_u914D_u62C5_.png)]


参考

[1] 《深入理解Java虚拟机》
[2] -XX:PretenureSizeThreshold的默认值和作用浅析
[3] JVM 触发Full gc条件
[4] JIT—即时编译

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值