JVM详解

JVM内存模型

在这里插入图片描述

其中,

线程私有的:程序计数器,虚拟机栈,本地方法栈

线程共享的:堆,方法区,直接内存

1 程序计数器
  程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完。

java虚拟机的多线程是通过线程轮流切换并分配CPU的时间片的方式实现的,因此在任何时刻一个处理器(如果是多核处理器,则只是一个核)都只会处理一个线程,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,因此这类内存区域为“线程私有”的内存。

从上面的介绍中我们知道程序计数器主要有两个作用:

字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
在多线程的情况下**,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。**
  注意:程序计数器是唯一不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。

2 Java 虚拟机栈
  Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型。Java虚拟机栈是由一个个栈帧组成,线程在执行一个方法时,便会向栈中放入一个栈帧,每个栈帧中都拥有局部变量表、操作数栈、动态链接、方法出口信息。局部变量表主要存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)和对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。

Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。

StackOverFlowError:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。

OutOfMemoryError:若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。

Java 虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。

3 本地方法栈
  和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。

4
  堆是Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存(目前由于编译器的优化,对象在堆上分配已经没有那么绝对了,参见:https://www.cnblogs.com/aiqiqi/p/10650394.html)。

Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap)。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:其中新生代又分为:Eden空间、From Survivor、To Survivor空间。进一步划分的目的是更好地回收内存,或者更快地分配内存。“分代回收”是基于这样一个事实:对象的生命周期不同,所以针对不同生命周期的对象可以采取不同的回收方式,以便提高回收效率。从内存分配的角度来看,线程共享的java堆中可能会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
在这里插入图片描述

如图所示,JVM内存主要由新生代、老年代、永久代构成。

① 新生代(Young Generation):大多数对象在新生代中被创建,其中很多对象的生命周期很短。每次新生代的垃圾回收(又称Minor GC)后只有少量对象存活,所以选用复制算法,只需要少量的复制成本就可以完成回收。

新生代内又分三个区:一个Eden区,两个Survivor区(一般而言),大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到两个Survivor区(中的一个)。当这个Survivor区满时,此区的存活且不满足“晋升”条件的对象将被复制到另外一个Survivor区。对象每经历一次Minor GC,年龄加1,达到“晋升年龄阈值”后,被放到老年代,这个过程也称为“晋升”。显然,“晋升年龄阈值”的大小直接影响着对象在新生代中的停留时间,在Serial和ParNew GC两种回收器中,“晋升年龄阈值”通过参数MaxTenuringThreshold设定,默认值为15。

② 老年代(Old Generation):在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代,该区域中对象存活率高。老年代的垃圾回收(又称Major GC)通常使用“标记-清理”或“标记-整理”算法。整堆包括新生代和老年代的垃圾回收称为Full GC(HotSpot VM里,除了CMS之外,其它能收集老年代的GC都会同时收集整个GC堆,包括新生代)。

③ 永久代(Perm Generation):主要存放元数据,例如Class、Method的元信息,与垃圾回收要回收的Java对象关系不大。相对于新生代和年老代来说,该区域的划分对垃圾回收影响比较小。

在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。

5 方法区
  方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。

HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。

相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。

6 运行时常量池
  运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)

既然运行时常量池时方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。

JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
在这里插入图片描述

7 直接内存
  直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致OutOfMemoryError异常出现。

JDK1.4中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式,它可以直接使用Native函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。

本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。

String类和常量池常见面试题

