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

surfacecreated啥时被调用_小豪爱上JVM--运行时数据区 | 面试必问

7f2e04ff983e14bbc9dacdc4f809d0bd.png

小豪今天又又又去面试了,托宇哥的福,面试官竟然真的问到了JVM,原本小豪以为能够凭借昨天宇哥教的知识蒙混过关,没想到面试官小哥哥没有按套路出牌,小豪于是又又又出糗啦,大家快来一起笑话他,嘻嘻嘻。

故事人物背景介绍小豪: 23岁,武汉某双非本科不知名专业大学四年级学生,成绩一般,面临毕业,对后端开发、Java很感兴趣,正求职找工作。宇哥: 跟小豪通过租房认识,两人是室友,26岁,毕业后长期从事软件开发工作,是一个半吊子工程师,兴趣爱好是吹牛,不打草稿那种。

目录速览

不按套路出牌的面试官

JVM运行时数据区

线程私有区

线程共有区

new一个对象的过程

对象的内存布局以及对象的访问

你又懂了

参考

更多

不按套路出牌的面试官

这家小公司先是让hr小姐姐跟小豪聊了聊上下班时间、薪资待遇的一些基本情况,小豪表示基本都能接受,倒贴钱都愿意,只要你能要我。小姐姐点了点头,于是小姐姐说去叫技术小哥哥来给他面试,如果通过的话就可以考虑入职啦。小豪捏了捏拳头,在心里给自己加了把油。

推门进来的是一个戴眼镜的小哥哥,看样子比小豪大不了几岁。面试官看着非常放松,对小豪点了点头。

1ba3a61238a46e127f70377bc65bbaa4.gif

小豪:额,你好,我先做个自我介绍吧。我叫帅豪,来自……
面试官:嗯,你简历上提到了解JVM,JVM有啥好处你知道不,Java为啥要使用它啊?

小豪:(努力回忆昨天的学习成果)额,第一点是JVM可以使Java程序能够一次编译,到处运行;第二点是JVM提供了托管环境,可以帮助开发人员自动完成内存管理和垃圾回收,使开发人员能将更多的精力放到业务逻辑上,相比C++,Java减少了…..

面试官:嗯,你提到了内存管理和垃圾回收。你了解垃圾回收器吗?知道哪些垃圾收集器?
小豪:额,这一块记得不是很清楚。
面试官:嗯,没关系,那你了解JVM运行时内存吗?内存泄漏一般发生在JVM的哪个部分?

小豪:面试官,我们聊类加载机制吧,你问的我恰好不会。
面试官:就不,我是面试官,听我的。
小豪:真不会啊。
面试官:没关系,出去帮我关下门。

4b2f813759dc9b180cda2b01748b7463.png

JVM运行时数据区

宇哥下班回到家,看到小豪正在学习《深入理解Java虚拟机:JVM高级特性与最佳实践》。

宇哥:学着呢,咋样啊今天面试,有进步没?

小豪:今天真问到JVM了,但面试官问的刚好是你昨天没说的……你得对我负责啊宇哥!

宇哥:哎哟我去,我这嘴开过光。没事,今天我再给你补补,昨天给你讲了JVM架构中的上部分--类加载,那今天着重给你讲讲JVM运行时数据区,这一块本身知识点比较简单,但是可以向外发散出很多问题,你要听好哦。

我先给你把运行时数据区的图在昨天的基础上再画的细致一点,一个个给你讲其中每个区域的作用和特点。

4d0867d57119647c147452b253fa66a7.png

线程私有区

程序技术器(Program Counter Register)当前线程所执行的字节码的行号指示器控制程序执行顺序。如果执行的是一个Native方法,那这个计数器是空的。JVM中多线程是通过线程轮流切换并分配处理器执行时间的方式所实现,为了切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,线程私有。此内存区域不用考虑OutOfMemoryError情况

虚拟机栈(Java Virtual Machine Stacks): 栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,8种基本类型的变量+对象的引用变量+实例方法都是在函数的栈内存中分配。

