想要学会JVM调优,先掌握JVM内存模型和JVM运行原理

1、前言

今天将和你一起探讨Java虚拟机(JVM)的性能调优。

JVM算是面试中的高频问题了,通常情况下总会有人问到:请你讲解下 JVM 的内存模型,JVM 的

性能调优做过

2、为什么 JVM 在 Java 中如此重要

首先你应该知道,运行一个Java应用程序,我们必须要先安装 JDK 或者 JRE 包。

这是因为 Java应用在编译后会变成字节码,然后通过字节码运行在 JVM 中,而 JVM 是 JRE 的核

心组成部分。

JVM不仅承担了Java字节码的分析(JIT compiler)和执行(Runtime),同时也内置了自动内

存分配管理机制

这个机制可以大大降低手动分配回收机制可能带来的内存泄露和内存溢出风险,使 Java开发人员

不需要关注每个对象的内存分配以及回收,从而更专注于业务本身。

3、从了解内存模型开始

JVM 自动内存分配管理机制的好处很多,但实则是把双刃剑

这个机制在提升Java开发效率的同时,也容易使 Java 开发人员过度依赖于自动化,弱化对内存的

管理能力,这样系统就很容易发生 JVM 的堆内存异常,垃圾回收(GC)的方式不合适以及 GC

次数过于频繁等问题,这些都将直接影响到应用服务的性能

因此,要进行 JVM 层面的调优,就需要深入了解 JVM 内存分配和回收原理,这样在遇到问题

时,我们才能通过日志分析快速地定位问题;也能在系统遇到性能瓶颈时,通过分析JVM 调优来

优化系统性能

这也是整个模块的重点内容,今天我们就从 JVM 的内存模型学起,为后续的学习打下一个坚实的

基础。

4、JVM内存模型的具体设计

我们先通过一张 JVM 内存模型图,来熟悉下其具体设计。

在 Java 中,JVM内存模型主要分为堆、程序计数器、方法区、虚拟机栈和本地方法栈

我们来分析,JVM 的 5 个分区具体是怎么实现的呢?

1.堆(Heap)

堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到

了堆内存中。

堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 和Survivor 区,最后 Survivor 由

From Survivor和To Survivor 组成。

Java6 版本中,永久代在非堆内存区;到了 Java7 版本,永久代的静态变量和运行时常量池

合并到了堆中;而到了 Java8永久代被元空间取代了

结构如下图所示:

2.程序计数器(Program Counter Register)

程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循

环、跳转、异常、线程恢复等都依赖于计数器。

由于Java是多线程语言,当执行的线程数量超过CPU数量时,线程之间会根据时间片轮询争夺

CPU资源。

如果一个线程的时间片用完了,或者是其它原因导致这个线程的CPU资源被提前抢夺,那么这个退

出的线程就需要单独的一个程序计数器,来记录下一条运行的指令。

3.方法区(Method Area)

很多开发者都习惯将方法区称为“永久代”,其实这两者并不是等价的。

HotSpot 虚拟机使用永久代来实现方法区,但在其它虚拟机中,例如,Oracle的JRockit、IBM 的

J9 就不存在永久代一说。

因此,方法区只是 JVM 中规范的一部分,可以说,在HotSpot 虚拟机中,设计人员使用了永久代

来实现了 JVM 规范的方法区。

方法区主要是用来存放已被虚拟机加载的类相关信息,包括类信息、运行时常量池、字符串常量

。类信息又包括了类的版本、字段、方法、接口和父类等信息。

JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶

段。

在加载类的时候,JVM会先加载 class 文件,而在 class 文件中除了有类的版本、字段、方法和接

口等描述信息外,还有一项信息是常量池 (Constant Pool Table),用于存放编译期间生成的各种字

面量和符号引用。

字面量包括字符串(String a=“b”)、基本类型的常量(final 修饰的变量),符号引用则包括类和

方法的全限定名(例如 String 这个类,它的全限定名就是Java/lang/String)、字段的名称描述

以及方法的名称描述符

而当类加载到内存中后,JVM 就会将 class 文件常量池中的内容存放到运行时的常量池中;在解析

阶段,JVM 会把符号引用替换为直接引用(对象的索引值)。

例如,类中的一个字符串常量在 class 文件中时,存放在 class 文件常量池中的;在 JVM加载完类

之后,JVM 会将这个字符串常量放到运行时常量池中,并在解析阶段,指定该字符串对象的索引

值。

运行时常量池是全局共享的,多个类共用一个运行时常量池,class 文件中常量池多个相同的字符

串在运行时常量池只会存在一份。

