深入理解JVM-java内存区域与内存溢出异常
1.内存模型概述
2.运行时数据区
2.1.程序计数器
理解:
1.什么是程序计数器
2.线程私有还是共享
引入难点:
理解什么是 native方法
简单地讲,一个Native Method就是一个java调用非java代码的接口。
一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。
这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数。
"A native method is a Java method whose implementation is provided by non-java code."
在定义一个native method时,并不提供实现体(有些像定义一个java interface),因为其实现体是由非java语言在外面实现的。,下面给了一个示例:
public class IHaveNatives
{
native public void Native1( int x ) ;
native static public long Native2() ;
native synchronized private float Native3( Object o ) ;
native void Native4( int[] ary ) throws Exception ;
}
2.2.java虚拟机栈
https://www.cnblogs.com/newAndHui/p/11168791.html
2.3.本地方法栈
本地方法栈(Native Method Stack) 与虚拟机栈所发挥的作用是相似的,
他们之间的区别是:
1.虚拟机栈为虚拟机执行java方法(也就是字节码)服务;
2.本地方法栈则为虚拟机使用的Native方法服务.
与虚拟机方法一样,本地方法栈区域也会就抛出StackOverflowError 和 OutOfMemoryErrory异常.
2.4.java堆
1. Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,是虚拟机所管理的内存中最大的一块。
此内存区域的唯一目的就是【存放对象实例和数组】,几乎所有的对象实例和数组都在这里分配内存。
2. Java堆是垃圾收集器管理的主要区域,也称为GC 垃圾堆。后面会专门讲解GC算法。
从内存回收的角度看,由于现在收集器基本都采用分代收集算法,所以Java堆可以细分为:新生代、老生代;
从内存分配的角度看,线程共享的Java堆可能划分出多个线程私有的分配缓冲区(TLAB);
不论如何划分,都与存放的内容无关,无论哪个区域,存储的仍然是对象实例和数组。
3. 如果在堆中没有内存完成实例分配,并且堆上也无法再扩展时,将会抛出OutOfMemoryError异常。
4. 内存泄露和内存溢出
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
内存泄漏的累积最终会导致内存溢出!
案例演示:
参看:https://www.cnblogs.com/newAndHui/p/11105956.html 的 2.4节 内存快照分析
2.5.方法区
1. 和堆一样,是各个线程共享的内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
2. Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样 不需要连续的内存和可以选择固定大小或者可扩展之外,还可以选择不实现垃圾回收。
这区域的内存回收目标主要是针对常量池的回收和类型的卸载,
一般而言,这个区域的内存回收比较难以令人满意,尤其是类型的回收,条件相当苛刻,但是这部分区域的内存回收确实是必要的。
3. 很多开发者更愿意把方法区称为“永久代”(Perm Gen)(Permanent Generation)「总是存放不会轻易改变的内容」,
原因:HotSpot的方法区也是采用的GC分代垃圾收集,省去了专门为方法区编写内存管理的工作。
在目前已经发布的JDK 1.7 的HotSpot中,已经把原本放在永久代(方法区)的字符串常量池移至堆中。
4. 运行时常量池(Runtime Constant Pool)是方法区的一部分(已移走)。
5.JDK 1.8 中,已经没有方法区(永久代),而是将方法区直接放在一个与堆不相连的本地内存区域(Native Memory),这个区域被叫做元空间。
6.当无法满足内存分配需求时:将会抛出OutOfMemoryError异常
2.6运行时常量池
1. 运行时常量池是方法区的一个部分
2. Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的字面量和符号引用,
这部分内容(也可以称为 .Class文件中的静态常量池)将在类加载后进入方法区的运行时常量池中存放。
字面量 : 比较接近Java语言层面的常量概念,如文本字符串、声明为final的常量值等。(final修饰的成员变量和类变量!「类变量即静态(成员)变量)」,也就是除final修饰的局部变量。
符号引用 : 属于编译原理方面的概念,包括
1.类和接口的全限定名(即路径,包名+类名)。
2.字段的名称和描述符。
3.方法的名称和描述符。
当虚拟机运行时,需要从常量池获得对应的符号引号,再在类创建或运行时解析、翻译到具体的内存地址之中(直接引用)。
3. 除了保存Class文件中描述的符号引用外,还会把编译出来的直接引用也存储在运行时常量池中。
4. Java语言并不要求常量一定只有编译期才能生成,也就是并非置入Class文件中常量池的内容才能进入方法区运行时常量池,
运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String 类的intern()方法。
5. 当常量池无法再申请到内存时也会抛出OutofMemoryError异常。
2.7直接内存
1.并不是虚拟机运行时数据区的第一部分
2.也不是java虚拟机规范中定义的内存区域
3.直接内存分配不会受到java堆大小的限制,但受到本机总内存限制
4.用处:直接使用Native函数分配该内存
5.抛出OutofMemoryError异常
2.8扩展
成员变量与局部变量
成员变量 : 方法外部,类内部定义的变量;
局部变量 : 方法或语句块内部定义的变量,必须初始化。
- 形参是局部变量,实参则可能是方法中的局部变量或全局变量。
- 栈内存中的局部变量随方法而生,随方法而灭。
- 成员变量存储在堆中的对象里,由垃圾收集器回收。
参考文献:
<<深入理解Java虚拟机:JVM高级特性与最佳实践>>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人