小豪:宇哥,既然虚拟机栈的生命期是跟随线程的生命期的,那么线程结束栈内存也就释放,对于栈来说是不是不存在垃圾回收问题
宇哥:是的,后面要讲的Java堆内存才是垃圾收集器管理的主要区域。对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。

栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧 F1,并被压入到栈中,
A方法又调用了 B方法,于是产生栈帧 F2 也被压入栈,B方法又调用了 C方法,于是产生栈帧 F3 也被压入栈……执行完毕后,先弹出F3栈帧,再弹出F2栈帧,再弹出F1栈帧……遵循“先进后出”/“后进先出”原则。

每个方法执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。栈的大小和具体JVM的实现有关,通常在256K~756K之间,约等于1Mb左右。

cfb663d16a632e0b5ef7fa72b015aa0a.png

图示在一个栈中有两个栈帧:栈帧 2是最先被调用的方法,先入栈,然后方法 2 又调用了方法1,栈帧 1处于栈顶的位置,栈帧 2 处于栈底,执行完毕后,依次弹出栈帧 1和栈帧 2,线程结束,栈释放。每执行一个方法都会产生一个栈帧,保存到栈(后进先出)的顶部,顶部栈就是当前的方法,该方法执行完毕 后会自动将此栈帧出栈。
在虚拟机规范中对虚拟机栈区域规定了两种异常情况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常,相当于你调用的栈指针指向为空了。大多数虚拟机栈可以动态扩展,如果因为内存原因导致虚拟机栈申请扩大内存没得到允许,就会出现OutOfMemoryError异常

本地方法栈(Native Method Stack):作用类似于虚拟机栈,只不过作用对象是本地方法,而不是Java方法。与虚拟机栈一样,本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。

线程共有区

Java堆 (Java Heap):此内存唯一目的就是存放对象实例,在虚拟机启动时就创建,属于线程共有区,是各种垃圾收集器和垃圾回收算法的主要战场。如果在堆中没有内存完成对象实例分配,并且堆也无法扩展,就会抛出OOM异常

方法区(Method Area):方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 JDK1.6以前HotSpot用永久代的方式实现方法区的作用,而其它虚拟机是不存在永久代的概念的,做这些改动的目的都是出于内存分配问题的考虑,当方法区无法满足内存分配需求时,也会抛出OOM异常。JDK1.7以后HotSpot也逐渐放弃了这一做法,改用直接内存来实现方法区,取名为元空间。

运行时常量池属于方法区的一部分,运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OOM异常。JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。

直接内存(Direct Memory):直接内存不属于虚拟机运行时数据区的一部分,但这部分内存也被频繁使用,会导致OutOfMemoryError异常出现,因为虽然本地直接内存的分配不受Java堆大小限制,但是却会收到本机总内存大小以及处理器寻址空间限制,服务器管理员在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制,从而动态扩展内存时出现OOM异常。

小豪:宇哥,直接内存到底有啥作用呢,为什么要把元空间放在直接内存中,跟以前的永久代方法区有啥区别吗

宇哥:永久代实现的方法区有-XX:MaxPermSize设置的固定大小上限容易内存溢出,而元空间使用的是直接内存,虽然受本机可用总内存的限制仍旧可能溢出,但是比原来出现的几率会更小。元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 MaxPermSize 控制了, 而由系统的实际可用空间来控制,这样能加载的类就更多了。

new一个对象的过程

小豪:(^o^)/,宇哥,你一口气把程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区、直接内存各自的作用、可能出现的异常情况都讲了,真棒,我都记住了。

宇哥:学东西光记住没用呀,这些区域分开来看确实就这些东西,但在Java语言运行过程中无时无刻都在发生的对象创建、对象访问、方法调用、方法执行过程中,这些运行时数据区域都做了哪些工作,怎么完成交互工作,这才是难点,也是面试官最爱考察的地方。

小豪:哦多克,那你继续吧,我准备好了。

宇哥:今天先给你讲讲对象创建和对象访问吧,方法调用和执行等到我们把运行时数据区域讲完,开始讲JVM架构上中下的执行引擎部分的时候再给你讲方法的调用和访问。

小豪:OK,上次那个架构图我还记得,上层的类加载器、中间的运行时数据区,下层的执行引擎

宇哥:是的,那我们从一个面试题开始,如果面试官问你,你能说一下new 一个对象的过程吗?你会怎么回答。

小豪:嗯,创建对象,结合昨天学习的类加载和今天讲的运行时数据区,我能还原一下大概,先判断该对象所属的类是否已被加载了,如果没有加载就会先通过类的全限定名来加载,然后进行类加载机制中的链接、初始化工作,然后在堆中分配内存空间给实例对象,然后给该实例对象赋值,设置,再执行实例对象的创建代码?

宇哥:嗯,说的大体上差不多,但是不够详细,你要把创建实例对象过程中的类加载、以及堆、方法区每一块区域和每一个步骤的详细作用说的更详细和具体就更好了,这也是面试官问你这个问题的目的所在。

首先,虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能够在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过,如果没有,那么必须先执行相应的类加载过程。现在假设是第一次使用该类创建对象,那么流程大概是这样:

  1. 加载:由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例,Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)。
  2. 连接:将加载到JVM中的二进制字节流的类数据信息合并到JVM的运行时状态中,细分为三步;验证:验证格式规范、语义正确、操作可行。准备:为类中的所有静态变量分配内存空间,并为其设置一个默认值(由于还没有产生对象,实例变量不在此操作范围内);被final修饰的static变量(常量),会直接赋值。解析:将常量池中的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法),这个也可以在初始化之后再执行。
  3. 初始化:为静态变量赋具体值,执行静态代码块。

最终,方法区会存储当前类类信息,包括类的静态变量、类初始化代码(定义静态变量时的赋值语句 和 静态初始化代码块)、实例变量定义、实例初始化代码(定义实例变量时的赋值语句实例代码块和构造方法)和实例方法,还有父类的类信息引用。

  1. 分配内存:在类加载检查通过后,接下来虚拟机将为新生对象在堆中分配内存。分配的内存包括本类和父类的所有实例变量,但不包括任何静态变量。
  2. 初始化内存空间:将方法区内对实例变量的定义拷贝一份到堆区,然后赋默认值。这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
  3. 设置对象头信息:初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
  4. 执行方法:在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始, 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

小豪:宇哥,我按照你说的画了个图,方便我记忆这个过程。

e334a436351f864d0d13ad6fc0c80819.png

宇哥:下次如果真被问到了这个问题,每个过程具体做了什么你也要能说上来哦。

对象的内存布局以及对象的访问

小豪:宇哥,你刚说要设置对象头信息设置,包括对象属于哪个类的实例、类的元数据信息指针、对象的哈希码、对象的GC分代年龄等等,那对象头是个什么东东啊?

