JAVA中所谓垃圾的处理

在网上看了一些,大多都是《thinking in Java》的原文拼凑的,我觉得这不是我想要的。按自己的思路来写写我的理解。

[color=red][size=large]一 垃圾在哪里[/size][/color]

首先需要明确的是,程序中的变量都等价于一块块内存。

然后就需要知道JVM内存分配:

◆栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中

◆堆:存放用new产生的数据

◆方法区:1.静态变量:存放在对象中用static定义的静态成员
2.常量池:存放常量
3.方法,多态,修饰符

也需要知道JAVA中内存管理:

在java中,有java程序、虚拟机、操作系统三个层次,其中java程序与虚拟机交互,而虚拟机与操作系统间交互!这就保证了java程序的平台无关性!下面我们从程序运行前,程序运行中、程序运行内存溢出三个阶段来说一下内存管理原理!

1、程序运行前:JVM向操作系统请求一定的内存空间,称为初始内存空间!程序执行过程中所需的内存都是由java虚拟机从这片内存空间中划分的。

2、程序运行中:java程序一直向java虚拟机申请内存,当程序所需要的内存空间超出初始内存空间时,java虚拟机会再次向操作系统申请更多的内存供程序使用!

3、内存溢出:程序接着运行,当java虚拟机已申请的内存达到了规定的最大内存空间,但程序还需要更多的内存,这时会出现内存溢出的错误!

至此可以看出,Java 程序所使用的内存是由 Java 虚拟机进行管理、分配的。Java 虚拟机规定了 Java 程序的初始内存空间和最大内存空间,开发者只需要关心 Java 虚拟机是如何管理内存空间的,而不用关心某一种操作系统是如何管理内存的。

现在我们知道了JVM如何申请内存,也知道了申请的内存如何分类,也知道了我们程序中的数据如何分配。
[color=red][size=large]
二 垃圾的回收[/size][/color]

[b]JVM堆与JVM栈[/b]

JVM栈是运行时的单位,而JVM堆是存储的单位。

JVM栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;JVM堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
[b]
为什么要把JVM堆和JVM栈区分出来呢?JVM栈中不是也可以存储数据吗?[/b]

第一,从软件设计的角度看,JVM栈代表了处理逻辑,而JVM堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。

第二,JVM堆与JVM栈的分离,使得JVM堆中的内容可以被多个JVM栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,JVM堆中的共享常量和缓存可以被所有JVM栈访问,节省了空间。

第三,JVM栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于JVM栈只能向上增长,因此就会限制住JVM栈存储内容的能力。而JVM堆不同,JVM堆中的对象是可以根据需要动态增长的,因此JVM栈和JVM堆的拆分,使得动态增长成为可能,相应JVM栈中只需记录JVM堆中的一个地址即可。

第四,面向对象就是JVM堆和JVM栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在JVM堆中;而对象的行为(方法),就是运行逻辑,放在JVM栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。

ok,切入主题。

1.栈中垃圾的回收:

在Java中一个线程就会相应有一个线程JVM栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程JVM栈。而JVM堆则是所有线程共享的。JVM栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。

栈中是通过指针来标记位置的。压栈指针上移,出栈(作用域或者线程生命周期来标志)指针下移。这个上移和下移就对应了编译器对栈空间的开辟和清理。

2.堆中垃圾的回收:

String str = new String("abc");

str在栈中不考虑,new String("abc")则是在堆中开辟了内存空间存放对象本身。我们常说的[color=red][b]java的垃圾回收器只知道释放由new分配的内存[/b][/color]。

垃圾回收器的回收技术:

a.引用计数
每个对象都有一个引用计数器,引用连接对象时,计数加1;引用离开作用域或者置为null时计数减1。这个思路我们写代码的时候经常会用到,也很好理解---到零即清理。有很大的隐患,对象间的关联是不可估的,对象之间循环引用,很可能会出现”对象应该清理,计数器却不为零“的现象。所以,这种方式没有应用于任何一个JAVA虚拟机。

b.自适应---停止复制和标记清扫的结合
停止复制(stop and copy)
暂停程序的运行,将现有堆中活着的对象紧凑排列复制到一个新的堆中,旧堆清空。
标记清扫(mark and sweep)
完整遍历所有对象,标记活着的对象,在标记工作完成后清扫未标记的堆数据。剩下的空间是不连续的,如果希望得到连续空间的话,就需要重新整理(切换到上个方式)。

java虚拟机跟踪标记清扫的效果,存活对象的标志位会不断递增,堆中碎片太多,定期执行的停止复制开始整理,然后切换到标记清扫方式。

finalize()方法,如果非常理解它的用法,我的意见是不重写不使用。它的作用集中在java程序调用native方法时,可以调用了内存分配的方法,比如C的malloc(),如果忘记了内存处理,就可以在finalize()中调用native方法释放内存。

3.方法区垃圾的回收

方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。

方法区在物理上也不需要是连续的,可以选择固定大小或可扩展大小,并且方法区比堆还多了一个限制:可以选择是否执行垃圾收集。一般的,方法区上 执行的垃圾收集是很少的,这也是方法区被称为永久代的原因之一(HotSpot),但这也不代表着在方法区上完全没有垃圾收集,其上的垃圾收集主要是针对 常量池的内存回收和对已加载类的卸载。

在方法区上定义了OutOfMemoryError:PermGen space异常,在内存不足时抛出。

对于hotspot中关于代的知识,还需要充电,方法去的垃圾回收也没有弄明白,感觉又是一盘大棋,慢慢磨吧。

剩下的看下这段代码,我为什么要调查学习Java垃圾回收。
for(Object obj:list){
TPlan plan = (TPlan)obj;
PlanInfo planInfo = new PlanInfo();
planInfo.setPlanName(plan.getPlanName);
/*
...
*/
}


本地数据库的数据量不大,5000条记录以下,程序正常跑。前两天项目部署前偶然在笔记本上做了一次查询,后台就报出了内存溢出。dao层是做了分页处理的,每次查询到的数据为50。虽然那台笔记本确实装了很多东西也很老了,但是出现这样的问题还是代码写的太随意,应该检讨。
只需要把TPlan和PlanInfo的声明放到for循环外就解决问题了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值