49.【面试宝典】面试宝典-JVM内存模型

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 【面试宝典】面试宝典-JVM内存模型

前文如上:

39.【面试宝典】面试宝典-redis过期k值回收策略,缓存淘汰策略

40.【面试宝典】面试宝典-redis持久化

41.【面试宝典】面试宝典-redis常用数据类型概述

42.【面试宝典】面试宝典-redis缓存穿透,击穿,雪崩

43.【面试宝典】面试宝典-redis缓存穿透之布隆过滤器

44.【面试宝典】面试宝典-redis分布式锁

45.【面试宝典】面试宝典-另外两种分布式锁

46.【面试宝典】面试宝典-虚拟机类加裁机制

47.【面试宝典】面试宝典-类加载器及双亲委派

48.【面试宝典】面试宝典-如何打破双亲委派机制

合集参考:面试宝典


作为一名JAVA骨灰级菜鸟开发(工作七年多了),Java虚拟机是必备的技能,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。


内存模型


首先就是JVM的内存模型


网络异常,图片无法展示
|


从图中,可以看到整个Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。 而这些区域分为线程共有和线程私有两部分。


网络异常,图片无法展示
|


线程私有的


  • 程序计数器
  • 一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的 程序计数器,这类内存也称为“线程私有”的内存。正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是 Native 方法,则为空。这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。
  • 虚拟机栈
    是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame) 用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、 方法返回值和异常分派( DispatchException)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异 常)都算作方法结束。
  • 本地方法栈
    本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为 Native 方法服务, 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个 C 栈,但HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。


线程共享的


是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行 垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以 细分为:新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。


新生代: 是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发 MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区。分别占比8:1:1


Eden 区: Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行 一次垃圾回收。ServivorFrom上一次 GC 的幸存者,作为这一次 GC 的被扫描者。ServivorTo 保留了一次 MinorGC 过程中的幸存者。


MinorGC 采用复制算法。MinorGC 的过程(复制->清空->互换),新生代的minorGC简单介绍如下:

(1)eden、servicorFrom 复制到 ServicorTo,首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的标准(默认年龄加到了15),则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不 够位置了就放到老年区);

(2)然后,清空 Eden 和 ServicorFrom 中的对象;

(3)最后,ServicorTo 和 ServicorFrom 角色互换,原 ServicorTo 成为下一次GC 时的 ServicorFrom 区。


老年代: 主要存放应用程序中生命周期长的内存对象。老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行 了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足 够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。


MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的 时候,就会抛出OOM(Out of Memory)异常。


扩展:对象何时进入老年代


(1)躲过15次GC之后进入老年代:系统刚启动时,创建的各种各样的对象,都是分配在年轻代里。随着慢慢系统跑着跑着,年轻代满了,就会出发Minor GC ,有的对象可能存活时间会久一些,无论年轻代中怎么垃圾回收,类似这种对象都不会被回收掉。而此对象每次在年轻代里躲过一次Minor GC被转移到一块Survivor区域中,他的年龄就会增加一岁默认的设置下,当对象的年龄达到15岁时,也就是躲过15次GC的时候,他就会转移到老年代里去。具体是多少岁进入老年代,可以通过JVM参数“-XX:MaxTenuringThreshold”来设置,默认是15岁。


(2)动态对象年龄判断:对于对象年龄,有另外一个规则可以让对象进入老年代,不用等到15次GC过后才可以。规则就是,假如说当前放对象的Survivor区域里,一批对象的总大小大于了这块Survivor区域的内存大小的50%,那么此时大于等于这批对象年龄的对象,就可以直接进入老年代了。

网络异常,图片无法展示
|


假设这个图中的Survivor2区有两个对象,这俩对象的年龄一样,都是2岁。然后俩对象加起来内存超过了50MB,这个时候,Survivor2区里大于等于2岁的对象,都要进入老年代里去。

这就是动态年龄判断的规则,这条规则也会让一些年轻代的对象进入老年代另外实际这个规则运行的时候是如下的逻辑:年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区的50%,此时就会把年龄n以上的对象都放入老年代。


(3)大对象直接进入老年代:有一个JVM参数,就是“-XX:PretenureSizeThreshold”,可以把它的值设置为字节数,比如“1048576”字节,就是1MB。意思就是如果你要创建一个大于这个大小的对象,比如一个超大的数组,或者是别的啥东西,此时就直接把这个大对象放到老年代里去,压根不会经过年轻代。之所以这么做,是因为要避免年轻代里出现那种大对象,然后屡次躲过GC,还得把他在两个Survivor区域里来回复制多次之后才能进入老年代。(减少开销)


(4)Minor GC后的对象太多,无法放入Survivor区:这个比较好理解,因为新生代分为 Eden 区、ServivorFrom、ServivorTo 三个区,分别占比8:1:1,复制清楚粘贴到ServivorTo 空间不足呀


