Java内存区域栈、堆、方法区如何划分

1. 概述

Java程序一般不在乎内存是怎么分配的,因为有虚拟机帮忙管理,但偶尔会出现奇怪的问题或者内存溢出报错,这些都非常头疼,如果不了解虚拟机是怎么管理内存的,那排查错误将会非常艰难。

如果仅仅把内存区域认为对象放入堆内存,局部变量放入栈内存略微粗糙,因为实际JVM分配数据比这个要复杂的多。

JVM所管理的内存主要包含运行时数据区域为栈、堆、方法区和程序计数器,如图所示:

Java内存区域栈、堆、方法区如何划分

2. 程序计数器

因为Java是多线程来执行的,线程切换后要恢复到原理执行的位置,程序计数器是为这个来服务的。

这样每个线程都有一个独立的程序计数器,主要作用是将通过计数器的值选取下一条需要执行的字节码指令。

3. 栈

Java栈也是线程私有的,每个方法被执行的时候会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。

局部变量表存放了Java的基本数据类型(boolean、byte、char、short、int、float、long、double)、以及对象引用(reference类型),如果把对象类比成电视机,那么reference类型就相当于遥控器,你要打开电视机,只需要点击遥控器的开关就可以。

注意,Java中并不存在string基础类型,仅有String类

栈内存中并没有自动垃圾回收(GC),只存在入栈和出栈,入栈则分配内存,出栈则释放内存。

4. 堆

Java堆被所有线程共享,,和C,C++不一样,这一块由JVM管理内存自动垃圾回收,存放对象实例。

堆内存是可以动态大小的,物理上可以不连续,但逻辑上连续的即可,如果堆上面的内存没有空间分配,会报OutOfMemoryError异常,如果遇到这种错误,通常需要使用工具Dump出当前的内存快照以便事后分析。

5. 方法区

方法区和堆一样是内存共享的,主要存放类信息、常量 (static final)、静态变量等数据,垃圾回收在该区域同样会存在

运行时常量池也存放在方法区,运行时常量池包含字符串常量池,Java性能好的一方面,就是把字符串单独出来处理,这样做有几个好处:

1. 在编译期间把字符串就放到常量池中

2. 同样的字符串只有一份,防止到处过多占用内存

6. 对象访问

简单举例:

Book book = new Book();

book会作为一个reference类型存在栈区,new Book() 为实例的结构化内存(包含各个实例字段的数据)反映在堆区,另外,在Java堆中还必须包含能查找到此类型(如对象类型,父类,实现的接口,方法等)的地址信息,这些类型的数据则存储在方法区内。如图所示:

Java内存区域栈、堆、方法区如何划分

7. 小结

堆和栈的概念可以说是Java开发底层的问题,是基本的数据结构,对于了解运行时数据区有很大的意义。

当了解运行时数据区的原理是,就会了解为什么两个字符串对象不能用“==”来比较,为什么静态变量在非线程安全的等这些问题有很大的意义

最后简单的一个小例子,看各个信息是划分到哪个存储区域

Java内存区域栈、堆、方法区如何划分


分享到:


相關文章: