当前位置: 首页 > news >正文

为什么jvm要分为堆、方法区等?原理是什么?_面试必会:JVM堆内存详解

目录

⊙  堆内存区域

⊙GC执行的机制

⊙GC原理-垃圾回收算法

今天重点讲一下JVM的堆内存模型。这些都是大厂面试必备的哦,同学们请注意听讲

68220c0912f0abce8a209fac17a6252a.png  

cf134e2ef8d281ba6f13f001e1f95884.gif 一、堆的内存区域 cf134e2ef8d281ba6f13f001e1f95884.gif 1.1  堆内存区域介绍

a2596f62286e0f22f4494eb1fe8055ba.png

在jvm的堆内存中有三个区域:

1、年轻代:用于存放新产生的对象。

2、老年代:用于存放被长期引用的对象。

3、持久带:用于存放Class,method元信息(1.8之后改为元空间)。

年轻代

年轻代中包含两个区:Eden 和survivor,并且用于存储新产生的对象,其中有两个survivor区

老年代

年轻代在垃圾回收多次都没有被GC回收的时候就会被放到老年代,以及一些大的对象(比如缓存,这里的缓存是弱引用),这些大对象可以不进入年轻代就直接进入老年代

持久代

持久代用来存储class,method元信息,大小配置和项目规模,类和方法的数量有关。

元空间

JDK1.8之后,取消perm永久代,转而用元空间代替

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。并且可以动态扩容。那么使用元空间会有哪些问题呢?同学们可以思考下。

1.2 为什么分代?

因为不同对象的生命周期是不一样的。80%-98%的对象都是“朝生夕死”,生命周期很短,大部分新对象都在年轻代,可以很高效地进行回收,不用遍历所有对象。而老年代对象生命周期一般很长,每次可能只回收一小部分内存,回收效率很低。

年轻代和老年代的内存回收算法完全不同,因为年轻代存活的对象很少,标记清楚再压缩的效率很低,所以采用复制算法将存活对象移到survivor区,更高效。而老年代则相反,存活对象的变动很少,所以采用标记清楚压缩算法更合适。

1.3  内存分配策略

1.3.1、 优先在Eden区分配 

在大多数情况下, 对象在新生代Eden区中分配, 当Eden区没有足够空间分配时, VM发起一次Minor GC, 将Eden区和其中一块Survivor区内尚存活的对象放入另一块Survivor区域, 如果在Minor GC期间发现新生代存活对象无法放入空闲的Survivor区, 则会通过空间分配担保机制使对象提前进入老年代(空间分配担保见下).

1.3.2、大对象直接进入老年代

Serial和ParNew两款收集器提供了-XX:PretenureSizeThreshold的参数, 令大于该值的大对象直接在老年代分配, 这样做的目的是避免在Eden区和Survivor区之间产生大量的内存复制(大对象一般指 需要大量连续内存的Java对象, 如很长的字符串和数组), 因此大对象容易导致还有不少空闲内存就提前触发GC以获取足够的连续空间.

1.3.3、长期存活对象进入老年区

 如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1,对象在Survivor区中每熬过一次 Minor GC,年龄就增加1,当它的年龄增加到一定程度(默认为15)_时,就会被晋升到老年代中。

1.3.4、对象年龄动态判定

如果在 Survivor空间中相同年龄所有对象大小的综合大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代

1.3.5、空间分配担保

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

HotSpot默认是开启空间分配担保的。

cf134e2ef8d281ba6f13f001e1f95884.gif 二、GC执行的机制 cf134e2ef8d281ba6f13f001e1f95884.gif

由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Minor GC和Full GC。

2.1  Minor GC(young GC)

一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

2.2  Full GC

对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:

1.年老代(Tenured)被写满

2.持久代(Perm)被写满

3.System.gc()被显示调用

4.上一次GC之后Heap的各域分配策略动态变化

2.3  对象生死判定方法

那我们了解JVM的GC机制之后,那满足什么条件的对象才会被GC掉呢?

1、引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。

2、可达性分析算法

567c4ab25aad5c8fff9feb26903b3b24.png

