JAVA面向对象
JAVA对象内存管理栈和方法区解析
下一章节 栈和方法区解析
请打开此链接https://blog.csdn.net/QQ1043051018/article/details/112384674
对象内存管理
首先请大家时刻记住一个概览:
两室一厅、两室一厅、两室一厅
最大最灵活的是堆,其次是栈,这些都是随程序运行而不断变化的,最后是方法区,方法区相对固定
堆内存
堆内容(最灵活、也是唯一让程序员自动控制的空间,也是java占用绝大内存的部分)
堆是专门储存使用new关键字创建的对象实例的空间。
例如:Emp eric = new Emp();
程序员创建的每一个对象,地址都是储存在堆中,每new一次就开辟一次空间,而且每一次开辟都有一个唯一的地址值。
地址值一般都是存在栈中
生活案例:
现实中,大家去饭馆点菜,new运算就好像你点了一盘糖醋排骨,人厨师就给你做了一盘糖醋排骨端上来,堆就好像,你面前的一张大桌子,菜摆哪儿无所谓,只要摆的下就行,而栈就好像饥饿难耐的你一样,眼睛直勾勾的盯着
堆中的一个对象,也就是桌子上的这盘菜。
对象的生命周期:
什么是生命周期?
其实就是一个对象何时创建出来,又何时销毁的,也就是一个对象在内存中,从产生到结束的过程。
生活案例:就好像饭店吃完了饭,服务员开始收拾残羹剩饭。
如果一个对象被回收了,那么这个对象就消失没有了
JAVA程序的内存泄漏问题
我们先来一个小案例:
java">package day05;
public class Memory {
public static void main(String[] args) {
/*对象生命周期的测试*/
//我点了一份小笼包,一笼4个
char[] my = new char[]{'包','包','包','包'};
//女朋友点了一份烧麦,一份5个
char[] gf = new char[]{'烧','烧','烧','烧','烧'};
//我吃完了所有的小笼包
for (int i = 0; i<my.length; i++) {
my[i]=0;
}
//女朋友只吃了2个烧麦
gf[0]=0;
gf[1]=0;
System.out.println(gf);
//我的包子吃完了,我去吃女朋友的烧麦
System.out.println("我去吃女朋友的烧麦");
my=gf; //把gf 堆中的地址值传给了 my
//我吃了第三个烧麦
my[2]=0;
//女朋友吃了第四个烧麦
gf[3]=0;
System.out.println(gf);
}
}
在练习案例中。我们会发现装小笼包的盘子已经空了,在生活中,我们一般会喊服务员收走,在java中,JVM自带一个垃圾回收机制,会帮我们把这个废弃的对象收走或者说删除掉
但是可能会发生延迟的情况,不能保证在第一时间被收走。
生活案例:就好比饭馆用餐高峰期,服务员忙不过来,不能第一时间把垃圾收走,垃圾没被收走,桌子也就无法使用,也就无法在接待新的客人。
…
这种情况就是一种资源浪费。
对于这种资源的浪费,在程序中,我们统称为“内存泄漏”。
这种内存泄漏不可避免,如果严重就麻烦了,会导致放弃对象堆积越来越多,内存空间越来越少,最终导致程序崩溃!
那么我们应该如何从自身小事做起,减少这种内存泄漏呢?
1.内存泄漏是指,废弃的对象没有被及时的回收。
2.严重的内存泄漏会导致内存中的废弃对象越来越多,直到内存占满程序崩溃。
3.垃圾回收器判断对象何时回收的依据是 “该对象是否还有变量正在引用”。
4.建议:确定一个引用变量指向的对象不再使用时,应该及时将引用类型变量设置为null。
注意:所以今后只要大家确定引用类型的变量 所指向的变量不在使用,就要立刻把这个对象赋值为null,也就是告诉JVM这个对象我不要了
我们还是以上面的例子 举例:
比如说:我女朋友吃完最后一个烧麦,准备离开了,那么内存中会怎么样呢?
java">package day05;
public class Memory {
public static void main(String[] args) {
/*对象生命周期的测试*/
//我点了一份小笼包,一笼4个
char[] my = new char[]{'包','包','包','包'};
//女朋友点了一份烧麦,一份5个
char[] gf = new char[]{'烧','烧','烧','烧','烧'};
//我吃完了所有的小笼包
for (int i = 0; i<my.length; i++) {
my[i]=0;
}
//女朋友只吃了2个烧麦
gf[0]=0;
gf[1]=0;
System.out.println(gf);
//我的包子吃完了,我去吃女朋友的烧麦
System.out.println("我去吃女朋友的烧麦");
my=gf; //把gf 堆中的地址值传给了 my
//我吃了第三个烧麦
my[2]=0;
//女朋友吃了第四个烧麦
gf[3]=0;
System.out.println(gf);
//女朋友吃了最后一个烧麦,准备离开
gf[4]=0;
gf=null; //告诉JVM这个变量我不要了
//仅将一个gf 变量设置为null是不够的,
//因为my 也在调用这个变量,所有2个都要设置为null
my=null;
}
}
需要把my 和 gf 都设置为空,JVM才会认为此对象已经废弃,无对象调用,然后回收。
System.gc()方法
生活案例:
说道这里可能会有人问了,在饭馆里吃饭,我吃的多,菜摆不下了,可以自动喊服务员帮我收拾下桌子清理掉空盘子,那么在程序中应该怎么让JVM提前帮我收拾呢?
System.gc()方法: 建议JVM马.上调度GC尽快回收废弃对象。
注意了是建议!
gc回收是有延迟的,不能保证一发现,就立刻回收!
GC何时回收废弃对象,程序员是不必关心的。
GC并不是一发现废弃对象 ,就立刻回收,都会有延迟。
当废弃对象占用内存可能比较大时,就需要调用System.gc()方法。主动请GC立刻回收。
System.gc()方法: 建议JVM马.上调度GC尽快回收废弃对象。
…
例如:先获得当前JVM最大内存,初始内存,空闲内存
- Runtime rt=Runtime.getRuntime();
-最大内存( M ): rt.MaxMemory()/1024/1024;
-初始内存( M ) : rt.TotalMemory()/1024/1024;
-空闲内存( M ): rt.FreeMemory()/1024/1024;
然后,循环创建200个10M大小的字符数组,观察创建前后的内存变化
java">package day05;
public class showMen {
public static void main(String[] args) {
//堆测试
showMen();
}
/**
* 显示JVM三大内存指标
*/
public static void showMen() {
//要想获得JVM三大指标,必须先获得一个对象叫做运行时对象
Runtime rt = Runtime.getRuntime();
//maxMemory得到的是一个字节单位,我们需要换算成兆M。
//前面的"MAX"是理论值,加如说java内存不够了,我可能会系统要,最大要到多少。
//而"Total"是,java虚拟机一启用 占多少内存。
//"Free":空闲的内存有多大
System.out.println("Max:" + (rt.maxMemory() / 1024 / 1024) + "M; "
+ "Total" + (rt.totalMemory() / 1024 / 1024) + "M; "
+ "Free" + (rt.freeMemory() / 1024 / 1024) + "M; ");
}
}
运行结果:
解析:
Max:1813M; Total123M; Free119M;
Max:1813M:其实是个理论值,都没有实际都分配,最大就只能1813M,在大程序就崩溃了(日常生活中经常看见 某某程序无响应,就是程序崩溃了)
Total123M:这是说现在JVM分配的内存为123M,当前123M可用
Free119M:这里指是在JVM分配的123M中,还可用的内存 也可以说还剩下119可用
练习:
创建一个10M的字符数组对象,然后观察 自动回收和不主动回收的差别。
java">package day05;
public class showMen {
public static void main(String[] args) {
//堆测试
showMen();
//那么这个10M的字符数组怎么定义呢?
//数组的长度和大小 由元素的个数决定的。
char[] chs=new char[10*1024*1024/2];//创建一个10M的char类型
showMen();
}
/**
* 显示JVM三大内存指标
*/
public static void showMen() {
//要想获得JVM三大指标,必须先获得一个对象叫做运行时对象
Runtime rt = Runtime.getRuntime();
//maxMemory得到的是一个字节单位,我们需要换算成兆M。
//前面的"MAX"是理论值,加如说java内存不够了,我可能会系统要,最大要到多少。
//而"Total"是,java虚拟机一启用 占多少内存。
//"Free":空闲的内存有多大
System.out.println("Max:" + (rt.maxMemory() / 1024 / 1024) + "M; "
+ "Total" + (rt.totalMemory() / 1024 / 1024) + "M; "
+ "Free" + (rt.freeMemory() / 1024 / 1024) + "M; ");
}
}
输出结果:
解析:
我们可以发现 Total还是123M,因为我们就调用了10M对他根本没影响,所以不改变。
我们在看发现 Free少了10M,因为我们现在程序有了10M的程序被占用。
让我们加上垃圾回收来看看:
java">public class showMen {
public static void main(String[] args) {
//堆测试
showMen();
//那么这个10M的字符数组怎么定义呢?
//数组的长度和大小 由元素的个数决定的。
char[] chs=new char[10*1024*1024/2];//创建一个10M的char类型
//让我们清空内存调用,执行垃圾回收
chs = null;
System.gc();
showMen();
}
//后面部分省略 和上面一样~
运行结果:
我们可以发现,内存已经回收成功,注意因为我们内存占用少,gc才能快速回收,如果内存占用过大,gc可能会发生内存泄漏!
接下来暴力测试:
java">//堆测试
showMen();
//用循环创建测试 占用2000M,程序是否会崩溃
for (int i = 0; i < 200; i++) {
char[] chs=new char[10*1024*1024/2];
//chs=null;
//System.gc();
}
showMen();
}
//后面部分省略 和上面一样~
运行结果:
大家是不是很好奇,为什么没有崩溃?
在程序运行中,gc已经察觉到了内存可能紧张,于是自己主动启动垃圾回收,
另外我们可以发现Total和Free相差74M,是为什么呢? 是因为刚才在清理的过程中,要到了显示的时候,还剩下74M还没来得及回收,使用就占用了!
我们可以得出结论,如果大内存占用,仅靠JVM回收,很容易内存泄漏,程序崩溃,
继续测试
我们把char内存的大小增长到10000M呢?
自动回收还回收的过来吗?
java">public class showMen {
public static void main(String[] args) {
//堆测试
showMen();
//循环创建占用2000M,程序是否会崩溃
for (int i = 0; i < 200; i++) {
char[] chs=new char[10000*1024*1024/2];
//chs=null;
//System.gc();
}
showMen();
}
//后面部分省略 和上面一样~
运行结果:
我们可以发现程序报错了!这是非常著名的错误!
表示堆里的内存溢出了!
注意:频繁的调用gc,会影响程序运行的效率。
就好像生活中,你正开心的吃着饭,服务员每隔5分钟来问你,“你吃完了吗?” ,是不是也会影响你吃饭的心情和效率呢?
下一章节 栈和方法区解析
请打开此链接https://blog.csdn.net/QQ1043051018/article/details/112384674