当前位置: 首页 > 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代码规范之注释和文档
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • 实现windows 窗体的自己画,网上摘抄的,学习了
  • 【前端学习】-粗谈选择器
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • CSS 提示工具(Tooltip)
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • Go 语言编译器的 //go: 详解
  • Java 最常见的 200+ 面试题:面试必备
  • JavaScript 奇技淫巧
  • jdbc就是这么简单
  • Odoo domain写法及运用
  • Python 基础起步 (十) 什么叫函数?
  • swift基础之_对象 实例方法 对象方法。
  • Vue小说阅读器(仿追书神器)
  • 紧急通知:《观止-微软》请在经管柜购买!
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 前端技术周刊 2019-01-14:客户端存储
  • 如何实现 font-size 的响应式
  • 如何用Ubuntu和Xen来设置Kubernetes?
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 积累各种好的链接
  • 进程与线程(三)——进程/线程间通信
  • 如何在招聘中考核.NET架构师
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • ​业务双活的数据切换思路设计(下)
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • # Apache SeaTunnel 究竟是什么?
  • #if和#ifdef区别
  • #pragma once与条件编译
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (简单) HDU 2612 Find a way,BFS。
  • (全注解开发)学习Spring-MVC的第三天
  • .gitignore文件_Git:.gitignore
  • .h头文件 .lib动态链接库文件 .dll 动态链接库
  • .net 前台table如何加一列下拉框_如何用Word编辑参考文献
  • .NET6 开发一个检查某些状态持续多长时间的类
  • .Net下的签名与混淆
  • @ComponentScan比较