总结篇」别再说自己不会JVM了,看完这篇能和面试官扯上半小时(上)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

  • 线程共享区域,因此是线程不安全的。

  • 能够发生OutOfMemoryError。

其实,Java堆区还可以划分为新生代和老年代,新生代又可以进一步划分为Eden区、Survivor 1区、Survivor 2区。具体比例参数的话,可以看一下下面这张图。

在这里插入图片描述

我想图中已经解释相当清楚了,就没有必要文字说明了吧?关于Java堆对象的创建,以及何时会发生内存泄漏,我后面应该会专门写一篇文章,这里的话就只是一些理论介绍。

虚拟机栈(VM Stack)

Java虚拟机栈也是一块被开发者重点关注的地方,同样,先把干货放上来:

  • 线程私有区域,每一个线程都有独享一个虚拟机栈,因此这是线程安全的区域。

  • 存放基本数据类型以及对象的引用。

  • 每一个方法执行的时候会在虚拟机栈中创建一个相应栈帧,方法执行完毕后该栈帧就会被销毁。方法栈帧是以先进后出的方式虚拟机栈的。

  • 每一个栈帧又可以划分为局部变量表、操作数栈、动态链接、方法出口以及额外的附加信息。

  • 这个区域可能有两种异常:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常(通常是递归导致的);JVM动态扩展时无法申请到足够内存则抛出OutOfMemoryError异常。

同样,这篇文章反响好的话,实战练习后面单独出文章。

本地方法栈(Native Method Stack)

本地方法栈其实可以和Java虚拟机栈进行对比理解,唯一不同的是本地方法栈是Java程序在调用本地方法的时候创建栈帧的地方。和JVM栈一样,这个区域也会抛出StackOverflowError和OutOfMemoryError。

方法区(Method Area)

方法区,也应该是以一块被重点关注的区域。同样,方法区的主要特点如下:

  • 线程共享区域,因此这是线程不安全的区域。

  • 方法区也是一个可能会发生OutOfMemoryError的区域。

  • 方法区存储的是从Class文件加载进来的静态变量、类信息、常量池以及编译器编译后的代码。

对于方法区,我觉得重点应该说一下常量池。常量池可以分为Class文件常量池以及运行时常量池,Java程序运行后,Class文件中的信息被字节码执行引擎加载到了方法区,从而形成了运行时常量池。

另外,说起方法区,可能还有人会把它与永久代、元空间混为一谈。那么他们之间的区别到底是什么?方法区是Java虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现。不过Java 8以后就没有永久代这个说法了,元空间取代了永久代。

程序计数器(Program Counter Register)

程序计数器非常简单,想必大家都不是Java的初学者了,也都应该明白一点线程与进程的概念?(灵魂拷问,你明白么?)不明白没关系,我一句话给你讲清楚。

进程是资源分配的最小单位,线程是CPU调度的最小单位,一个进程可以包含多个线程, Java线程通过抢占的方法获得CPU的执行权。现在可以思考下面这个场景。

某一次,线程A获得CPU的执行权,开始执行内部程序。但是线程A的程序还没有执行完,在某一时刻CPU的执行权被另一个线程B抢走了。后来经过线程A的不懈努力,又抢回了CPU的执行权,那么线程A的程序又要从头开始执行?

这个时候程序计数器就粉墨登场了,它的作用就是记录当前线程所执行的位置。 这样,当线程重新获得CPU的执行权的时候,就直接从记录的位置开始执行,分支、循环、跳转、异常处理也都依赖这个程序计数器来完成。此外,程序计数器还具有以下特点:

  • 线程私有,每一个线程都有一个程序计数器,因此它是线程安全的。

  • 唯一一块不存在OutOfMemoryError的区域,可能是设计者觉得没必要。

对象的创建与访问


对象的创建

前面我们已经说过,对象是在堆中创建的,通常只需要new一个就行了。难道就是这么简单?确实没有这么简单,就单单是这样的new关键字,Java虚拟内部进行了一系列的sao操作。

当虚拟机遇到字节码new指令时,就会去运行时常量池寻找该实例化对象相对应的类是否被加载、解析和初始化。如果没有被加载,就会先加载该类的信息,否则就为新生对象分配内存。

分配内存无非有两种方法:

  • 指针碰撞:通过一个类似于指针的东西为对象分配内存,前提是堆空间是相对规整的。

  • 空闲列表:堆空间不规整,使用一个列表记录了哪些空间是空闲的,分配内存的时候会更新列表。

以上是两种不同的方法,至于虚拟机使用哪一种方法,这个就取决虚拟机的类型了。

对象的内存布局

