![f7edc18dde7347601a0cfbafe99ba433.png](https://i-blog.csdnimg.cn/blog_migrate/2d0ebfa9381d53f891ab2bcfdb4988ad.jpeg)
Java内存分配
一、运行时数据区域
众所周知,程序的运行要把数据和代码装入到内存中运行,所以明白程序执行过程中内存是如何分配的是很有必要的。 Java虚拟机中,根据不同对象的特点,将内存划分为不同的数据区,如下图:
![876dcd427b2143110fde44977e71ba09.png](https://i-blog.csdnimg.cn/blog_migrate/1f5888bd65f9084a5a6faf238a4eff46.jpeg)
其中,方法区和堆区是所有线程共享的区域,随着虚拟机进程的启动而存在。
栈区和PC是线程私有的,随着用户线程的启动和消亡。
1.1 程序计数器(PC)
与OS中PC的作用一样,每个线程私有的PC用来指示下一条要执行的字节码指令。 所以分支、循环等流程的实现都需要依靠PC来实现
1.2 Java虚拟机栈
Java虚拟机栈是线程私有的,生命周期与线程相同。
每个方法在执行的同时创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
每个方法被调用时,入栈,方法结束时,退栈。
局部变量表存放着编译期间可以知道的数据类型(char boolean等)
局部变量表所需的内存空间在编译期间完成分配,也就是说当一个方法分配栈帧的时候,大小是提前确定的,方法运行期间不会改变局部变量表的大小。
1.3 本地方法栈
作用于Java虚拟机栈类似
当执行Java方法时,使用的是Java虚拟机栈
执行本地方法时,使用的是本地方法栈
1.4 堆
Java堆是Java虚拟机所管理的内存最大的一块,所有new出来的对象都在这里分配。
堆是所有线程共享的,所以GC时主要是对堆里面的对象进行回收。
为了GC时方便,将内存的堆分为新生代和老年代,新生代的对象生命周期更新频繁,每次都有大量的对象死去,少量的存活,老年代的对象生命周期比较长,每次存活率都比较高。
同时,堆区在物理内存上是不连续的,只是逻辑上连续。
1.5 方法区
方法区是所有线程共享的。 用来存储加载的类信息、常量、静态变量、即时编译器后的代码等数据。
1.6 运行时常量池
常量池是方法区的一部分
用来存放编译期间或者运行期间产生的常量。
1.7 直接内存
直接内存不属于虚拟机运行时内存的一部分
而是在本机当中剩余内存分配出来的一块空间,通过一些传递数据的手段,从外内存到Java内存进行传递数据。 其大小受到主机内存大小的限制。
二、Java内存分配
根据以上特点,可以总结如下:
- 栈:存放基本数据类型,对象的引用。注意对象的引用是放在栈中,而对象本身是放在堆中(new出来的对象)或者常量池中(字符串常量对象)
- 堆:存放所有new出来的对象
- 常量池:存放字符串常量和基本数据类型的常量(public static final)
- 方法区:放静态常量等(static)
2.1 String常量和引用的分配
如下面代码的内存分配:
String
![0b29148acbca479d854190b4c1c01c3f.png](https://i-blog.csdnimg.cn/blog_migrate/f723ab527138231f3442c2980086aed6.jpeg)
首先字符串常量分配在常量池当中,其引用s1等在栈中
new出来的string对象分配在堆中,其引用s4等在栈中
所以可以看出,s1 s2 s3指向的是同一个字符串常量,而s4 s5 s6则不是
这里有一个细节需要注意,new出来的“Wang”对象,会首先去常量池中查找是否已经有“Wang”对象,如果没有则在常量池创建一个,再复制到堆中。
2.2 基础类型的变量和常量
int
![fff571cbe3499e1f05bb760212352434.png](https://i-blog.csdnimg.cn/blog_migrate/bc9fe226e5b762aa244e84a53ae8ec5b.jpeg)
基础的数据类型直接存放在栈中
stactic final的存放在常量池当中
2.3 成员变量和局部变量
局部变量(包括形式参数)分配在栈中,随着方法的消失而消失
成员变量存储在堆中,有GC负责回收
public
从main方法的执行分析:
1. int date = 9;
date属于局部变量,此时分配在栈中。
2. TestPeople testPeople = new ();
testPeople 属于引用,分配在栈中
对象new TestPeople()分配在堆中
3. testPeople .change(date);
i属于局部变量,存放在栈中,随着change方法的结束而消失,因此,在这里并不会改变传入date的值
4. People wang= new People(7, 7,1970);
wang是对象引用,存放在栈中
对象new People(7,7,1970)存放在堆中
传入的7 7 1970 对应的d m y形参属于局部变量,存放在栈中,当构造方法执行完毕时自动消失
People对象中的day month year为成员变量,分配在堆中
5. main方法结束时
date变量 testPeople wang引用都从栈中消失
此时new出来的TestPeople People没有引用指向他们,等待被GC
![d06238f3293200a4a9c4c3a14501e30a.png](https://i-blog.csdnimg.cn/blog_migrate/2ba1e42f207c6491e6516641a3523b1d.jpeg)
三、内存溢出
3.1 Java堆溢出
根据堆的特性,Java堆主要存放new出来的对象。
所以只要不断的创建对象,并且保持GC Roots到对象之间有可达路径就可以避免这些对象被GC,超过堆的最大容量之后就会造成内存溢出(OutOfMemoryError)。
import
3.2 栈溢出
我们知道栈分配的是局部变量和每一个方法的栈帧,所以不断地调用方法,可以造成StackOverflowError
public
3.3 方法区和常量池溢出
常量区存放字符串常量等,所以这我们的思想就是在常量池中不断产生字符串常量。
这里我们借助String.intern()方法:如果字符串常量池中已经包含一个此String对象的字符串,则返回常量池中这个对象;否则,将此String对象添加到常量池中并返回其引用。
import