宇哥:对象头是对象内存布局里面的一个概念,就是虚拟机中存储的每一个对象的堆内存主要分为三个块:对象头、实例数据和对齐填充。

  • Hotspot 虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的自身运行时数据(哈希码、GC 分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。
  • 实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容
  • 对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

小豪:哦,我知道了,这就是常说的Java对象内存模型--JMM?

宇哥:哎哟我的天,还好我跟你聊到了这一茬,不然你以后得闹大笑话。我问你,JMM(Java内存模型)、JVM内存结构(运行时数据区)、Java对象内存模型(对象的内存布局),这几个该您你是不是没分清楚,弄混了?

小豪:哈哈,好像是,我一直分不清楚谁是谁?不过你这么一说,我好像知道了。JVM内存结构也就是今天讲的运行时数据区,Java对象内存模型就是刚刚讲的Java对象在虚拟机中的内存布局。那常说的JMM是啥呀宇哥?

宇哥:可算我今天没白唠叨,JMM(Java内存模型)是用于多线程通信的,以后你学并发编程就知道了,你学volatile关键字可见性的时候其实遇见过它。

小豪:哦哦哦,我想起来了,就是几个线程修改共享变量在各自内存中的值后要更新到主内存中,那个内存图我见过几次,原来它才是JMM。

宇哥:是的,JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序提供内存可见性保证。

小豪:宇哥,创建就是为了使用,你把创建对象的过程说完了,可是虚拟机中是怎么访问这个对象的呀?

宇哥:问的好哦,你还记得运行时数据区中的栈保存了哪些数据不?

小豪:记得呀,栈中就是一个个栈帧,保存了8种基本类型的变量+对象的引用变量+实例方法等等。

宇哥:是的,Java程序需要通过栈上的引用数据来操作堆上的具体对象

e5ec2da922fd1d3784712a2dde1585f2.png


小豪:哦,我懂了,原来栈、堆、方法区之间是这么相互交互的。

你又懂了

宇哥:你又懂了哈,今天其实就给你简单聊了下运行时数据区、创建对象的过程以及JMM、JVM内存结构、Java对象内存布局这几种容易搞混的概念,这是很多很多面试题的根源地,你以后会慢慢有所体会。

小豪:啊,宇哥,也就是说我今天学了这个还是不够呗,那面试到底要咋办嘛?

宇哥:饭一口口吃呀,最起码你今天又进步了很多,等以后再给你比把GC、调优、执行引擎讲了,你的JVM基本不会有啥大问题了,应付面试绝对够了,而且后面有时间的话我专门给你唠唠JVM常见的面试题。

小豪:哈哈哈,那最好了,我要求也不高,懂些基础,会点实践,能让面试官点点头就是我的小幸运!

宇哥:真有你的,吃饭了凑弟弟!
小豪:走走走,炒猪肉吃!

参考

深入理解Java虚拟机:JVM高级特性与最佳实践/周志明著. 第2版。北京:机械工业出版社.

更多

觉得文章写的不错,关注、点赞、评论是对我最大的支持!
欢迎关注微信公众号LearnJava,一起学习,一起交流哦!

http://www.xdnf.cn/news/819325.html

相关文章:

  • Macbook air(2012 MID)乞丐版升级——继续发挥余热
  • visio2002无法安装(您必须首先安装Enterprise Architect Edition of visual Studio.Net2003)的解决方案
  • 分享54个ASP.NET源码总有一个是你想要的
  • Web前端-BOM之Navigator对象
  • Linux内核中内存相关的操作函数
  • 由于找不到d3dx9_26.dll文件导致游戏软件无法运行启动问题
  • 《Microsoft Visual Studio 6.0 Enterprise Edition》(完整9CD,带中文MSDN Library)下载
  • Qt学习笔记之--Qt内置图标一览表
  • ASCII码对照表(HTML颜色代码表)
  • C语言程序设计经典例题100道(四)
  • 性能优化之YUICompressor压缩JS、CSS
  • 未能找到你安装了radmin服务器,Radmin
  • 服务器如何端口映射?
  • 全网最全!分子材料计算模拟软件Materials Studio安装教程(附安装包),以及使用教程!...
  • Framework-Res.apk 反编译、编译、打包、替换详细教程
  • 大疆RoboMaster技术总监:我是如何成为一名机器人工程师的
  • Ubuntu 11.10简介
  • QeePHP:一切从头开始
  • 小凡模拟器(DynamipsGUI)打不开的简单解决方法
  • VirtualBox使用教程
  • php学习
  • 内存卡无法格式化怎么办?这5个办法可以帮你
  • 搭建私有云的5大主流方案
  • 正态分布(Normal distribution)又名高斯分布(Gaussian distribution)
  • Ubuntu安装QQ教程
  • 主流数据库连接池配置信息梳理
  • Spring MVC DataBinder
  • Windows11系统win32kbase.sys文件丢失问题
  • 程序员入门教程【非常详细】从零基础入门到精通,看完这一篇就够了 !_程序员怎么学
  • 神奇的运放--你都了解了吗?