对象在堆中的存储布局可以分为三个部分:

  • 对象头

  • 第一类信息:存储对象自身的运行时数据,例如哈希码、GC分代年龄、锁状态标志等等。

  • 第二类信息:指针类型,Java虚拟机通过这个指针来确定该对象是那个类的实例。

  • 实例数据:对象真正存储的有效信息。

  • 对齐填充:没有实际的意义,起着占位符的作用。

对象的访问定位

我们前面说到过,Java虚拟机栈中存储的是基本数据类型和对象引用。基本数据类型我们已经很清楚了,那么,这个对象引用又是什么鬼?

是这样的,对象实例存储在Java堆中,通过这个对象引用我们就可以找到对象在堆中的位置。但是,对于如何定位到这个对象,不同的Java虚拟机又有不同的方法。

通常情况下,有下面两种方法:

  • 使用句柄访问,通常会在Java堆中划分一块句柄池。

  • 使用直接指针,这样Java虚拟机栈中存储的就是该对象在堆中的地址。

使用句柄访问对象使用直接指针访问对象

这两种访问对象的方法各有优势。使用直接指针进行访问,就可以直接定位到对象,减小了一次指针定位的时间开销(使用句柄的话会通过句柄池的指针二次定位对象),最大的好处就是速度更快。但是使用句柄的话,就是当对象发生移动的时候,可以不用改变栈中存储的reference,只需要改变句柄池中实例数据的指针。

垃圾收集算法


论对象已死?

前面一部分我们都在讲对象,一个对象能够被创建,那么这个对象在什么时候被销毁了?通常,判断一个对象是否被销毁有两种方法:

  • 引用计数算法: 为对象添加一个引用计数器,每当对象在一个地方被引用,则该计数器加1;每当对象引用失效时,计数器减1。但计数器为0的时候,就表白该对象没有被引用。

  • 可达性分析算法: 通过一系列被称之为“GC Roots”的根节点开始,沿着引用链进行搜索,凡是在引用链上的对象都不会被回收。

就像上图的那样,绿色部分的对象都在GC Roots的引用链上,就不会被垃圾回收器回收,灰色部分的对象没有在引用链上,自然就被判定为可回收对象。

那么,问题来了,这个GC Roots又是什么?下面列举可以作为GC Roots的对象:

  • Java虚拟机栈中被引用的对象,各个线程调用的参数、局部变量、临时变量等。

  • 方法区中类静态属性引用的对象,比如引用类型的静态变量。

  • 方法区中常量引用的对象。

  • 本地方法栈中所引用的对象。

  • Java虚拟机内部的引用,基本数据类型对应的Class对象,一些常驻的异常对象。

  • 被同步锁(synchronized)持有的对象。

现在,我们已经知道哪些对像是可以回收的。那么又要采取什么方式对对象进行回收呢?垃圾回收算法主要有三种,依次是标记-清除算法标记-复制算法标记-整理算法。这三种垃圾收集算法其实也比较容易理解,下面我先介绍概念,然后在依次总结一下。

标记–清除算法

见名知义,标记–清除算法就是对无效的对象进行标记,然后清除。如下图:

对于标记–清除算法,你一定会清楚看到,在进行垃圾回收之后,堆空间有大量的碎片,出现了不规整的情况。在给大对象分配内存的时候,由于无法找到足够的连续的内存空间,就不得不再一次触发垃圾收集。另外,如果Java堆中存在大量的垃圾对象,那么垃圾回收的就必然进行大量的标记和清除动作,这个势必造成回收效率的降低

复制算法

标记–复制算法就是把Java堆分成两块,每次垃圾回收时只使用其中一块,然后把存活的对象全部移动到另一块区域。如下图:

标记–复制算法有一个很明显的缺点,那就是每次只使用堆空间的一半,造成了Java堆空间使用率的的下降

现在大部分Java虚拟机的垃圾回收器使用的就是标记–复制算法,但是,对于Java堆空间的划分,并不是简单地一分为二。

还记得这张图么?

前面讲Java内存结构的时候,提到过Java堆的具体划分,那现在就来好好的说一说。

首先得从两个分代收集理论说起:

  • 弱分代假说:大多数对象的生命存活时间很短。

  • 强分代假说:经过越多次垃圾收集的对象,存活的时间就越久。

正是这两个分代假说,使得设计者对Java堆的划分更加合理。下面,来说一下GC的分类:

  • Minor GC/Young GC:针对新生代的垃圾收集。

  • Major GC/Old GC:针对老年代的垃圾收集。

  • Full GC:针对整个Java堆以及方法区的垃圾收集。

好了,知道了GC的分类,是时候知道GC的流程了。

