JVM管理数据的方式
JVM有极其强大的内存管理机制,在JAVA中所有申请的资源都由JVM自动清理,而无需手动销毁。
1.JVM管理数据的机制
在JVM中,内存由栈区、堆区、方法区、本地方法栈等组成。
栈区是存储局部变量和方法调用的地址;堆区用于存储对象实例和各种数组,堆区上的任何资源都由JVM自动清除。方法区由于存储各种方法、各种类中的方法以及静态变量等;而本地方法栈主要由于存主函数中的方法。
2.引用数据类型的存储方式
在Java中,引用类型(对象、数组、字符串等)的存储涉及两个部分:引用本身和实际对象。
引用本身的存储位置
存储在Java虚拟机栈的栈帧局部变量表中
包括方法参数和方法内定义的局部变量
局部变量中的引用
对象成员变量中的引用:存储在堆内存中(作为对象实例数据的一部分)
静态变量中的引用:存储在方法区
注意:一切引用类型指向的实际对象(包括普通对象、数组、字符串对象等)都存储在堆内存(Heap)中。但是也有特殊情况:
字符串常量:
基本类型数组:
字符串字面量(如
"hello"
)存储在字符串常量池中Java 7之前位于方法区,Java 7及之后位于堆内存
数组对象本身在堆中,元素如果是基本类型则直接存储值
如int[] arr = new int[10]
,整个数组在堆中
对象数组:
数组对象在堆中,元素存储的是指向其他对象的引用
如String[] arr = new String[10]
,数组在堆中,元素初始为null
下面重点讨论string类的存储方式。JAVA为了提高性能,直接量字符串会缓存在字符串常量池中。对于重复出现的字符串直接量,JVM会优先在常量池中寻找,找到了就直接返回该对象的引用,达到重复使用内存的目的。
String s1 = "123abc";
String s2 = "Hello World";
String s3 = "Hello World";
System.out.println(s1 == s2);
System.out.println(s1 == s3);
String s4=new String("Hello World");
System.out.println(s1 == s4);
在JAVA中,两个字符串做==比较时,比较的是两个字符串的地址而不是内容(如果想比较内容应该调用String类中的equal方法)。所以可以看到输出的结果是false true false。这样就验证了s3的确是复用了s2的"Hello World"。让人意外的是第三个也是false,明明都是"Hello world"怎么这里地址就不同呢?
其实new出来的对象都不会复用s2,而是在堆区新开辟一个"hello"的空间,所以s2和s4的地址必然不相同。可是既然s2和s3共用一个字符串,那万一哪天s2销毁了然后又想访问s3怎么办呢?它之所以敢这样复用,是因为在堆区一切的资源都由JVM来清理,它的底层是用了引用计数来记录有几个复用,当为0时才会自动销毁,这就无需担心能否访问的问题了。
所以整个过程就是,首先s1里面是"123abc",先到字符串常量池中找,没找到就为它新开辟一个string;s2创建的时候也没有,那就新开辟;s3创建的时候找了,在字符串找到了就直接用s2的,而然后就把字符串常量池的那个地址拷贝过来到它的栈内存的地址;s4由于是实例化出来的对象,它就直接在堆区新开辟空间,栈内存中是不存任何东西的。
3.基本类型的存储方式
Java中的基本数据类型的存储方式与引用类型不同,它们的存储位置取决于使用场景。
(1)方法体中的局部变量
存放在JVM栈区
public void method() {int a = 10; // 存储在栈中double b = 0.5; // 存储在栈中
}
(2)对象成员变量中的基本类型
作为对象实例的一部分,里面的基本变量存储在堆中
class A {int x; // 当创建A对象时,x存储在堆中boolean flag; // 存储在堆中
}
(3)静态变量中的基本类型
存储在方法区.静态变量(包括基本类型和引用)的变量名和值直接存储在方法区/元空间,只有一份
下面进行验证:
public class StaticVar {int a;static int b;StaticVar(){a++;b++;}void Show(){System.out.println(a+" "+b);}
}public class StaticDemo {public static void main(String[] args) {StaticVar demo1 = new StaticVar();demo1.Show();StaticVar demo2 = new StaticVar();demo2.Show();StaticVar demo3 = new StaticVar();demo3.Show();}
}
类中定义了一个整形变量a和静态变量b,在构造方法中给a和b分别进行++操作。接着创建了三个实例对象,每创建一个输出a和b的值。运行结果输出了1 1/1 2/1 3。
在进行demo1的实例化时,毫无疑问a、b分别进行了++,所以输出1 1;在进行demo2实例化时,b进行了++而a没有,这是因为demo2独有的a,默认从0开始,而b是静态变量,存在方法区,所以在原来1的基础上再加1;同样的demo3也是a从0加到1,b从2加到3。
基本类型由于直接存储在栈中或作为对象的一部分连续存储有优势:访问速度更快;内存占用更小;没有垃圾回收开销。
4.JVM管理内存的优势和不足
JVM采用自动内存管理(垃圾回收机制),开发者无需手动分配或释放内存。内存分为堆、方法区、虚拟机栈、本地方法栈和程序计数器。堆是垃圾回收的主要区域,存放对象实例;方法区存储类信息、常量等;栈存储局部变量和方法调用。
垃圾回收器通过标记-清除、复制、标记-整理等算法自动回收不再使用的对象。分代收集策略将堆分为新生代和老年代,针对不同生命周期对象采用不同回收算法。
JVM:全自动垃圾回收,无需开发者干预,但可能因GC暂停影响性能。垃圾回收可能导致不可预测的停顿,尤其在Full GC时。严格划分内存区域,对象仅能分配在堆上(逃逸分析优化可能例外)。内存问题相对少见,但GC调优复杂(如选择回收器、调整堆大小)。
而在C++中支持手动内存管理,通过new
/delete
或malloc
/free
显式控制内存分配与释放。内存分为栈、堆、全局/静态存储区等。栈用于局部变量和函数调用,自动管理;堆由程序员手动管理,动态分配的内存需手动释放,否则可能导致内存泄漏。同时为了减轻程序员的负担,在C++11引入智能指针辅助自动管理堆内存,基于RAII(资源获取即初始化)原则减少手动释放的负担,但核心仍依赖开发者控制资源生命周期。
C++:以手动管理为主,智能指针提供部分自动化支持,灵活性高但易出错。无GC开销,实时性更强,但手动管理不当会引发内存泄漏或野指针。对象可分配在栈或堆,栈对象生命周期与作用域绑定,效率更高。需处理内存泄漏、悬垂指针等问题,调试工具(如Valgrind)常被使用。
综上所述,JVM适合开发效率优先、高复杂度的业务系统(如Web应用),避免手动管理风险。C++:适合性能敏感、实时性要求高的场景(如游戏引擎、嵌入式系统),需精细控制内存。
JVM通过这些机制高效地管理数据,同时提供自动内存管理功能,大大减轻了程序员的负担。