进阶架构师面试重灾区:JVM整体架构、内存模型与调优实战
在互联网这个行业中,谁掌握了底层的核心知识,谁就能在激烈的竞争环境中脱颖而出。JVM看起来很难,只要你掌握了学习JVM的规律和方法,吃透它,其实很简单的。
JVM的分类
这里,我们先来说说什么是VM吧,VM的中文含义为:虚拟机,指的是使用软件的方式模拟具有完整硬件系统功能、运行在一个完全隔离环境中的完整计算机系统,是物理机的软件实现。
常用的虚拟机有:VMWare、Virtual Box,Java Virtual Machine(JVM,Java虚拟机)。
这里,我们重点聊的就是JVM,Java虚拟机。看下图。
这张图看起来还是比较简单的,JVM运行于操作系统之上,操作系统是运行在计算机硬件上的。
关于JVM,其实有很多大厂开发了不同版本的JVM,比较知名的有:Sun HotSpot VM、BEA JRockit VM、IBM J9 VM、 Azul VM、 Apache Harmony、 Google Dalvik VM、 Microsoft JVM等等。
现在使用的比较多的JDK8版本就是Sun HotSpot VM与BEA JRockit VM合并之后开发出的JDK版本。
JVM的构成
JVM主要由三个子系统构成,分别为:类加载器子系统、运行时数据区(内存结构)和字节码执行引擎。
当我们开发Java程序时,首先会编写.java文件,之后,会将.java文件编译成.class文件。
JVM中,会通过类装载子系统将.class文件的内容装载到JVM的运行时数据区,而JVM的运行时数据区又会分为:方法区、堆、栈、本地方法栈和程序计数器 几个部分。
在装载class文件的内容时,会将class文件的内容拆分为几个部分,分别装载到JVM运行时数据区的几个部分。其中,值得注意的是:程序计数器的作用是:记录程序执行的下一条指令的地址。
方法区也叫作元空间,主要包含了:运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应的Class实例的引用等信息。
在JVM中,程序的执行是通过执行引擎进行的,执行引擎会调用本地方法的接口来执行本地方法库,进而完成整个程序逻辑的执行。
我们常说的垃圾收集器是包含在执行引擎中的,在程序的运行过程中,执行引擎会开启垃圾收集器,并在后台运行,垃圾收集器会不断监控程序运行过程中产生的内存垃圾信息,并根据相应的策略对垃圾信息进行清理。
这里,大家需要注意的是:栈、本地方法栈和程序计数器是每个线程运行时独占的,而方法区和堆是所有线程共享的。所以,栈、本地方法栈和程序计数器不会涉及线程安全问题,而方法区和堆会涉及线程安全问题。
方法区(元空间)
很多小伙伴一看到方法区三个字,脑海中的第一印象可能是存储方法的地方吧。
实则不然,方法区的另一个名字叫作元空间,相信不少小伙伴或多或少的听说过元空间。这个区域是JDK1.8中划分出来的。主要包含:运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应的Class实例的引用等信息。方法区中的信息能够被多个线程共享。
例如,在程序中声明的常量、静态变量和有关于类的信息等的引用,都会存放在方法区,而这些引用所指向的具体对象 一般都会在堆中开辟单独的空间进行存储,也可能会在直接内存中进行存储。
堆
堆中主要存储的是实际创建的对象,也就是会存储通过new关键字创建的对象,堆中的对象能够被多个线程共享。堆中的数据不需要事先明确生存期,可以动态的分配内存,不再使用的数据和对象由JVM中的GC机制自动回收。对JVM的性能调优一般就是对堆内存的调优。
Java中基本类型的包装类:Byte、Short、Integer、Long、Float、Double、Boolean、Character类型的数据是存储在堆中的。
堆一般会被分成年轻代和老年代。而年轻代又会被进一步分为1个Eden区和2个Survivor区。在内存分配上,如果保持默认配置的话,年轻代和老年代的内存大小比例为1 : 2,年轻代中的1个Eden区和2个Survivor区的内存大小比例为:8 : 1 : 1。
栈
栈一般又叫作线程栈或虚拟机栈,一般存储的是局部变量。在Java中,每个线程都会有一个单独的栈区,每个栈中的元素都是私有的,不会被其他的栈所访问。栈中的数据大小和生存期都是确定的,存取速度比较快。
在Java中,所有的基本数据类型(byte、short、int、long、float、double、boolean、char)和引用变量(对象引用)都是在栈中的。一般情况下,线程退出或者方法退出时,栈中的数据会被自动清除。
程序在执行过程中,会在栈中为不同的方法创建不同的栈帧,在栈帧中又包含了:局部变量表、操作数栈、动态链接和方法出口。
关于局部变量表、操作数栈、动态链接和方法出口的具体作用,会在《架构师进阶系列》中的后续文章中详细阐述。
栈中一般会存储对象的引用,这些引用所指向的具体对象一般都会在堆中开辟单独的地址空间进行存储,也有可能存储在直接内存中。
注意:这里说的是这些引用所指向的具体对象一般都会在堆中开辟单独的地址空间进行存储,也有可能存储在直接内存中。
因为在JVM中,如果开启了逃逸分析和标量替换,则可能不会再在堆上创建对象,可能会将对象直接分配到栈上,也可能不再创建对象,而是进一步分解对象中的成员变量,将其直接在栈上分配空间并赋值。
本地方法栈
本地方法栈相对来说比较简单,就是保存native方法进入区域的地址。
例如,在Java中创建线程,调用Thread对象的start()方法时,会通过本地方法start0()调用操作系统创建线程的方法。此时,本地方法栈就会保存start0()方法进入区域的内存地址。
程序计数器
程序计数器也叫作PC计数器,只要存储的是下一条将要执行的命令的地址。
JVM调优实战
在JVM中,主要是对堆(新生代)、方法区和栈进行性能调优。各个区域的调优参数如下所示。
堆:-Xms、-Xmx新生代:-Xmn方法区(元空间):-XX:MetaspaceSize、-XX:MaxMetaspaceSize栈(线程):-Xss为了更加直观的表述,我们可以将JVM的内存区域和对应的调优参数总结成下图所示。
在设置JVM启动参数时,需要特别注意方法区(元空间)的参数设置。
关于方法区(元空间)的JVM参数主要有两个:-XX:MetaspaceSize和-XX:MaxMetaspaceSize。
-XX:MetaspaceSize: 指的是方法区(元空间)触发Full GC的初始内存大小(方法区没有固定的初始内存大小),以字节为单位,默认为21M。达到设置的值时,会触发Full GC,同时垃圾收集器会对这个值进行修改。
如果在发生Full GC时,回收了大量内存空间,则垃圾收集器会适当降低此值的大小;如果在发生Full GC时,释放的空间比较少,则在不超过设置的-XX:MetaspaceSize值或者在没设置-XX:MetaspaceSize的值时不超过21M,适当提高此值。
-XX:MaxMetaspaceSize: 指的是方法区(元空间)的最大值,默认值为-1,不受堆内存大小限制,此时,只会受限于本地内存大小。
最后需要注意的是: 调整方法区(元空间)的大小会发生Full GC,这种操作的代价是非常昂贵的。如果发现应用在启动的时候发生了Full GC,则很有可能是方法区(元空间)的大小被动态调整了。
所以,为了尽量不让JVM动态调整方法区(元空间)的大小造成频繁的Full GC,一般将-XX:MetaspaceSize和-XX:MaxMetaspaceSize设置成一样的值。例如,物理内存8G,可以将这两个值设置为256M。
最后,我们一起看下在物理内存8G的情况下,启动应用程序时,可以设置的JVM参数。当然,我这里给出的是一些经验值,实际部署到生产环境时,需要经过压测找到最佳的参数值。
启动SpringBoot
java ‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐jar xxx.jar启动Tomcat(Linux)
在Tomcat bin目录下catalina.sh文件里配置。
‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M
启动Tomcat(Windows)
在Tomcat bin目录下catalina.bat文件里配置。
‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M
本篇文章源于:公众号-冰河技术,如有侵权,请联系删除。
相关文章
猜你喜欢
-
用来写java程序的软件
推荐原创在Java编程的浩瀚海洋中,我曾是一名孜孜不倦的探索者,寻找着能让我高效编写代码的灯塔。在这段旅程中,我发现了两个强大的编程工具——Eclipse和Intell...
-
java锁机制面试题
推荐原创在软件开发中,多线程环境是常见的场景,而线程安全问题也随之而来。Java作为一种广泛使用的编程语言,提供了多种锁机制来保证线程安全。在这篇文章中,我将以第一人称...
-
java连接redis集群主节点挂了导致无法存储数据
推荐原创作为一名资深的后端开发者,我经常面对数据存储与高可用性的问题。Redis作为当前流行的内存数据结构存储系统,以其高性能、高可用性而广受欢迎。然而,即使是Redi...
-
java转换小写
推荐原创在编程的世界里,字符串处理是一项基本且频繁的任务。作为一名Java开发者,我经常需要处理字符串的大小写转换,以确保数据的一致性和格式的正确性。字符串转换为小写是...
-
java转小写快捷键
推荐原创在Java编程的世界中,字符串处理是一项非常基础且频繁的操作。其中,将字符串转换为小写是一个常见的需求。作为一名Java开发者,我经常需要处理各种字符串转换问题...
-
Java读取配置文件的注解
推荐原创在软件开发过程中,配置文件是不可或缺的一部分,它允许开发者在不修改代码的前提下调整应用程序的行为。Java提供了多种方式来读取配置文件,其中使用注解(Annot...
-
java计算时间间隔
推荐原创在软件开发中,时间管理是一个不可或缺的部分,尤其是当我们需要记录操作的执行时间或比较两个时间点的差异时。Java提供了多种方式来处理时间计算,其中最常见的两种是...
-
java获取当前目录的上两层目录
推荐原创在Java编程的世界中,文件和目录的操作是一项基础而重要的技能。作为一名Java开发者,我经常需要处理文件系统的各种任务,比如读取文件、写入数据、或者仅仅是导航...
-
java线程状态和操作系统线程状态
推荐原创正文:
-
java的switch的default
推荐原创在Java编程语言中,`switch`语句是一种条件语句,它允许一个变量与多个值进行比较,从而选择执行不同的代码块。它是一种比多个`if-else`语句更加清晰...
-
java的scanner函数
推荐原创在软件开发的旅程中,我经常与Java的Scanner类打交道。这个类是Java标准库中处理输入的强大工具,它允许我们从不同的输入源读取数据。今天,我将分享两个详...
-
java测试类怎么调用方法
推荐原创在软件开发的漫长旅途中,测试环节犹如一盏明灯,指引着代码质量的航向。作为一名Java开发者,我深知测试类的重要性。它不仅是代码质量的守护者,更是开发效率的加速器...
-
java断点续传记录下载位置
推荐原创在网络通信的世界中,断点续传技术是一种非常实用的功能,它允许用户在下载过程中遇到中断时,能够从中断点继续下载,而不是从头开始。这不仅节省了时间,也减少了网络资源...
-
java文字转语音给前端输出
推荐原创在数字化时代,交互方式的多样化是提升用户体验的关键。文字转语音(Text-to-Speech,TTS)技术作为其中一种,能够将文本信息转化为语音输出,为视障用户...
-
java数组转成list
推荐原创在Java编程中,数组和List是两种常见的数据结构,它们各自有着独特的用途和优势。作为一个Java开发者,我经常需要在数组和List之间进行转换,以适应不同的...
-
java数组定义的几种方式
推荐原创在Java编程语言中,数组是一种基本的数据结构,它允许我们将一组固定数量的相同类型的元素存储在一起。数组的使用非常广泛,因为它们提供了一种简单的方式来组织和访问...
-
java数据库连接池的作用
推荐原创在Java的世界里,数据库连接池是一种至关重要的技术,它极大地提升了应用程序与数据库交互的效率。我曾与数据库连接池紧密合作,见证了它如何优化资源管理,减少系统开...
-
java数据库连接池原理
推荐原创正文:
-
java数字转字符char
推荐原创在Java编程中,我们经常需要将数字转换为字符,这在处理ASCII编码、文件编码、网络协议等方面尤为重要。数字到字符的转换不仅仅是一个简单的操作,它涉及到字符编...
-
java接口自动化测试实战课程
推荐原创在软件开发的海洋中,自动化测试如同一盏明灯,指引着代码质量的航向。我,作为一名资深的测试工程师,深知自动化测试在提高软件质量和开发效率中的重要性。今天,我将带领...
-
java怎么调用方法实现数组遍历
推荐原创 -
java怎么获取数组的值
推荐原创作为一名Java开发者,我深知数组在编程中的重要性。数组是一种基本的数据结构,它允许我们存储一系列相同类型的元素。在Java中,数组的使用无处不在,从简单的排序...
-
Java开发环境配置实验报告
推荐原创 -
java定义数组的几种方式
推荐原创#### 开篇
-
java如何运行一个应用程序
推荐原创#### 开篇
-
java堆排序时间复杂度
推荐原创大家好,我是Kimi,一个由月之暗面科技有限公司开发的人工智能助手。今天,我将带大家深入了解一种高效的排序算法——堆排序。堆排序是一种基于二叉堆数据结构的比较类...
-
JAVA哈希表什么时候改的
推荐原创在Java的世界中,数据结构的演变总是伴随着性能和功能的不断优化。作为一名Java开发者,我经常需要选择适合项目需求的数据结构。今天,我想分享关于Java中哈希...
-
java向下转型格式
推荐原创在Java的世界里,类型转换是程序设计中不可或缺的一部分。作为面向对象语言的代表,Java提供了向上转型和向下转型两种类型转换方式。向上转型是隐式的,而向下转型...
-
java反射调用方法传参为null
推荐原创在Java的世界中,反射是一个强大的特性,它允许程序在运行时查询、访问和修改类、接口、字段和方法的属性。通过反射,我们可以动态地调用方法,即使这些方法的参数为`...
-
java十种常见的异常
推荐原创在Java编程的世界里,异常处理是确保程序稳定性和健壮性的关键。作为一名开发者,我深知掌握异常处理机制的重要性。异常是程序运行时发生的不正常情况,它们可以是编译...
-
java动态编译的代码找不到依赖
推荐原创在Java的世界里,动态编译是一个强大的概念,它允许开发者在运行时编译Java源代码。这在某些情况下非常有用,比如在开发IDE插件、构建工具或者需要在运行时生成...
-
java动态编译源码
推荐原创在Java编程的世界中,动态编译是一种强大的技术,它允许开发者在运行时编译Java源代码。这种技术在某些场景下非常有用,例如在需要根据用户输入生成代码,或者在开...
-
java判断两字符串相等
推荐原创在Java编程的世界里,字符串是最常见的数据类型之一。我们经常需要判断两个字符串是否相等。但你知道吗?在Java中,字符串相等的判断并不像看上去那么简单。今天,...
-
java使用redis集群
推荐原创在现代软件开发中,缓存系统是提升应用性能的关键组件之一。Redis,作为一种高性能的键值存储数据库,因其出色的读写速度和丰富的数据结构支持,成为了缓存解决方案的...
-
java互斥锁和同步锁
推荐原创正文:
-
eclipse如何创建javaee项目
推荐原创#### 开篇
-
java的scanner怎么导入
推荐原创在Java编程世界中,与用户进行交互是必不可少的一部分。而Scanner类,作为Java标准库中用于获取用户输入的强大工具,扮演着至关重要的角色。我将从第一人称...
-
java的map方法
推荐原创在编程的世界里,我经常与Java打交道,尤其是它的集合框架。Map是Java集合框架中一个非常重要的接口,它存储键值对(key-value pairs),允许我...
-
java生成csv文件单元格设置数值模式
推荐原创在软件开发中,数据的导入和导出是一项常见的任务。CSV(逗号分隔值)文件因其简单性和广泛的兼容性而成为数据交换的优选格式之一。在Java中生成CSV文件并不复杂...
-
java流程图计算步骤
推荐原创在软件开发的海洋中,Java作为一艘强大的船只,承载着无数开发者的梦想与创造。我,作为一名Java开发者,深知在编程的旅途中,流程图是导航的重要工具。它不仅帮助...
-
Java数据类型转换形式有哪几种分别要符合什么规则
推荐原创在Java编程中,数据类型转换是实现不同数据类型间相互操作的基础。它允许我们根据需要将数据从一个类型转换为另一个类型,以便于进行计算、比较或存储。数据类型转换的...
-
java实现二进制转十六进制
推荐原创在软件开发中,数据格式的转换是常见的需求之一。特别是二进制与十六进制之间的转换,由于其在计算机系统中的广泛应用,成为了程序员必须掌握的技能。本文将从第一人称的角...
-
java定义数组必须定义长度么
推荐原创在Java编程语言中,数组是一种基本的数据结构,它允许我们存储一系列相同类型的元素。与Python等其他语言不同,Java数组在定义时必须指定其长度和元素类型。...
-
java定义数组不赋值值为多少
推荐原创#### 开头:
-
java大文件上传内存溢出
推荐原创在处理大型文件上传时,Java应用程序经常面临内存溢出的问题。这主要是因为Java虚拟机(JVM)在处理大文件时会消耗大量的内存资源。本文将详细解释大文件上传时...
-
java反射调用方法 参数限制
推荐原创在Java的世界里,反射是一个强大而复杂的功能,它允许程序在运行时查询、访问和操作类的对象。反射的用途广泛,从动态加载类到调用方法,再到获取字段值,它几乎无所不...
-
java去空格和换行
推荐原创在Java编程中,字符串处理是一项基础而重要的技能。作为开发者,我们经常需要对字符串进行各种操作,包括去除其中的空格和换行符。这不仅关乎代码的整洁性,还可能影响...
-
java匿名类和匿名内部类
推荐原创在Java的世界里,我曾被那些隐藏在代码深处的匿名类和匿名内部类所吸引。它们如同编程界的隐秘特工,执行任务时不留下任何身份痕迹。今天,我将带领大家深入探索这两种...
-
java创建list集合对象
推荐原创#### 开篇
-
javaword转pdf工具类
推荐原创作为一名软件开发者,我经常需要处理各种文档格式的转换,其中Word转PDF是日常工作中常见的需求之一。Word文档是一种广泛使用的文档编辑格式,而PDF则因其跨...