通常情况下,初次被创建的对象存放在新生代的Eden区,当第一次触发Minor GC,Eden区存活的对象被转移到Survivor区的某一块区域。以后再次触发Minor GC的时候,Eden区的对象连同一块Survivor区的对象一起,被转移到了另一块Survivor区。可以看到,这两块Survivor区我们每一次只使用其中的一块,这样也仅仅是浪费了一块Survivor区。

每经历过一次垃圾回收的对象,它的分代年龄就加1,当分代年龄达到15以后,就直接被存放到老年代中。

还有一种情况,给大对象分配内存的时候,Eden区已经没有足够的内存空间了,这时候该怎么办?对于这种情况,大对象就会直接进入老年代

标记–整理算法

标记–整理算法算是一种折中的垃圾收集算法,在对象标记的过程,和前面两个执行的是一样步骤。但是,进行标记之后,存活的对象会移动到堆的一端,然后直接清理存活对象以外的区域就可以了。这样,既避免了内存碎片,也不存在堆空间浪费的说法了。但是,每次进行垃圾回收的时候,都要暂停所有的用户线程,特别是对老年代的对象回收,则需要更长的回收时间,这对用户体验是非常不好的。如下图:

HotSpot的算法细节


根节点枚举

根节点枚举,其实就是找出可以作为GC Roots的对象,在这个过程中,所有的用户线程都必须停下。到目前为止,几乎还没有虚拟机可以做到GC Roots遍历与用户线程并发执行。当然,可达性分析算法中最耗时的寻找引用链的过程已经可以做到和用户线程并发执行了。那么,为什么需要在根节点枚举的时候停止用户线程?

其实也不难考虑,如果进行GC Roots遍历的时候,用户线程没有暂停,根节点集合的对象引用关系还在不断发生变化,这样遍历到的结果是不准确的。那么,Java虚拟机在查找GC Roots的时候,是真的需要进行全局遍历?

其实不是这样的,HotSpot虚拟机通过一个叫做OopMap的数据结构,可以知道哪些地方存储了对象引用。这样,大大减小了GC Roots的遍历时间。

最后

我还通过一些渠道整理了一些大厂真实面试主要有:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合,附带超详细答案,希望能帮助到大家。

新鲜出炉的蚂蚁金服面经,熬夜整理出来的答案,已有千人收藏

还有专门针对JVM、SPringBoot、SpringCloud、数据库、Linux、缓存、消息中间件、源码等相关面试题。

新鲜出炉的蚂蚁金服面经,熬夜整理出来的答案,已有千人收藏

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
FJVM8-1713332283201)]

还有专门针对JVM、SPringBoot、SpringCloud、数据库、Linux、缓存、消息中间件、源码等相关面试题。

[外链图片转存中…(img-fiZb2lH6-1713332283202)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-2uI94kTP-1713332283202)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 28
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
面试中讲解JVM,需要以30分钟的时间,尽可能详细地涵盖其核心概念和运行机制。首先,我将从介绍JVM的定义和作用开始。 JVM(Java虚拟机)是Java程序执行的平台,它通过将字节码翻译为本机代码来实现跨平台的特性。JVM是在操作系统上的虚拟计算机,可以执行Java程序,并负责内存管理、垃圾回收、线程管理、安全性等任务。 然后,我简要介绍JVM的架构。JVM由三个主要组件组成:类加载器(Class Loader)、运行时数据区(Runtime Data Area)和执行引擎(Execution Engine)。类加载器负责将Java字节码从文件系统、网络等加载到运行时数据区。运行时数据区包括方法区、堆、栈、本地方法栈和程序计数器,用于存储程序执行期间的数据。执行引擎将字节码转化为机器码,使其能够在底层硬件上执行。 接下来,我详细介绍垃圾回收机制。JVM通过垃圾回收机制自动管理内存,有效地回收不再使用的对象。主要的垃圾回收算法有标记-清除、复制、标记-整理等。我解释这些算法的原理、优缺点,并介绍常见的垃圾收集器,如Serial、Parallel、CMS和G1。 然后,我讲解JVM的优化技术。包括即时编译器、分层编译、逃逸分析、锁消除等。我介绍这些技术的原理和实现,以及如何通过它们提升Java程序的性能和效率。 最后,我谈论关于性能调优的一些建议。我涵盖一些常见的性能瓶颈、调优工具和技术,如监控工具、性能测试、堆分析等。我还强调重要的最佳实践,如避免内存泄漏、合理使用线程和同步等。 通过这样一个30分钟的面试讲解,我能够全面介绍JVM的核心知识,包括其定义和作用、架构、垃圾回收机制、优化技术和性能调优建议。这样的讲解能够展示我对JVM的扎实理解和广泛知识背景,体现我在Java开发中的专业能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值