JVM内存分配回收入门(一)
前沿
菜鸟Android程序员入门jvm虚拟机,如有错误,欢迎批评指正。我们都知道Java的内存管理是交由jvm来管理的,这很方便,但是也带来了一些困扰,一旦出现内存泄漏和内存溢出方面的问题,如果不了解jvm虚拟机是如何分配回收内存的,那排除bug将会是非常困难的工作。
为什么要学习jvm内存管理?
我觉得起码学个入门级的吧,知道平时代码中那些对象,变量,方法在内存中是如何创建和回收的。我负责任的告诉你,真的不是为了装逼,而是为了解决实际问题。
我的理由如下:
1、解决常见的内存泄漏和oom;
2、内存优化;
3、在开发中写出更健壮的程序,减少一些内存bug或者说增加内存负担的问题;
4、现阶段的面试大概率会问。
关于运行时数据区
在JVM加载class文件的时候,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运行时数据区)
,也就是我们常说的JVM内存。我们常说的内存管理(内存分配和回收)就是针对这段空间进行管理。
根据 JVM 规范,JVM 内存共分为方法区
、堆
、虚拟机栈
、本地方法栈
、程序计数器
五个部分。其中方法区
和堆
是被所有线程共享的一块内存区域,在虚拟机启动时创建,生命周期与虚拟机相同;而后面3个则是线程私有,生命周期与线程相同。
注意:这是 JVM 规范,不同虚拟机,不同版本在实现上虽然有所不同,但总体上是按照这个规范来做的
下面简单介绍一下这5个数据区域。
-
方法区
:是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量(final )、静态变量(static)、即时编译器编译后的代码等数据; -
堆(Heap)
:也是各个线程共享的内存区域,它的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存;它是jvm虚拟机管理的内存中最大的一块; -
虚拟机栈(VM Stack)
:线程私有,生命周期与线程相同,使用连续的内存空间来存储数据;栈描述的是Java 方法执行的内存模型;每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息;方法调用时会入栈,方法执行完成会出栈; -
本地方法栈
:与虚拟机栈(VM Stack)的作用非常相似,区别在于Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务(是系统层面的); -
程序计数器
:线程私有,占用内存小,生命周期与线程相同,大致为字节码行号指示器,记录的是正在执行的虚拟机字节码指令的地址。
这里重点看一下方法区
、堆(Heap)
、虚拟机栈(VM Stack)
;而在平时的应用开发中做的内存优化又主要是堆(Heap)
,这下工作就变得没那么复杂了。
下面通过一个例子来讲解内存分配:
public class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
int num = 1; //1
Person person = new Person("张三", 20); //2
change1(num); //3
change2(person); //4
}
private static void change1(int n) {
n = 11;
}
private static void change2(Person p) {
p = new Person("李四", 21); //5
}
}
内容很简单,在java的main()
方法中定义了一个变量num,创建了一个对象Person,然后再调用change1(int n)
、change2(Person p)
方法
运行过程
首先这段java程序是从main()
方法作为入口的,
1、当运行到1时,会把局部变量num
加载到栈内存;
2、当运行到2时,jvm会先把Person .class
加载到方法区,方法区里面存放的就是class的数据结构;然后在堆内存中开辟一个空间来存储Person的实例,且在堆内存的地址为0x001
;接着把引用person
放在栈中,这个引用指向的就是0x001
;
3、当调用change1(int n)
时,因为n是局部变量,所以会把n放到栈中,并且给n赋值,当方法执行完毕的时候,会立即释放n所占用的栈空间;
4、 当调用change2(Person p)
时,因为p是局部变量,所以会把p放到栈中,由于是引用类型的变量,p
和person
指向堆中的同一个对象;
5、当执行到5时,会在堆内存中重新创建一个新的Person
对象,然后p
的指针地址不再指向之前的0x001
,而是指向新的对象的地址0x002
。change2(Person p)
方法执行完后会立即释放p
的栈内存,但是在堆内存中的对象不会立即回收,而是等待gc自动回收。
总结
1、栈中存放的是局部变量(包括引用),如果是普通变量的话存放的是值;如果是引用的话,存放的是堆内存的指针;在使用时入栈,用完就立即出栈;
2、堆内存存放的就是创建的对象/实例,堆是内存中比较大的一块区域,是垃圾回收器管理的主要区域,因此很多时候也被称做“GC 堆”;
3、栈和堆的内存释放不是同步的。方法结束,栈中的局部变量立即销毁,但是堆中对象不一定马上销毁,而是等垃圾回收扫描时才可以被销毁,具体的算法后面章节再讲;
4、创建一个对象时,类的成员变量会存储在堆内存,但是成员方法不会,你想想如果一个类创建了多个对象 ,每个方法都入栈,那多占内存啊?类的方法是该类的所有对象共享的,只有一套,在调用方法时,会通过堆内存中的实例找到方法区中相对应的方法。