<think>好的,我需要回答用户关于JVM工作原理和性能调优方法的问题。首先,我得回顾一下JVM的基本结构和工作机制,确保自己理解正确。JVM主要包括类加载子系统、运行时数据区、执行引擎和本地库接口这几个部分。运行时数据区又分为方法区、堆、虚拟机栈、本地方法栈和程序计数器。其中堆是垃圾回收的重点区域,分为新生代和老年代。 接下来是性能调优部分。用户可能想知道具体怎么调整参数,比如堆内存大小、新生代和老年代的比例。常见的调优参数有-Xms、-Xmx设置堆的初始和最大大小,-XX:NewRatio调整新生代和老年代的比例。另外,垃圾回收器的选择也很重要,比如Serial、Parallel、CMS、G1和ZGC,每种都有适用场景。 用户提供的引用中有提到性能调优的步骤,比如监控分析、参数调整、优化代码等。需要结合这些内容,给出系统化的建议。例如,先用监控工具如jstat、VisualVM分析内存使用和GC情况,再根据结果调整参数,可能还需要优化代码减少内存泄漏。 另外,引用里提到的书籍和工具,比如《深入理解Java虚拟机》和Universal JVM GC analyzer,可以作为推荐的参考资料,增强回答的权威性。要注意引用标识的正确添加,比如在提到调优步骤时引用[^1][^2],书籍推荐时引用[^3]。 用户的问题还要求生成相关问题,我需要根据JVM的结构和调优方法,提出几个相关的问题,比如调优步骤、内存模型、垃圾回收算法比较等。确保问题有针对性,帮助用户进一步学习。 最后,检查回答是否符合格式要求:使用中文,数学表达式正确,引用标识自然添加,结构清晰。确保没有使用Markdown,语言口语化,分步骤解释,逻辑连贯。</think>### JVM工作原理与性能调优指南 #### 一、JVM核心工作原理 1. **类加载机制** JVM通过类加载子系统(ClassLoader)加载字节码文件(.class),包含以下步骤: - **加载**:查找并读取类文件 - **验证**:确保字节码符合规范 - **准备**:为静态变量分配内存 - **解析**:将符号引用转为直接引用 - **初始化**:执行静态代码块和赋值操作 2. **运行时数据区** - **方法区**:存储类信息、常量池等(JDK 8后由元空间实现) - **堆**:对象实例的存储区域(分为新生代和老年代) - **虚拟机栈**:存储方法调用的栈帧(局部变量、操作数栈等) - **本地方法栈**:服务于Native方法 - **程序计数器**:记录当前线程执行位置 3. **垃圾回收(GC)** - **分代收集**:新生代使用复制算法(如Minor GC),老年代使用标记-清除或标记-整理算法(如Full GC) - **可达性分析**:通过GC Roots(如栈中引用、静态变量)判断对象存活 4. **执行引擎** 将字节码解释为机器码或通过即时编译器(JIT)优化热点代码[^3]。 --- #### 二、JVM性能调优方法 1. **内存调优** - **堆内存设置**:通过`-Xms`(初始堆大小)、`-Xmx`(最大堆大小)避免频繁扩容 - **新生代与老年代比例**:`-XX:NewRatio=3`表示老年代是新生代的3倍 - **元空间限制**:`-XX:MaxMetaspaceSize`防止元空间无限增长 2. **垃圾回收器选择** | 回收器 | 适用场景 | 参数示例 | |-------------------|-----------------------------------|-------------------------| | Serial GC | 单线程、小内存应用 | `-XX:+UseSerialGC` | | Parallel GC | 多核CPU、高吞吐量 | `-XX:+UseParallelGC` | | CMS GC | 低延迟应用 | `-XX:+UseConcMarkSweepGC` | | G1 GC | 大堆内存、可预测停顿 | `-XX:+UseG1GC` | | ZGC | 超低延迟(JDK 15+) | `-XX:+UseZGC` | 3. **监控与分析工具** - **命令行工具**:`jstat`(GC统计)、`jmap`(堆转储) - **可视化工具**:VisualVM、JConsole - **GC日志分析**:添加`-Xloggc:<file>`记录日志,使用工具如[Universal JVM GC analyzer][^4]解析 4. **常见调优策略** - **减少Full GC频率**:优化对象生命周期,避免内存泄漏 - **调整线程栈大小**:`-Xss`防止栈溢出或内存浪费 - **禁用偏向锁**:高并发场景下使用`-XX:-UseBiasedLocking` --- #### 三、调优步骤示例 1. **监控基线性能**:使用`jstat -gcutil <pid> 1000`观察GC频率 2. **分析内存泄漏**:通过`jmap -histo:live <pid>`查看对象分布 3. **调整参数**:若Minor GC频繁,增加`-Xmn`(新生代大小) 4. **验证优化效果**:对比调优前后的吞吐量和停顿时间[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值