Java基础知识总结(二):JVM内存结构与变量生命周期

Java基础知识总结(二):JVM内存结构与变量生命周期

很多Java开发者刚开始学习时都会有这样的疑问:

  • 对象到底存在哪里?
  • new出来的对象什么时候被回收?
  • 局部变量和成员变量有什么区别?
  • static变量到底存放在哪里?

这些问题都与 JVM 内存结构密切相关。

本文将从 JVM 运行时数据区出发,深入理解 Java 程序运行过程中内存是如何分配和管理的。


目录

  • JVM内存结构概述
  • 程序计数器
  • Java虚拟机栈
  • 本地方法栈
  • 堆内存
  • 方法区
  • 对象创建过程
  • 成员变量与局部变量区别
  • static变量存储位置
  • 生命周期分析
  • 常见面试题
  • 总结

一、JVM内存结构概述

Java程序运行时,JVM会将内存划分为多个区域。

主要包括:

text 复制代码
┌──────────────┐
│ 程序计数器    │
├──────────────┤
│ Java虚拟机栈 │
├──────────────┤
│ 本地方法栈    │
├──────────────┤
│     堆       │
├──────────────┤
│   方法区      │
└──────────────┘

通常面试中提到的JVM内存结构,指的就是这些运行时数据区。


二、程序计数器(Program Counter Register)

程序计数器是一块较小的内存空间。

作用:

记录当前线程正在执行的字节码指令地址。

简单理解:

text 复制代码
程序执行到哪一行代码了
程序计数器负责记录

例如:

java 复制代码
public static void main(String[] args) {

    int a = 10;

    int b = 20;

    int c = a + b;

    System.out.println(c);
}

JVM执行时:

text 复制代码
执行第一句
记录位置

执行第二句
更新位置

执行第三句
更新位置

程序计数器就是一个"导航仪"。


为什么需要程序计数器?

Java支持:

  • 多线程
  • 线程切换

例如:

text 复制代码
线程A执行一半

CPU切换到线程B

线程B执行完

再切换回线程A

程序计数器能够帮助线程恢复到之前执行的位置。


特点

程序计数器:

text 复制代码
线程私有

每个线程都有自己的程序计数器。

也是 JVM 唯一不会发生 OutOfMemoryError 的区域。


三、Java虚拟机栈(Stack)

栈是面试中最常见的知识点之一。

作用:

保存方法运行时产生的数据。


方法调用过程

例如:

java 复制代码
public static void main(String[] args) {

    test();
}

public static void test() {

    int num = 10;

}

执行过程:

text 复制代码
main() 入栈

↓

test() 入栈

↓

test()执行结束

↓

test()出栈

↓

main()结束

↓

main()出栈

图示:

text 复制代码
┌───────┐
│ test  │
├───────┤
│ main  │
└───────┘

栈中存放什么?

主要存放:

text 复制代码
局部变量

方法参数

方法调用信息

返回地址

例如:

java 复制代码
public void test() {

    int a = 10;

    String name = "Tom";

}

变量:

java 复制代码
a
name

都存放在栈中。


栈的特点

先进后出(FILO)

text 复制代码
Last In First Out

后进入的方法先执行完毕。


四、本地方法栈(Native Method Stack)

本地方法栈专门服务于:

java 复制代码
native

修饰的方法。

例如:

java 复制代码
public native void start0();

JDK底层很多功能都依赖native方法实现。

例如:

  • 操作系统交互
  • 文件系统
  • 网络通信
  • 硬件访问

为什么需要本地方法?

Java本身无法直接操作:

text 复制代码
CPU

内存

磁盘

网卡

因此需要借助:

text 复制代码
C

C++

编写的本地方法实现。


五、堆(Heap)

堆是 JVM 中最大的一块内存区域。

作用:

存储对象和数组。


对象创建过程

例如:

java 复制代码
Student stu = new Student();

执行时:

第一步

栈中创建引用变量

text 复制代码
stu

第二步

堆中创建对象

text 复制代码
Student对象

第三步

引用指向对象

text 复制代码
stu → Student对象

图示:

text 复制代码
栈(Stack)

stu
 │
 ▼

堆(Heap)

Student对象

数组也存放在堆中

例如:

java 复制代码
int[] nums = {1,2,3};

图示:

text 复制代码
栈

nums
 │
 ▼

堆

[1,2,3]

堆中的默认值

对象创建后会自动初始化。

例如:

java 复制代码
class User {

    int age;

    boolean flag;

}

创建对象:

java 复制代码
User user = new User();

默认值:

java 复制代码
age = 0

flag = false

为什么堆需要GC?

因为:

java 复制代码
new User();

new User();

new User();

会不断创建对象。

如果不回收:

text 复制代码
内存耗尽

因此 JVM 提供:

text 复制代码
Garbage Collection

垃圾回收机制。


六、方法区(Method Area)

