JVM 内存分配策略
在分析JVM 内存分配策略之前先介绍一下通常情况下操作系统都是采用哪些策
略来分配内存的。
通常的内存分配策略
在操作系统中将内存分配策略分为三种,分别是:
◎ 静态内存分配;
◎ 栈内存分配;
◎ 堆内存分配。
静态内存分配是指在程序编译时就能确定每个数据在运行时的存储空间需求,因此在
编译时就可以给它们分配固定的内存空间。这种分配策略不允许在程序代码中有可变数据
结构(如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编
译程序无法计算准确的存储空间需求。
栈式内存分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的。和静
态内存分配相反,在栈式内存方案中,程序对数据区的需求在编译时是完全未知的,只有
到运行时才能知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的
数据区大小才能够为其分配内存。和我们所熟知的数据结构中的栈一样,栈式内存分配按
照先进后出的原则进行分配。
在编写程序时除了在编译时能确定数据的存储空间和在程序入口处能知道存储空间
外,还有一种情况就是当程序真正运行到相应代码时才会知道空间大小,在这种情况下我
们就需要堆这种分配策略。
这几种内存分配策略中,很明显堆分配策略是最自由的,但是这种分配策略对操作系
统和内存管理程序来说是一种挑战。另外,这个动态的内存分配是在程序运行时才执行的,
它的运行效率也是比较差的。
Java 中的内存分配详解
从前面的 JVM 内存结构的分析我们可知,JVM 内存分配主要基于两种,分别是堆和
栈。先来说说Java 栈是如何分配的。
Java 栈的分配是和线程绑定在一起的,当我们创建一个线程时,很显然,JVM 就会为
这个线程创建一个新的Java 栈,一个线程的方法的调用和返回对应于这个Java 栈的压栈
和出栈。当线程激活一个Java 方法时,JVM 就会在线程的Java 堆栈里新压入一个帧,这
个帧自然成了当前帧。在此方法执行期间,这个帧将用来保存参数、局部变量、中间计算
过程和其他数据。
栈中主要存放一些基本类型的变量数据(int、short、long、byte、float、double、boolean、
char)和对象句柄(引用)。存取速度比堆要快,仅次于寄存器,栈数据可以共享。缺点是,
存在栈中的数据大小与生存期必须是确定的,这也导致缺乏了其灵活性。
如下面这段代码:
public void stack(String[] arg) {
String str = "junshan";
if (str.equals("junshan")) {
int i = 3;
while (i > 0) {
long j = 1;
i--;
}
} else {
char b = 'a';
System.out.println(b);
}
}
这段代码的stack 方法中定义了多个变量,这些变量在运行时需要存储空间,同时在
执行指令时JVM 也需要知道操作栈的大小,这些数据都会在Javac 编译这段代码时就已经
确定,下面是这个方法对应的class 字节码:
public void stack(java.lang.String[]);
Code:
Stack=2, Locals=6, Args_size=2
0: ldc #3; //String junshan
2: astore_2
3: aload_2
4: ldc #3; //String junshan
6: invokevirtual #4; //Method java/lang/String.equals:(Ljava/lang/
Object;)Z
9: ifeq 30
12: iconst_3
13: istore_3
14: iload_3
15: ifle 27
18: lconst_1
19: lstore 4
21: iinc 3, -1
24: goto 14
27: goto 40
30: bipush 97
32: istore_3
33: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
36: iload_3
37: invokevirtual #6; //Method java/io/PrintStream.println:(C)V
40: return
LineNumberTable:
line 15: 0
line 16: 3
line 17: 12
line 18: 14
line 19: 18
line 20: 21
line 21: 24
line 22: 27
line 23: 30
line 24: 33
line 26: 40
LocalVariableTable:
Start Length Slot Name Signature
21 3 4 j J
14 13 3 i I
33 7 3 b C
0 41 0 this Lheap/StackSize;
0 41 1 arg [Ljava/lang/String;
3 38 2 str Ljava/lang/String;
在这个方法的attribute 中就已经知道了stack 和local variable 的大小,分别是2 和6。
还有一点不得不提,就是这里的大小指定的是最大值,为什么是最大值呢?因为JVM 在
真正执行时分配的stack 和local variable 的空间是可以共用的。举例来说,上面的6 个local
variable 除去变量0 是this 指针外,其他5 个都是在这个方法中定义的,这6 个变量需要
的Slot 是1+1+1+1+2+1=7,但是实际上使用的Slot 只有4 个,这是因为不同的变量作用
范围如果没有重合,Slot 则可以重复使用。
每个 Java 应用都唯一对应一个JVM 实例,每个实例唯一对应一个堆。应用程序在运
行中所创建的所有类实例或数组都放在这个堆中,并由应用程序所有的线程共享。在Java
中分配堆内存是自动初始化的,所有对象的存储空间都是在堆中分配的,但是这个对象的
引用却是在堆栈中分配的。也就是说在建立一个对象时两个地方都分配内存,在堆中分配
的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)
而已。
Java 的堆是一个运行时数据区, 这些对象通过new 、newarray 、anewarray 和
multianewarray 等指令建立,它们不需要程序代码来显式地释放。堆是由垃圾回收来负责
的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运
行时动态分配内存的,Java 的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由
于要在运行时动态分配内存,存取速度较慢。
如下代码描述新对象是如何在堆上分配内存的:
public static void main(String[] args) {
String str = new String("hello world!") ;
}
上面的代码创建了一个String 对象,这个String 对象将会在堆上分配内存,JVM 创建
对象的字节码指令如下:
public static void main(java.lang.String[]);
Code:
Stack=3, Locals=2, Args_size=1
0: new #7; //class java/lang/String
3: dup
4: ldc #8; //String hello world!
6: invokespecial #9; //Method java/lang/String."<init>":(Ljava/lang/
String;)V
9: astore_1
10: return
LineNumberTable:
line 29: 0
line 35: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String;
10 1 1 str Ljava/lang/String;
先执行new 指令,这个new 指令根据后面的16 位的“#7”常量池索引创建指定类型
的对象,而该#7 索引所指向的入口类型首先必须是CONSTANT_Class_info,也就是它必
须是类类型,然后JVM 会为这个类的新对象分配一个空间,这个新对象的属性值都设置
为默认值,最后将执行这个新对象的objectref 引用压入栈顶。
new 指令执行完成后,得到的对象还没有初始化,所以这个新对象并没有创建完成,
这个对象的引用在这时不应该赋值给str 变量,而应该接下去就调用这个类的构造函数初
始化类,这时就必须将 objectref 引用复制一份,在新对象初始化完成后再将这个引用赋
值给本地变量。调用构造函数是通过invokespecial 指令完成的,构造函数如果有参数要传
递,则先将参数压栈。构造函数执行完成后再将objectref 的对象引用赋值给本地变量1,
这样一个新对象才创建完成。
上面的内存分配策略定义从编译原理的教材中总结而来,除静态内存分配之外,都显
得很呆板和难以理解,下面撇开静态内存分配,集中比较堆和栈。
从堆和栈的功能和作用来通俗地比较,堆主要用来存放对象,栈主要用来执行程序,
这种不同主要是由堆和栈的特点决定的。
在编程中,如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量、形
式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,就
好像工厂中的传送带一样,栈指针会自动指引你到放东西的位置,你所要做的只是把东西
放下来就行。在退出函数时,修改栈指针就可以把栈中的内容销毁。这样的模式速度最
快,当然要用来运行程序了。需要注意的是,在分配时,如为一个即将要调用的程序模块
分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程序运行时进行的,
但是分配的大小是确定的、不变的,而这个“大小多少”是在编译时确定的,而不是在运
行时。
堆在应用程序运行时请求操作系统给自己分配内存,由于操作系统管理内存分配,所
以在分配和销毁时都要占用时间,因此用堆的效率非常低。但是堆的优点在于,编译器不
必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长时间,因此,
用堆保存数据时会得到更大的灵活性。事实上,由于面向对象的多态性,堆内存分配是必
不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定。在C++
中,要求创建一个对象时,只需用 new 命令编制相关的代码即可。执行这些代码时,会
在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价——在堆
里分配存储空间时会花掉更长的时间。
相关推荐
### JVM内存空间分配详解 #### 一、JVM内存模型概览 ...综上所述,理解JVM内存分配机制对于Java开发者来说至关重要,这不仅有助于编写高效、稳定的代码,还能在遇到性能瓶颈时快速定位问题并进行优化。
本资料总结主要关注JVM内存分配及其运行原理,这对于理解和优化Java应用程序的性能至关重要。 1. **JVM内存结构** JVM内存分为几个关键区域:方法区(Method Area)、堆(Heap)、栈(Stack)、程序计数器(PC ...
JVM 内存分配与垃圾回收详解 Java 虚拟机(JVM)是 Java 语言的 runtime 环境,它提供了一个平台独立的方式来执行 Java 字节码。JVM 内存分配与垃圾回收是 JVM 中两个非常重要的概念,本文将对 JVM 内存分配与垃圾...
Java JVM(Java虚拟机)内存分配与调优是Java开发者必须掌握的重要技能,它涉及到程序的性能优化和稳定性。在Java应用中,JVM扮演着至关重要的角色,它负责解析字节码、管理内存以及执行线程等。本文将深入探讨JVM...
本文主要围绕JVM内存区域的分配策略,尤其是对象在新生代(Young Generation)的Eden区分配,以及大对象直接进入老年代(Tenured Generation)的情况进行详细解释,并通过实例分析Minor GC和Full GC的区别。...
JVM内存分配策略基于对象的生命周期,新生代采用复制算法,老年代采用标记-压缩或标记-整理算法。内存分配的策略会影响垃圾收集的效率和程序的响应速度。 ### GC(垃圾回收) 垃圾回收是JVM自动管理内存的关键特性...
堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,但缺点是要在运行时动态分配内存,存取速度较慢。 JVM 如何设置虚拟内存?在 JVM 中,如果 98%的时间是用于 GC 且可用的 Heap size 不足 2%的时候...
《Jvm内存分配(7)》这篇博文主要探讨的是Java虚拟机(JVM)中的内存管理,特别是关于内存分配的相关知识。...开发者需要关注对象生命周期、内存分配策略以及垃圾收集过程,以便做出更明智的设计决策。
1. **内存分配和泄漏检测**:JProfiler11能详细追踪对象的创建、存活和销毁过程,帮助识别内存泄漏。通过查看对象分配图表,可以发现哪些类或方法在消耗大量内存,从而定位潜在的问题。 2. **垃圾收集分析**:...
在这个资源中,我们将详细讨论 JVM 内存参数的配置和调优,包括 JVM 的结构、内存管理、垃圾回收、堆和非堆内存、内存分配和限制等方面。 JVM 结构 JVM 的结构主要由六个部分组成:JVM API、JVM 内部组件、平台...
JVM内存优化的目的是尽可能让对象都在新生代里分配和回收,避免频繁对老年代进行垃圾回收。以下是一些JVM内存优化的技巧: 1. 设置适当的堆大小:根据实际项目情况,设置适当的堆大小可以避免频繁的垃圾回收。 2. ...
Java虚拟机(JVM)是Java程序运行的基础,它的核心组成部分之一就是垃圾回收器(Garbage Collector, GC),以及内存分配策略。理解这些概念对于优化Java应用性能至关重要。本篇文章将深入探讨JVM的垃圾回收机制以及...
在这份由Sun Microsystems公司出版的《JVM内存管理白皮书》中,我们可以找到关于Java虚拟机(JVM)内存管理的详细介绍和深入分析。这份文档对于想要深入了解JVM工作原理的读者来说是一份宝贵的学习资料。在这份...
23丨如何优化JVM内存分配?.html
MAT能够分析内存中的碎片,这在优化内存分配和减少内存碎片化方面非常有用。 7. **Plots**: MAT提供的图表功能可以帮助可视化内存使用情况随时间的变化,以便更好地理解内存行为。 8. **比较堆转储**: 用户...
通过动态地调整`-Xms`和`-Xmx`参数,可以在运行时根据实际需求优化内存分配,从而提高程序的性能和稳定性。此外,定期检查和分析`heapSize`、`heapMaxSize`和`heapFreeSize`等指标,可以帮助开发者及时发现潜在的...
MAT JVM内存分析工具可以帮助开发者深入理解Java虚拟机(JVM)的内存管理机制,通过分析堆内存快照来识别内存消耗异常的情况。 首先,MAT提供了丰富的视图来帮助用户查看内存状态,例如“概述”视图可以快速了解堆...