JAVA中所谓垃圾的处理

本文深入探讨Java中的垃圾回收机制,包括内存分配、垃圾回收过程及技术,并解析了JVM堆和JVM栈的区别及其对垃圾回收的影响。
在网上看了一些,大多都是《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循环外就解决问题了。
### Java 垃圾回收算法面试总结 #### 一、垃圾回收的概念 在 Java 中,垃圾回收(Garbage Collection, GC)指的是 JVM 自动管理内存的过程。当某些对象不再被引用时,它们会被视为垃圾并由垃圾回收器清理掉,从而释放内存空间供新对象使用[^3]。 #### 二、常见的垃圾回收算法 以下是几种主流的垃圾回收算法及其特点: 1. **分代收集算法** 分代收集并不是一种全新的独立算法,而是一种策略。它基于对象存活周期的不同将内存划分为几个区域(通常是新生代和老年代),然后针对不同区域选择合适的垃圾回收算法来执行垃圾回收操作[^1]。 2. **标记-清除(Mark-Sweep)算法** 这种算法首先遍历所有的可达对象并将它们标记为存活状态;随后扫描整个堆内存,删除未被标记的对象。然而,这种方法容易导致内存碎片化问题[^4]。 3. **复制(Copying)算法** 复制算法将可用内存按容量分成两块,在每次垃圾回收时只使用其中一块作为活跃区。对于活跃区内仍然存活的对象,将其复制到另一块空闲区域内,并清空原活跃区的内容。尽管此方法效率较高,但它浪费了一半的空间资源。 下面是一个简单的伪代码实现: ```java void copy() { for (Object obj : currentArea) { if (obj.isAlive()) { moveTo(obj, anotherArea); // 将活动对象复制到另一个区域 } } clear(currentArea); // 清理当前区域 swapAreas(); // 交换当前区域和另一个区域的角色 } ``` 4. **标记-整理(Mark-Compact)算法** 结合了前两种算法的优点,先同样完成一次标记过程,接着移动所有存活的对象至堆的一端排列紧凑,最后仅需简单地调整指针即可完成回收工作而不产生过多碎片。 5. **Major GC 和 Minor GC 的区别** - Minor GC 主要发生在新生代中,频率相对较高; - Major GC 则涉及到了老年代甚至整个堆内存范围内的全面清扫行动,通常是由某个特定事件触发比如某次 Minor GC 后未能满足晋升失败处理需求等情况引发 Full GC 行为[^5]。 #### 三、类卸载机制 除了常规意义上的实例级别的垃圾回收外,JVM 还提供了关于加载过的 Class 文件本身的销毁功能——即所谓的“类卸载”。一旦发现某个已经不存在任何外部强引用关系指向它的 `Class` 类型,则允许对其进行最终处置动作以节省更多系统开销[^2]。 #### 四、实际应用建议 准备参加技术岗位招聘过程中涉及到此类话题讨论时可以从以下几个方面入手复习巩固知识点: - 掌握每种主要GC方式的工作原理以及优缺点对比情况; - 明确各自适用场景下的表现差异如何影响性能指标选取依据; - 学习调优参数配置技巧帮助解决可能出现的实际生产环境难题案例分享经验交流学习成果展示等环节都非常重要不可忽视哦!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值