方法区用于保存:

text 复制代码
类信息

方法信息

常量

静态变量

运行时常量池

例如:

java 复制代码
public class User {

    private String name;

    public void login() {

    }

}

类加载后:

text 复制代码
User类的信息

login方法的信息

都会进入方法区。


方法区的发展

JDK6

text 复制代码
永久代(PermGen)

属于方法区实现。


JDK8

永久代被移除。

改为:

text 复制代码
元空间(MetaSpace)

使用本地内存。


七、对象创建全过程

例如:

java 复制代码
User user = new User();

执行过程:

1. 类加载

如果User类未加载:

text 复制代码
加载User.class

进入方法区。


2. 分配内存

在堆中创建对象。


3. 默认初始化

例如:

java 复制代码
int age;

初始化为:

java 复制代码
0

4. 调用构造方法

java 复制代码
public User() {

}

执行构造方法。


5. 返回对象地址

引用变量保存地址。

text 复制代码
user → 对象

八、成员变量与局部变量区别

这是面试高频题。


定义位置不同

成员变量:

java 复制代码
public class User {

    int age;

}

定义在类中、方法外。


局部变量:

java 复制代码
public void test() {

    int age = 18;

}

定义在方法内部。


默认值不同

成员变量:

有默认值。

例如:

java 复制代码
int age;

默认:

java 复制代码
0

局部变量:

没有默认值。

必须先赋值再使用。

错误示例:

java 复制代码
public void test() {

    int age;

    System.out.println(age);
}

编译失败。


作用域不同

成员变量:

text 复制代码
整个对象

都可以访问。


局部变量:

text 复制代码
仅当前方法

有效。


存储位置不同

成员变量:

text 复制代码

跟随对象存在。


局部变量:

text 复制代码

跟随方法存在。


生命周期不同

成员变量:

text 复制代码
对象创建
↓
对象销毁

局部变量:

text 复制代码
方法开始
↓
方法结束

对比表

对比项 成员变量 局部变量
定义位置 类中 方法中
默认值
存储位置
生命周期 对象生命周期 方法生命周期
作用域 整个类 当前方法

九、static变量存储位置

很多初学者认为:

text 复制代码
static变量在堆中

实际上需要区分JDK版本。


JDK6

text 复制代码
永久代(方法区)

JDK8+

text 复制代码
元空间保存类信息

静态变量实际存储在堆中

因此现在更准确的说法:

text 复制代码
静态变量属于类

生命周期与类一致

对象共享同一份静态变量

示例:

java 复制代码
public class User {

    public static int count = 0;

}

无论创建多少对象:

java 复制代码
new User();

new User();

new User();

都共享:

java 复制代码
count

十、常见面试题

面试题1

对象存放在哪里?

答案:

text 复制代码
堆(Heap)

面试题2

局部变量存放在哪里?

答案:

text 复制代码
Java虚拟机栈

面试题3

数组存放在哪里?

答案:

text 复制代码

面试题4

方法调用为什么要入栈?

答案:

text 复制代码
保存方法运行状态

记录局部变量

记录返回地址

面试题5

成员变量和局部变量有什么区别?

答案:

text 复制代码
定义位置不同

默认值不同

作用域不同

生命周期不同

存储位置不同

面试题6

为什么Java需要垃圾回收?

答案:

text 复制代码
对象不断创建

如果不回收

最终导致内存耗尽

总结

本文介绍了 JVM 运行时数据区的重要组成部分:

  • 程序计数器
  • Java虚拟机栈
  • 本地方法栈
  • 方法区

同时分析了:

  • 对象创建过程
  • 成员变量与局部变量区别
  • static变量存储位置
  • 生命周期差异

掌握这些知识后,你将能够更好地理解:

  • 对象为什么会被回收
  • 内存溢出的原因
  • JVM性能调优基础
  • Java面试中的内存相关问题
相关推荐
我是大猴子1 小时前
连接池+虚拟线程
java
技术小结-李爽1 小时前
【工具】如何认识Maven
java·maven
石山代码1 小时前
Python 进阶学习指南
开发语言·python
小碗羊肉1 小时前
【RabbitMQ高级】如何保证消息的可靠性?
java·rabbitmq·java-rabbitmq
xiaoshuaishuai82 小时前
C# 多线程之间对比
java·开发语言·c#
越努力越幸运662 小时前
Java 无需 Office 环境实现 Word 转 HTML
java
用户8176967132352 小时前
Java OOM 排查完整指南:从告警到根因,MAT 堆分析全流程实战
java
要开心吖ZSH3 小时前
AI医疗分诊与健康咨询助手agent开发——(0)项目背景与概要
java·ai·agent·健康医疗·rag
ZC跨境爬虫3 小时前
跟着 MDN 学JavaScript day_9:字符串方法实战挑战与解题思路
开发语言·前端·javascript