(5)老年代空间分配担保规则:如果年轻代里大量对象存活,确实自己的Survivor区放不下了,必须转移到老年代去,但是如果老年代里空间也不够放这些对象,改怎么办呢?(说白了就是老年代也放不下年轻代存活下来的对象了,就会先去判断自己要不要进行Full GC,然后再minor GC把年轻代存活的的对象挪过来)

首先,在执行任何一次Minor GC之前,JVM都会先检查一些老年代可用的内存空间,是否大于年轻代所有对象的总大小.

网络异常,图片无法展示
|

在最极端的情况下,可能年轻代Minor GC之后,所有对象都存活下来了,在执行Minor GC之前,发现老年代的可用内存已经小于了年轻代的全部对象大小了. 那么老年代内存空间就不够了?

首先如Minor GC之前,发现老年代的可用内存已经小于了年轻代的全部对象大小,就会看一个“-XX:-HandlePromotionFailure”的参数是否设置了,如果设置了这个参数接下来会看看老年代的内存大小,是否大于之前每一次Minor GC后进去老年代对象的平均大小, 但是如果上面步骤判断失败了,或者是“-XX:-HandlePromotionFailure”参数没设置,此时就会直接触发一次“Full GC”,就是对老年代进行垃圾回收,尽量腾出来一些内存空间,然后再执行Minor GC。

如果上面两个步骤判断成功,那么就可以尝试Minor GC,此时进行Minor GC有几种可能:

①Minor GC过后,剩余的存活对象的大小,小于Survivor区的大小,那么此时存活对象进入Survivor区即可

②Minor GC过后,剩余的存活对象的大小,大于Survivor区的大小,但是小于老年代可用内存大小,就直接进入老年代即可\

③Minor GC过后,剩余的存活对象的大小,大于Survivor区的大小,同时大于老年代可用内存大小,此时就会发生“Handle Promotion Failure”的情况,这个时候就会出发一次“Full GC”。

Full GC就是对老年代进行垃圾回收,同时也一般会对年轻代进行垃圾回收。如果Full GC之后,老年代还是没有足够空间存放Minor GC过后的剩余存活对象,此时就会导致所谓的“OOM”内存溢出了。


(6).老年代垃圾回收算法

老年代触发垃圾回收的机制,一般就是两个;

①在Minor GC之前,一通检查发现很可能Minor GC之后要进入老年代的对象太多了,老年代放不下,此时需要提前触发Full GC再然后再带着进行Minor GC;

②要不然是在Minor GC之后,发现剩余对象太多放入老年代都放不下了。

老年代采取的垃圾回收算法是标记整理算法。

老年代的垃圾回收算法的速度至少比年轻代的垃圾回收算法的速度慢10倍。

如果系统频繁出现老年代的Full GC垃圾回收,会导致系统性能被严重影响,出现频繁卡顿的情况。

所以,所谓的JVM优化,就是尽可能的让对象都在年轻代里分配和回收,尽量别让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,同时给系统充足的内存大小,避免年轻代频繁的进行垃圾回收。


参考:blog.csdn.net/wangshiwen0…


方法区(Heap-线程共享)-运行时数据区


  • 即我们常说的永久代(Permanent Generation), 用于存储被 JVM 加载的类信息、常量、 态变量、即时编译器编译后的代码等数据.
  • 运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。Java 虚拟机对 Class 文件的每一部分(自然也包括常量 池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会 被虚拟机可、装载和执行。GC 不会在主程序运行期对永久区域进行清理。所以这 也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。(1.6和1.8异常不一样)
    在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。 元空间 的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,不再由 MaxPermSize控制,而是使用本地内存。 因此,默认情况下,元空间的大小仅受本地内存限制。 jdk1.8中:类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中,所以注意string 字符串1.6("计算机","java" false,false)和1.8("计算机","java" true,false) ==的区别。


直接内存

      直接内存不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。直接内存是Java堆外的、直接向系统申请的内存区间。大小和系统硬件内存有关,一般NIO用的多。


公众号,感谢关注

网络异常,图片无法展示
|



相关文章
|
14天前
|
SQL 缓存 监控
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
本文详细解析了数据库、缓存、异步处理和Web性能优化四大策略,系统性能优化必知必备,大厂面试高频。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
|
14天前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
12天前
|
Java Linux Windows
JVM内存
首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制。
11 1
|
1月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
1月前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
50 10
|
30天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
1月前
|
监控 Java easyexcel
面试官:POI大量数据读取内存溢出?如何解决?
【10月更文挑战第14天】 在处理大量数据时,使用Apache POI库读取Excel文件可能会导致内存溢出的问题。这是因为POI在读取Excel文件时,会将整个文档加载到内存中,如果文件过大,就会消耗大量内存。以下是一些解决这一问题的策略:
102 1
|
1月前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
385 0
|
29天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
60 1