在主流商用语言(如Java、C#)的主流实现中, 都是通过可达性分析算法来判定对象是否存活的: 通过一系列的称为 GC Roots 的对象作为起点, 然后向下搜索; 搜索所走过的路径称为引用链/Reference Chain, 当一个对象到 GC Roots 没有任何引用链相连时, 即该对象不可达, 也就说明此对象是不可用的, 如下图: Object5、6、7 虽然互有关联, 但它们到GC Roots是不可达的, 因此也会被判定为可回收的对象:

在Java, 可作为GC Roots的对象包括:

方法区: 类静态属性引用的对象;

方法区: 常量引用的对象;

虚拟机栈(本地变量表)中引用的对象.

本地方法栈JNI(Native方法)中引用的对象。

注: 即使在可达性分析算法中不可达的对象, VM也并不是马上对其回收, 因为要真正宣告一个对象死亡, 至少要经历两次标记过程: 第一次是在可达性分析后发现没有与GC Roots相连接的引用链, 第二次是GC对在F-Queue执行队列中的对象进行的小规模标记(对象需要覆盖finalize()方法且没被调用过). 

cf134e2ef8d281ba6f13f001e1f95884.gif、GC原理-垃圾回收算法 cf134e2ef8d281ba6f13f001e1f95884.gif

Java与C++等语言最大的技术区别:自动化的垃圾回收机制(GC),那么为什么要了解GC和内存分配策略呢?

1、面试需要

2、GC对应用的性能是有影响的;

3、写代码有好处

栈:栈中的生命周期是跟随线程,所以一般不需要关注

堆:堆中的对象是垃圾回收的重点

方法区/元空间:这一块也会发生垃圾回收,不过这块的效率比较低,一般不是我们关注的重点

目前为止,jvm已经发展处四种比较成熟的垃圾收集算法:

1、标记-清除算法;

2、复制算法;

3、标记-整理算法;

4、分代收集算法

3.1  标记-清除算法

75fee78cb5aac3e6492227520704ca9c.png

这种垃圾回收一次回收分为两个阶段:标记、清除。首先标记所有需要回收的对象,在标记完成后回收所有被标记的对象。这种回收算法会产生大量不连续的内存碎片,当要频繁分配一个大对象时,jvm在新生代中找不到足够大的连续的内存块,会导致jvm频繁进行内存回收(目前有机制,对大对象,直接分配到老年代中)

这种垃圾回收一次回收分为两个阶段:标记、清除。首先标记所有需要回收的对象,在标记完成后回收所有被标记的对象。这种回收算法会产生大量不连续的内存碎片,当要频繁分配一个大对象时,jvm在新生代中找不到足够大的连续的内存块,会导致jvm频繁进行内存回收(目前有机制,对大对象,直接分配到老年代中)

优点

  • 利用率百分之百

缺点

  • 标记和清除的效率都不高(比对复制算法)

  • 会产生大量的不连续的内存碎片

3.2  复制算法

33f6700c55c5b07cc23cd80f8bf45d08.png

这种算法会将内存划分为两个相等的块,每次只使用其中一块。当这块内存不够使用时,就将还存活的对象复制到另一块内存中,然后把这块内存一次清理掉。这样做的效率比较高,也避免了内存碎片。但是这样内存的可使用空间减半,是个不小的损失。

优点

简单高效,不会出现内存碎片问题

缺点

内存利用率低,只有一半

存活对象较多时效率明显会降低

9d74d491490485b3bfb2d5e721dd62c5.png

3.3  标记-整理算法

 472638869e0d2fd1dff6b89b8eae19cc.png

这是标记-清除算法的升级版。在完成标记阶段后,不是直接对可回收对象进行清理,而是让存活对象向着一端移动,然后清理掉边界以外的内存

优点

  • 利用率百分之百

  • 没有内存碎片

缺点

  • 标记和清除的效率都不高

  • 效率相对标记-清除要低

3.4  分代收集算法

当前商业虚拟机都采用这种算法。首先根据对象存活周期的不同将内存分为几块即新生代、老年代,然后根据不同年代的特点,采用不同的收集算法

新生代: 每次垃圾收集都能发现大批对象已死, 只有少量存活. 因此选用复制算法, 只需要付出少量存活对象的复制成本就可以完成

老年代: 因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标记—整理”算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存.

相关文章:

  • 为什么英文的windows操作系统安装中文的软件会出现乱码?
  • 其他信息: 尝试读取或写入受保护的内存。这通常指示其他内存已损坏。_KeyDB: 基于多线程模型且Redis API兼容的内存数据库...
  • DLL的Export和Import
  • 双向链表删除节点时间复杂度_leetcode 19 删除链表的倒数第N个节点(c++)
  • Struts、spring、hibernate、EJB、ant术语说明
  • nexus 安装_Nexus在windows和centos中安装教程
  • 傅里叶级数拟合实验
  • k8s 安装nfs_GCP学习笔记6-NFS\MySQL\GKE配置
  • 数据图表与分析图_单一数据图表动态展示,学会圆环图搭配饼图,效果就是与众不同...
  • 关于inline函数
  • python怎么重新运行_崩溃后如何重新运行Linux? - python
  • 蓝眼泪
  • python3元组_python3 列表、元组操作
  • AT91RM9200引导程序的建立(一)--------建立交叉编译工具链
  • python 平均值_Python随笔11:Python代码规范之注释和文档
  • JavaScript 如何正确处理 Unicode 编码问题!
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • 78. Subsets
  • co.js - 让异步代码同步化
  • CSS实用技巧干货
  • golang 发送GET和POST示例
  • Java编程基础24——递归练习
  • nodejs实现webservice问题总结
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • Redux系列x:源码分析
  • SAP云平台里Global Account和Sub Account的关系
  • Sass 快速入门教程
  • Terraform入门 - 1. 安装Terraform
  • vue.js框架原理浅析
  • Vue2.x学习三:事件处理生命周期钩子
  • 程序员该如何有效的找工作?
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 删除表内多余的重复数据
  • 思维导图—你不知道的JavaScript中卷
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • 线性表及其算法(java实现)
  • PostgreSQL之连接数修改
  • ​Linux·i2c驱动架构​
  • (c语言)strcpy函数用法
  • (delphi11最新学习资料) Object Pascal 学习笔记---第7章第3节(封装和窗体)
  • (Forward) Music Player: From UI Proposal to Code
  • (二十三)Flask之高频面试点
  • (附源码)ssm基于web技术的医务志愿者管理系统 毕业设计 100910
  • (附源码)流浪动物保护平台的设计与实现 毕业设计 161154
  • (删)Java线程同步实现一:synchronzied和wait()/notify()
  • (十三)Maven插件解析运行机制
  • (算法)Game
  • (转)我也是一只IT小小鸟
  • .NET CORE 第一节 创建基本的 asp.net core
  • .net core 6 使用注解自动注入实例,无需构造注入 autowrite4net
  • .net core webapi Startup 注入ConfigurePrimaryHttpMessageHandler
  • .NET CORE使用Redis分布式锁续命(续期)问题
  • .NET 中各种混淆(Obfuscation)的含义、原理、实际效果和不同级别的差异(使用 SmartAssembly)
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)...
  • .NET微信公众号开发-2.0创建自定义菜单