方法区与堆空间类似,也是一个共享内存区,所以方法区是线程共享的。假如两个线程都试图访问

方法区中的同一个类信息,而这个类还没有装入 JVM,那么此时就只允许一个线程去加载它,另

一个线程必须等待。

HotSpot 虚拟机Java7 版本中已经将永久代的静态变量和运行时常量池转移到了堆中其余

部分则存储在JVM 的非堆内存中,而 Java8 版本已经将方法区中实现的永久代去掉了,并用元空

间(class metadata)代替了之前的永久代,并且元空间的存储位置是本地内存。

之前永久代的类的元数据存储在了元空间,永久代的静态变量(class static variables)以及运行

时常量池(runtime constant pool)则跟 Java7 一样,转移到了堆中。

那你可能又有疑问了,Java8 为什么使用元空间替代永久代,这样做有什么好处呢?

官方给出的解释是:

移除永久代是为了融合 HotSpot JVM 与 JRockit VM 而做出的努力,因为JRockit 没有永久代,所

以不需要配置永久代。

永久代内存经常不够用或发生内存溢出,爆出异常 java.lang.OutOfMemoryError: PermGen。

这是因为在 JDK1.7 版本中,指定的 PermGen 区大小为 8M,由于PermGen 中类的元数据信息在

每次 FullGC 的时候都可能被收集,回收率都偏低,成绩很难令人满意;还有为 PermGen分配多

大的空间很难确定PermSize的大小依赖于很多因素,比如,JVM加载的 class 总数、常量池的大

小和方法的大小等。

4.虚拟机栈(VM stack)

Java虚拟机栈是线程私有的内存空间,它和Java 线程一起创建

当创建一个线程时,会在虚拟机栈中申请一个线程栈,用来保存方法的局部变量、操作数栈、动态

链接方法和返回地址等信息,并参与方法的调用和返回。每一个方法的调用都伴随着栈帧的入栈操

作,方法的返回则是栈帧的出栈操作。

5.本地方法栈(Native Method Stack)

本地方法栈跟 Java 虚拟机栈的功能类似,Java虚拟机栈用于管理 Java 函数的调用,而本地方法

栈则用于管理本地方法的调用。

但本地方法并不是用 Java 实现的,而是由C语言实现的。

5、JVM的运行原理

看到这里,相信你对 JVM 内存模型已经有个充分的了解了。

接下来,我们通过一个案例来了解下代码和对象是如何分配存储的,Java 代码又是如何在 JVM 中运行的。

 
public class JVMCase {
     // 常量
     public final static String MAN_SEX_TYPE = "man";
     // 静态变量
     public static String WOMAN_SEX_TYPE = "woman";
     
     public static void main(String[] args) {
         Student stu = new Student();
         stu.setName("nick");
         stu.setSexType(MAN_SEX_TYPE);
         stu.setAge(20);
         JVMCase jvmcase = new JVMCase();
         // 调用静态方法
         print(stu);
         // 调用非静态方法
         jvmcase.sayHello(stu);
     }
     // 常规静态方法
     public static void print(Student stu) {
         System.out.println("name: " + stu.getName() + "; sex:" + stu.getSexType
     }
     // 非静态方法
     public void sayHello(Student stu) {
         System.out.println(stu.getName() + "say: hello"); 
     }
 }
                            
 class Student{
     private String name;
     private String sexType;
     private int age;
     
     public String getName() {
     return name;
     }
     public void setName(String name) {
     this.name = name;
     }
     public String getSexType() {
     return sexType;
     }
     public void setSexType(String sexType) {
     this.sexType = sexType;
     }
     public int getAge() {
     return age;
     }
     public void setAge(int age) {
     this.age = age;
     }
 }

当我们通过 Java 运行以上代码时,JVM 的整个处理过程如下:

1.JVM向操作系统申请内存

JVM 第一步就是通过配置参数或者默认配置参数向操作系统申请内存空间,根据内存大小找到具

体的内存分配表,然后把内存段的起始地址和终止地址分配给 JVM,接下来 JVM 就进行内部分

配。

2.JVM进行内部分配

JVM 获得内存空间后,会根据配置参数分配堆、栈以及方法区的内存大小

3.class文件加载、验证、准备以及解析

其中准备阶段会为类的静态变量分配内存,初始化为系统的初始值。

4.初始化阶段

完成上一个步骤后,将会进行最后一个初始化阶段。

在这个阶段中,JVM首先会执行构造器 <clinit> 方法编译器会在.java 文件被编译成.class 文件

,收集所有类的初始化代码,包括静态变量赋值语句、静态代码块、静态方法,收集在一起成为

<clinit>() 方法。

5.执行方法

启动main线程,执行main方法,开始执行第一行代码。

此时堆内存中会创建一个student对象,对象引用 student 就存放在栈中

此时再次创建一个 JVMCase 对象,调用 sayHello 非静态方法,sayHello 方法属于对象

JVMCase,此时sayHello 方法入栈,并通过栈中的 student 引用调用堆中的 Student对象;之后,

调用静态方法 print,print静态方法属于 JVMCase 类,是从静态方法中获取,之后放入到栈中,也

是通过 student 引用调用堆中的student 对象。

了解完实际代码在 JVM 中分配的内存空间以及运行原理,相信你会更加清楚内存模型中各个区域

的职责分工。

6、总结

我们主要学习了最基础的内存模型设计了解其各个分区的作用及实现原理

在 Java 中,JVM内存模型主要分为堆、程序计数器、方法区、虚拟机栈和本地方法栈

