Java内存分析(1)——内存分区和分配过程
我们已经习惯于写出类似Object obj=new Object();型的语句,然而背后究竟发生了什么?从JVM内存结构分析更有助于加深理解记忆。下面试着举例说明:JVM内存分区:如上图所示,JVM主要分为以上几块:程序计数器,本地方法栈,虚拟机栈,堆和方法区。稍微粗糙一些得分法是JVM分为栈和堆,栈包括虚拟机栈,本地方法栈,程序计数器,堆分为堆和方法区。需要说明的是这里所说的Java内存和硬件上
我们已经习惯于写出类似Object obj=new Object();
型的语句,然而背后究竟发生了什么?从JVM内存结构分析更有助于加深理解记忆。下面试着举例说明:
JVM内存分区:
如上图所示,JVM主要分为以上几块:程序计数器,本地方法栈,虚拟机栈,堆和方法区。稍微粗糙一些得分法是JVM分为栈和堆,栈包括虚拟机栈,本地方法栈,程序计数器,堆分为堆和方法区。需要说明的是这里所说的Java内存和硬件上的内存并不能完全对应,Java内存还可能包含CPU的寄存器,高速缓存等。
程序计数器
程序计数器是用于存储每个线程下一步将执行的JVM指令,如该方法为native的,则程序计数器中不存储任何信息。
本地方法栈
众所周知,Java的底层是C实现的,所有调用的C代码都被称为本地方法(native method)。JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。
虚拟机栈
JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。
以上三块是伴随着线程同生共死的,生命周期由编译器控制。
堆 Java Heap
它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Java Heap是线程共享的。Heap中的对象的内存需要等待GC进行回收。
方法区
方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息。方法区域也是全局共享的,在一定的条件下它也会被GC。
其中,方法区中有一个运行常量池:存放的为类中的固定的常量信息、方法和Field的引用信息等。
内存分配过程
了解了JVM的内存分区,接下来就以实例分析到底变量都存放在哪里。由于下面的例子和本地方法栈,程序计数器关系不大,所以暂时略去。
一段司空见惯的代码:
public class student{
String name;
Integer age;
String score
public void study(){
system.out.println("I am studing");
}
public void sayHello(){
system.out.println("Hello");
}
}
public class computer(){
String brand;
String speed;
}
public class test(){
public static void main(String args[]){
Student stu1 =new Student();
stu1.name="wang";
stu1.age=24;
Student stu2=new Student();
stu2.name="li";
stu2.age=23;
stu1.computer.brand="Lenovo";
stu1.speed="2.5GHz"
}
}
内存存储详情图:
由图中所示:
栈存储的是局部变量,如上述代码中的stu1,stu2。
堆的heap区存储的是new创建出的变量。
堆的方法区存储的是类的方法(动态,静态),变量(动态,静态),常量。
我们知道:Java中,除了基本数据类型,都是引用类型,也就是说这些数据类型存的都是引用地址,直至一个接一个的引用,最终指向到基本类型,基本类型存储的才是值的本身。
这样就可以理解图中的所有箭头,一条箭头代表一个引用,箭尾是引用的地址,箭尖是被引用的变量。
由此,我们来剖析代码在内存中是如何实现的:
1,首先JRE加载了编译好的class文件,将相关类的方法,变量,常量都放到了类区。然后开始执行语句。
2,执行第一句语句Student stu1=new Student();
按照Java由右向左逐步执行的机制,首先是运行new Student()在Java Heap中开辟出一个可以存储Student类所有属性和方法的引用的连续空间。方法都指向了方法区的对应方法,而属性在未赋值的情况下都是null或0。
之后是执行Student stu1在栈空间内分配一个空间存储一个存储Student类型变量引用的空间。
最后运行等号,等号就是将变量区中变量的地址赋值给栈中的变量,即建立了引用关系。
3, 执行第二句:stu1.name="wang";
“wang”是一个已经存在于方法区的常量,stu1.name是变量区中的一个空变量,等号将两者连接,将“wang”的地址赋给了stu1.name。
以此类推,以下都如图所示。被赋值的变量存储了相应的地址,未赋值的保持为空。
4,值得注意的是,Java Heap的变量也可以相互引用,例如stu1.computer
就是从一个变量引用到另一个变量。
静态变量与方法与与一般变量和方法所不同的是,在用new关键词创建类对象时,这个类里面的static变量和方法不会被初始化,栈中的对象直接对它们进行引用。因此,静态方法无需实例化即可使用,静态变量被类的所有实例所共享。
顺带提一句垃圾回收机制(garbage collection),Java采用的是自动的垃圾回收机制,当虚拟机,发现某一个变量在之后不会被使用到,例如图中stu1已经用完了,就会把stu1引用的一整块在变量区的内容释放掉。
垃圾回收是一个后台线程,在不知不觉中帮我们管理着内存,好处是解放了程序员,编写程序少操了一份心,坏处是相比C++这种需要主动释放内存的语言少了一点灵活性。Java可以使用System.gc()来提醒虚拟机可以考虑进行一下垃圾回收,但是系统可能不一定听你的,你可以建议,但做不做由它,Java这种脾气在很多地方都有体现。
总结一下,
Java内存管理分为两大块,栈和堆,堆又分为变量区和类区;
栈引用变量区,变量区引用类区,变量区还相互引用,类区内部也相互引用;
Java的有自动的垃圾回收机制,可以将不再被使用的变量释放。
核心:一定要深入理解Java的引用机制。
更多推荐
所有评论(0)