面试官:JVM调优,主要针对是哪一个区域?JVM内存结构是怎样的?
作为一个Java程序员,在日常的开发中,不必像C/C++程序员那样,为每一个内存的分配而操心,JVM会替我们进行自动的内存分配和回收,方便我们开发。但是一旦发生内存泄漏或者内存溢出,如果对Java内存结构不清楚,那将会是一件非常麻烦的事情!本文笔者将为大家详解Java内存结构。
面试tips
- 聊聊Java内存结构?都有哪些组成部分?哪些是线程共享?哪些是线程私有?
- 我们通常说的JVM调优,主要针对是哪一个区域?这个区域中那一块是最大的?主要用于存放什么内容?
- Java虚拟机栈存储的内容是什么?
- 程序计数器的作用是什么?当内存不足时,程序计数器会发生OOM吗?
- 聊聊你对方法区的看法?在不同JDK版本中,方法区的演进过程是什么?
你是否对这些问题都了如指掌?看完本文相信你心中就会有答案!
JVM架构
JVM的平台无关性
- 计算机的CPU、内存、显卡等等属于硬件
- 常用的MacOs、Windows、Linux属于计算机的操作系统
- 而Java的虚拟机,也就是JVM是运行在操作系统之上的,与硬件没有直接联系,JVM也是Java能够跨平台的根本原因。
JVM架构
1. Class Loader 类加载器
类加载器的作用是加载类文件到内存,比如编写一个 HelloWord.java 文件,然后通过 javac 编译成 class 文件,那怎么才能加载到内存中被执行呢?答案就是 Class Loader。当然,不是任何 .class 文件就能被加载的,Class Loader 加载的 class 文件是有格式要求。
2. Execution Engine 执行引擎
Class Loader 只负责加载,只要符合文件结构就加载,至于说能不能运行,则不是它负责的,那是由 Execution Engine 负责的。执行引擎也叫做解释器 (Interpreter),负责解释命令,提交操作系统执行。
3. Native Interface 本地接口
本地接口的作用是融合不同的编程语言为 Java 所用,它的初衷是融合 C/C++ 程序,Java 诞生的时候是 C/C++ 横行的时候,于是就在内存中专门开辟了一块区域处理标记为 native 的代码。
4. Runtime data area 运行时数据区
运行时数据区是整个 JVM 的重点。我们所有写的程序都被加载到这里,之后才开始运行,下面会重点讲解运行时数据区。
JVM执行流程
当然不同的VM的具体实现细节也不是不一样的,现在使用的比较多的JDK8版本就是Sun HotSpot VM与BEA JRockit VM合并之后开发出的JDK版本。
下面就是一个Java文件加载并执行的流程
运行时数据区
运行时数据区是JVM中最为重要的部分。也是我们在调优时需要重点关注的区域。
运行时数据区分为:程序计数器、Java虚拟机栈、本地方法栈、Java堆区、方法区
其中
- 线程私有:程序计数器、虚拟机栈、本地方法栈
- 线程共享:堆、方法区, 堆外内存(Java7的永久代或JDK8的元空间、直接内存)
JDK 1.8 和之前的版本略有不同,我们这里以 JDK 1.7 和 JDK 1.8 这两个版本为例介绍。
JDK 1.7:
JDK 1.8:
程序计数器
程序计数寄存器(Program Counter Register),Register 的命名源于 CPU 的寄存器,寄存器存储指令相关的线程信息,CPU 只有把数据装载到寄存器才能够运行。它是一块很小的内存空间,几乎可以忽略不计。也是运行速度最快的存储区域
JVM 中的 PC 寄存器是对物理 PC 寄存器的一种抽象模拟。可以看作是当前线程所执行的字节码的行号指示器。解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器。
任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。如果当前线程正在执行的是 Java 方法,程序计数器记录的是 JVM 字节码指令地址,如果是执行 native 方法,则是未指定值(undefined)
程序计数器是唯一一个不会出现
OutOfMemoryError
的内存区域,它的生命周期与线程保持一致。
Java虚拟机栈
每个线程在创建的时候都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次 Java 方法调用,是线程私有的,生命周期和线程一致。
1、栈的内部结构
每个栈帧(Stack Frame)中存储着:
局部变量表(Local Variables):主要存放了编译期可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)
操作数栈(Operand Stack):主要用于存放方法执行过程中产生的中间计算结果。另外,计算过程中产生的临时变量也会放在操作数栈中。如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中。
动态链接(Dynamic Linking):指向运行时常量池的方法引用。Class 文件的常量池里保存有大量的符号引用比如方法引用的符号引用,当一个方法要调用其他方法,需要将常量池中指向方法的符号引用转化为其在内存地址中的直接引用。这个过程也被称为 动态连接
方法返回地址(Return Address):方法正常退出或异常退出的地址
PS: 局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收
2、栈的执行流程
- JVM 直接对虚拟机栈的操作只有两个:方法调用入栈,方法执行结束出栈。
- 在线程中,同一时间只会有一个活动的栈帧,即(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧(Current Frame),与当前栈帧对应的方法就是当前方法(Current Method),定义这个方法的类就是当前类(Current Class)
- 如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,称为新的当前栈帧
- 不同线程中所包含的栈帧是不允许相互引用的,即不可能在一个栈帧中引用另外一个线程的栈帧
3、栈的异常
StackOverFlowError
: 若栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出StackOverFlowError
错误。OutOfMemoryError
: 如果栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError
异常。
本地方法栈
本地方法栈和Java虚拟机栈所发挥的作用非常相似
二者区别在于: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现
StackOverFlowError
和OutOfMemoryError
两种异常。在 Hotspot JVM 中,直接将本地方法栈和虚拟机栈合二为一
Java堆区
栈是运行时的单位,而堆是存储的单位。
Java 堆是 Java 虚拟机管理的内存中最大的一块,被所有线程共享。
PS:关于Java堆有很多细节可以深挖,例如堆的分代和对象的创建和回收等,后续我还会专门开一篇文章展开讲
1、堆的存储内容
此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数据都在这里分配内存。成员变量名和值存储于堆中,其生命周期和对象的是一致的。
Java 世界中“几乎”所有的对象都在堆中分配,但是,随着 JIT 编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。从 JDK 1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。
2、堆的分区和垃圾回收
为了进行高效的垃圾回收,虚拟机把堆内存逻辑上划分成三块区域(分代的唯一理由就是优化 GC 性能):
- 新生带(年轻代):新对象和没达到一定年龄的对象都在新生代
- 老年代(养老区):被长时间使用的对象,老年代的内存空间应该要比年轻代更大
- 元空间(JDK1.8 之前叫永久代):一些方法中的操作临时对象等,JDK1.8 之前是占用 JVM 内存,JDK1.8 之后直接使用物理内存
3、堆出现的异常
堆这里最容易出现的就是 OutOfMemoryError
错误,比如:
java.lang.OutOfMemoryError: GC Overhead Limit Exceeded
:当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。java.lang.OutOfMemoryError: Java heap space
:假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发此错误。(和配置的最大堆内存有关,且受制于物理内存大小。最大堆内存可通过-Xmx
参数配置,若没有特别配置,将会使用默认值,详见:Default Java 8 max heap sizeopen in new window)
方法区
方法区属于是 JVM 运行时数据区域的一块逻辑区域,是各个线程共享的内存区域
1、方法区和永久代以及元空间是什么关系呢?
方法区和永久代以及元空间的关系很像 Java 中接口和类的关系,类实现了接口,这里的类就可以看作是永久代和元空间,接口可以看作是方法区,也就是说永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中方法区的两种实现方式。并且,永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现变成了元空间。
2、方法区的存储内容
当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据。在加载类和结构到虚拟机后,就会创建对应的运行时常量池。
运行时常量池(Runtime Constant Pool)是虚拟机规范中是方法区的一部分,在加载类和结构到虚拟机后,就会创建对应的运行时常量池;而字符串常量池是这个过程中常量字符串的存放位置。所以从这个角度,字符串常量池属于虚拟机规范中的方法区,它是一个逻辑上的概念;而堆区,永久代以及元空间是实际的存放位置。
3、方法区在 JDK6、7、8中的演进细节
JDK版本 | 是否有永久代,字符串常量池放在哪里? | 方法区逻辑上规范,由哪些实际的部分实现的? |
---|---|---|
jdk1.6及之前 | 有永久代,运行时常量池(包括字符串常量池),静态变量存放在永久代上 | 这个时期方法区在HotSpot中是由永久代来实现的,以至于这个时期说方法区就是指永久代 |
jdk1.7 | 有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中; | 这个时期方法区在HotSpot中由永久代(类型信息、字段、方法、常量)和堆(字符串常量池、静态变量)共同实现 |
jdk1.8及之后 | 取消永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆中 | 这个时期方法区在HotSpot中由本地内存的元空间(类型信息、字段、方法、常量)和堆(字符串常量池、静态变量)共同实现 |
参考文章
面试官:JVM调优,主要针对是哪一个区域?JVM内存结构是怎样的?的更多相关文章
- JVM调优 jdk版本 机器配置 建议jvm参数 备注
https://juejin.im/post/5b091ee35188253892389683 大型跨境电商JVM调优经历 前提:某大型跨境电商业务发展非常快,线上机器扩容也很频繁,但是对于线上机器的 ...
- JVM调优系列:(五)JVM常用调试参数和工具
转自:http://blog.csdn.net/opensure/article/details/46715769 JVM常用调试参数: –verbose:gc在虚拟机发生内存回收时在输出设备显示信息 ...
- 【JVM进阶之路】十:JVM调优总结
1.调优原则 JVM调优听起来很高大上,但是要认识到,JVM调优应该是Java性能优化的最后一颗子弹. 比较认可廖雪峰老师的观点,要认识到JVM调优不是常规手段,性能问题一般第一选择是优化程序,最后的 ...
- jvm系列(六):jvm调优-从eclipse开始
jvm调优-从eclipse开始 概述 什么是jvm调优呢?jvm调优就是根据gc日志分析jvm内存分配.回收的情况来调整各区域内存比例或者gc回收的策略:更深一层就是根据dump出来的内存结构和线程 ...
- SpringBoot项目优化和Jvm调优(转)
原文:https://blog.csdn.net/wd2014610/article/details/82182617 项目调优作为一名工程师,项目调优这事,是必须得熟练掌握的事情. 在SpringB ...
- jvm调优-从eclipse开始
一.概述 什么是jvm调优呢?jvm调优就是根据gc日志分析jvm内存分配.回收的情况来调整各区域内存比例或者gc回收的策略:更深一层就是根据dump出来的内存结构和线程栈来分析代码中不合理的地方给予 ...
- Java虚拟机(七):JVM调优案列
Eclispe启动优化 概述 什么是jvm调优呢?jvm调优就是根据gc日志分析jvm内存分配.回收的情况来调整各区域内存比例或者gc回收的策略:更深一层就是根据dump出来的内存结构和线程栈来分析代 ...
- Spark调优之JVM调优
一.JVM调优 JVM: 老年代: 存放少量生命周期长的对象,如连接池 年轻代: Spark task执行算子函数自己创建的大量对象 JVM机制: 对象进入java虚拟机之后会放在eden区域和一个s ...
- SpringBoot项目优化和Jvm调优(楼主亲测,真实有效)
项目调优 作为一名工程师,项目调优这事,是必须得熟练掌握的事情. 在SpringBoot项目中,调优主要通过配置文件和配置JVM的参数的方式进行. 在这边有一篇比较好的文章,推荐给大家! Spring ...
- SpringBoot项目优化和Jvm调优
https://www.cnblogs.com/jpfss/p/9753215.html 项目调优 作为一名工程师,项目调优这事,是必须得熟练掌握的事情. 在SpringBoot项目中,调优主要通过配 ...
随机推荐
- hexo 博客插入本地图片时遇到的坑
哈喽大家好,我是咸鱼. 最近一直在折腾博客的事,说是 hexo 极易上手,我觉得只仅限于在安装部署的时候,随着对 hexo 的深入使用,发现遇到的问题还是挺多的. 那今天来讲一下我在把本地图片插入到 ...
- Sublime Text 3 初试牛刀
每次我在其他视频网站上看学习视频的时候,看着老师用的编辑器高大上档次,而我一般用Notepad,和Dreamweaver去编辑网页,需要每一行代码,打进去,效率低.最近看到sublime编辑器,在网上 ...
- python教程6.4-json序列化
序列化:dumps,编码,将python类型转成json对象 反序列化:loads,解码,将json对象转成python对象 pickle 模块提供了四个功能:dumps.loads.dump.loa ...
- cesium教程4-用entity加载glb和gltf格式的小模型
示例完整代码如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...
- C++ 资源大全:标准库、Web框架、人工智能等 | 最全整理
C++ 资源列表,内容包括: 标准库.Web应用框架.人工智能.数据库.图片处理.机器学习.日志.代码分析等 目录 进程间通信 Json 日志 机器学习 数学 内存分配 多媒体 网络 PDF 物理学 ...
- 前端使用 Konva 实现可视化设计器(10)- 对齐线
请大家动动小手,给我一个免费的 Star 吧~ 大家如果发现了 Bug,欢迎来提 Issue 哟~ github源码 gitee源码 示例地址 不知不觉来到第 10 章了,感觉接近尾声了... 对齐线 ...
- Clip-跳过
在 Stable Diffusion 1.x 模型中,CLIP 用作文本嵌入.CLIP模型由多层组成.他们一层一层地变得更加具体.过于简单化,第一层可以理解"人",第二层可以区分& ...
- 5分钟明白LangChain 的输出解析器和链
本文介绍 LangChain 的输出解析器OutputParser的使用,和基于LangChain的LCEL构建链. 1. 输出解析器OutputParser 1.1.为什么需要OutputParse ...
- 轻松绕过 Graphql 接口爬取有米有数的商品数据
轻松绕过 Graphql 接口爬取有米有数的商品数据 有米有数数据的 API 接口,使用的是一种 API 查询语言 graphql.所有的 API 只有一个入口,具体的操作隐藏在请求数据体里面传输. ...
- Django----模型----数据库的基本操作
1.添加记录 方式一:save() 方式二(推荐):create() 2.查询记录 (1)all() 查询所有记录,返回queryset对象. stu_list = Student.objects.a ...