  • 堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享

  • 程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,支、循环、跳转、异常、线程恢复等都依赖于计数器。

  • 方法区只是 JVM 中规范的一部分,方法区主要是用来存放已被虚拟机加载的类相关信息,包括类信息、运行时常量池、字符串常量池

  • Java虚拟机栈用于管理 Java 函数的调用,而本地方法栈则用于管理本地方法的调用。

如今,JVM在很大程度上减轻了Java开发人员投入到对象生命周期的管理精力。

在使用对象的时候,JVM会自动分配内存给对象,在不使用的时候,垃圾回收器会自动回收对象,

释放占用的内存。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/274418.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IT安全:实时网络安全监控

了解庞大而复杂的网络环境并非易事&#xff0c;它需要持续观察、深入分析&#xff0c;并对任何违规行为做出快速反应。这就是为什么实时网络安全监控工具是任何组织 IT 安全战略的一个重要方面。 网络攻击和合规性法规是 IT 安全的两个主要驱动因素。同时&#xff0c;数据泄露…

LaTeX论文排版

LaTeX论文排版 LaTeX 简介与使用为什么选择使用LaTeX进行论文排版&#xff1f;LaTeX下载与安装LaTeX环境安装——TeX Live(Windows、Linux)安装IDE——TeXstudio LaTeX软件界面 BIT-thesis模板BIT-Thesis&#xff1a;主控文件demo.tex&#xff1a; 公式、图片、表格的排版使用L…

c语言用四种方式求解成绩之中最高分和最低分的差值

文章目录 一&#xff0c;题目二&#xff0c;方法1&#xff0c;方法一2&#xff0c;方法二3&#xff0c;方法三4&#xff0c;方法四 三&#xff0c;示例结果 一&#xff0c;题目 最高分最低分之差 输入n个成绩&#xff0c;换行输出n个成绩中最高分数和最低分数的差 输入 : 两行…

安防视频监控系统EasyCVR实现H.265视频在3秒内起播的注意事项

可视化云监控平台/安防视频监控系统EasyCVR视频综合管理平台&#xff0c;采用了开放式的网络结构&#xff0c;可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;同时…

Java中XML的解析

1.采用第三方开元工具dom4j完成 使用步骤 1.导包dom4j的jar包 2.add as lib.... 3.创建核心对象, 读取xml得到Document对象 SAXReader sr new SAXReader(); Document doc sr.read(String path); 4.根据Document获取根元素对象 Element root doc.getRootElement(); …

Bean 生命周期 和 SpringMVC 执行过程

这里简单记录下 Bean 生命周期的过程&#xff0c;方便自己日后面试用。源码部分还没看懂&#xff0c;这里先贴上结论 源码 结论

Spring Boot 中的虚拟线程

在本文中&#xff0c;我将讨论 Spring Boot 中的虚拟线程。 什么是虚拟线程&#xff1f; 虚拟线程作为 Java 中的一项功能引入&#xff0c;旨在简化并发性。 Virtual threads 是 轻量级的线程&#xff0c;由 Java Virtual Machine 而不是操作系统管理。它们被设计为易于使用且…

Apache Flink连载(十八):Flink On Yarn运行原理及环境准备

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录 1. Flink On Yarn运行原理…

Final Cut 视频剪辑快速入门,小白上手视频课的制作

本文是一个快速入门教程&#xff0c;如果您是0视频处理基础&#xff0c;又想录制网课或是一些对效果要求不高的视频那么这篇教程足够使用了。 本文主要用Final Cut处理视频课&#xff0c;本文是笔者在制作视频课过程中逐渐摸索的&#xff0c;如果您想制作一些比较专业的视频&a…

docker学习(二十一、network使用示例container、自定义)

文章目录 一、container应用示例1.需要共用同一个端口的服务&#xff0c;不适用container方式2.可用示例3.停掉共享源的容器&#xff0c;其他容器只有本地回环lo地址 总结 二、自定义网络应用示例默认bridge&#xff0c;容器间ip通信默认bridge&#xff0c;容器间服务名不通 自…

SpringBoot 3.2.0 基于Logback定制日志框架

依赖版本 JDK 17 Spring Boot 3.2.0 工程源码&#xff1a;Gitee 日志门面和日志实现 日志门面&#xff08;如Slf4j&#xff09;就是一个标准&#xff0c;同JDBC一样来制定“规则”&#xff0c;把不同的日志系统的实现进行了具体的抽象化&#xff0c;只提供了统一的日志使用接…

C语言实验1:C程序的运行环境和运行C程序的方法

一、算法原理 这是学C语言的入门&#xff0c;并不需要很高深的知识&#xff0c;一个hello world 或者一个简单的加法即可 二、实验要求 了解所用的计算机系统的基本操作方法&#xff0c;学会独立使用该系统。 了解在该系统上如何编辑、编译、连接和运行一个C程序。 通过运…

Kali Linux如何启动SSH并在Windows系统远程连接

文章目录 1. 启动kali ssh 服务2. kali 安装cpolar 内网穿透3. 配置kali ssh公网地址4. 远程连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 简单几步通过[cpolar 内网穿透](cpolar官网-安全的内网穿透工具 | 无需公网ip | 远程访问 | 搭建网站)软件实现ssh 远程连接kali! …

PyTorch实战:基于Seq2seq模型处理机器翻译任务(模型预测)

文章目录 引言数据预处理加载字典对象en2id和zh2id文本分词 加载训练好的Seq2Seq模型模型预测完整代码结束语 引言 随着全球化的深入&#xff0c;翻译需求日益增长。传统的人工翻译方式虽然质量高&#xff0c;但效率低&#xff0c;成本高。机器翻译的出现&#xff0c;为解决这…

八股文打卡day12——计算机网络(12)

面试题&#xff1a;HTTPS的工作原理&#xff1f;HTTPS是怎么建立连接的&#xff1f; 我的回答&#xff1a; 1.客户端向服务器发起请求&#xff0c;请求建立连接。 2.服务器收到请求之后&#xff0c;向客户端发送其SSL证书&#xff0c;这个证书包含服务器的公钥和一些其他信息…

Python 网络编程之搭建简易服务器和客户端

用Python搭建简易的CS架构并通信 文章目录 用Python搭建简易的CS架构并通信前言一、基本结构二、代码编写1.服务器端2.客户端 三、效果展示总结 前言 本文主要是用Python写一个CS架构的东西&#xff0c;包括服务器和客户端。程序运行后在客户端输入消息&#xff0c;服务器端会…

文件操作安全之-目录穿越流量告警运营分析篇

本文从目录穿越的定义,目录穿越的多种编码流量数据包示例,目录穿越的suricata规则,目录穿越的告警分析研判,目录穿越的处置建议等几个方面阐述如何通过IDS/NDR,态势感知等流量平台的目录穿越类型的告警的线索,开展日常安全运营工作,从而挖掘有意义的安全事件。 目录穿越…

Quartus的Signal Tap II的使用技巧

概述&#xff1a; Signal Tap II全称Signal Tap II Logic Analyzer&#xff0c;是第二代系统级调试工具&#xff0c;它集成在Quartus II软件中&#xff0c;可以捕获和显示实时信号&#xff0c;是一款功能强大、极具实用性的FPGA片上调试工具软件。 传统的FPGA板级调试是由外接…

TCP的三次握手

TCP 是一种面向连接的单播协议&#xff0c;在发送数据前&#xff0c;通信双方必须在彼此间建立一条连接。所谓的“连接”&#xff0c;其实是客户端和服务器的内存里保存的一份关于对方的信息&#xff0c;如 IP 地址、端口号等。 TCP 可以看成是一种字节流&#xff0c;它…

《Spring Cloud学习笔记:微服务保护Sentinel》

Review 解决了服务拆分之后的服务治理问题&#xff1a;Nacos解决了服务治理问题OpenFeign解决了服务之间的远程调用问题网关与前端进行交互&#xff0c;基于网关的过滤器解决了登录校验的问题 流量控制&#xff1a;避免因为突发流量而导致的服务宕机。 隔离和降级&#xff1a…