Java内存管理第三篇 - 内存可能产生的问题

Java内存在分配和回收的过程中会产品很多的问题,下面来说一说可能会产生的问题。

1、垃圾处理

从程序运行的根节点出发,遍历整个对象引用,查找存活的对象。那么在这种方式的实现中,垃圾回收从哪儿开始的呢?即,从哪儿开始查找哪些对象是正在被当前系统使用的。上面分析的堆和栈的区别,其中栈是真正进行程序执行地方,所以要获取哪些对象正在被使用,则需要从Java栈开始。同时,一个栈是与一个线程对应的,因此,如果有多个线程的话,则必须对这些线程对应的所有的栈进行检查。

同时,除了栈外,还有系统运行时的寄存器等,也是存储程序运行数据的。这样,以栈或寄存器中的引用为起点,我们可以找到堆中的对象,又从这些对象找到对堆中其他对象的引用,这种引用逐步扩展,最终以null引用或者基本类型结束,这样就形成了一颗以Java栈中引用所对应的对象为根节点的一颗对象树,如果栈中有多个引用,则最终会形成多颗对象树。在这些对象树上的对象,都是当前系统运行所需要的对象,不能被垃圾回收。而其他剩余对象,则可以视为无法被引用到的对象,可以被当做垃圾进行回收。

因此,垃圾回收的起点是一些根对象(java栈,
静态变量, 寄存器...)。而最简单的Java栈就是Java程序执行的main函数。这种回收方式,也是“标记-清除”的回收方式。

2、碎片处理

由于不同Java对象存活时间是不一定的,因此,在程序运行一段时间以后,如果不进行内存整理,就会出现零散的内存碎片。碎片最直接的问题就是会导致无法分配大块的内存空间,以及程序运行效率降低。所以,“标记-整理”算法可以解决碎片的问题。

3、内存泄漏

所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。java中有垃圾回收机制,它可以保证一对象不再被引用的时候,即对象编程了孤儿的时候,对象将自动被垃圾回收器从内存中清除掉。由于Java 使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。

(1)为什么内存会泄露

在下面的情境中,对象A引用了对象B. A的生命周期(t1 - t4) 比 B的(t2 - t3)要长得多. 当对象B在应用程序逻辑中不会再被使用以后, 对象 A 仍然持有着 B的引用. (根据虚拟机规范)在这种情况下垃圾收集器不能将 B 从内存中释放. 这种情况很可能会引起内存问题,假若A 还持有着其他对象的引用,那么这些被引用的(无用)对象都不会被回收,并占用着内存空间.

甚至有可能 B 也持有一大堆其他对象的引用。 这些对象由于被 B 所引用,也不会被垃圾收集器所回收. 所有这些无用的对象将消耗大量宝贵的内存空间。

典型举例:如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。

又如:缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。

(2)避免内存泄漏

检查java中的内存泄露,一定要让程序将各种分支情况都完整执行到程序结束,然后看某个对象是否被使用过,如果没有,则才能判定这个对象属于内存泄露。

1.
当心集合类,比如 HashMap,ArrayList等,因为这是最容易发生内存泄露的地方.当集合对象被声明为static时,他们的生命周期一般和整个应用程序一样长。

2. 注意事件监听和回调.当注册的监听器不再使用以后,如果没有被注销,那么很可能会发生内存泄露.

3. "当一个类自己管理其内存空间时,程序员应该注意内存泄露." 常常是一个对象的成员变量需要被置为null 时仍然指向其他对象,

(3)内存泄漏举例

[java] view plaincopyprint?

  1. import java.util.EmptyStackException;
  2. public class test {
  3. private Object[] elements = new Object[10];
  4. private int size = 0;
  5. public void push(Object e) {
  6. ensureCapacity();
  7. elements[size++] = e;
  8. }
  9. public Object pop() {
  10. if (size == 0)
  11. throw new EmptyStackException();
  12. return elements[--size];
  13. }
  14. private void ensureCapacity() {
  15. if (elements.length == size) {
  16. Object[] oldElements = elements;
  17. elements = new Object[2 * elements.length + 1];
  18. System.arraycopy(oldElements, 0, elements, 0, size);
  19. }
  20. }
  21. }

上面的原理应该很简单,假如堆栈加了10个元素,然后全部弹出来,虽然堆栈是空的,没有我们要的东西,但是这是个对象是无法回收的,这个才符合了内存泄露的两个条件:无用,无法回收。

但是就是存在这样的东西也不一定会导致什么样的后果,如果这个堆栈用的比较少,也就浪费了几个K内存而已,反正我们的内存都上G了,哪里会有什么影响,再说这个东西很快就会被回收的,有什么关系。下面看两个例子。

[java] view plaincopyprint?

  1. import java.util.Stack;
  2. public class test3 {
  3. public static Stack<Object> s = new Stack<>();
  4. static {
  5. s.push(new Object());
  6. s.pop(); // 这里有一个对象发生内存泄露
  7. s.push(new Object()); // 上面的对象可以被回收了,等于是自愈了 } }
  8. }
  9. }

因为是static,就一直存在到程序退出,但是我们也可以看到它有自愈功能,就是说如果你的Stack最多有100个对象,那么最多也就只有100个对象无法被回收其实这个应该很容易理解,Stack内部持有100个引用,最坏的情况就是他们都是无用的,因为我们一旦放新的进去,以前的引用自然消失!

内存泄露的另外一种情况:当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。

[java] view plaincopyprint?

  1. import java.util.HashSet;
  2. import java.util.Set;
  3. public class HashSetTest {
  4. public static void main(String[] args) {
  5. Set<Person> set = new HashSet<Person>();
  6. Person p1 = new Person("唐僧", "pwd1", 25);
  7. Person p2 = new Person("孙悟空", "pwd2", 26);
  8. Person p3 = new Person("猪八戒", "pwd3", 27);
  9. set.add(p1);
  10. set.add(p2);
  11. set.add(p3);
  12. System.out.println("总共有:" + set.size() + " 个元素!"); // 结果:总共有:3 个元素!
  13. p3.setAge(2); // 修改p3的年龄,此时p3元素对应的hashcode值发生改变
  14. set.remove(p3); // 此时remove不掉,造成内存泄漏
  15. set.add(p3); // 重新添加,居然添加成功
  16. System.out.println("总共有:" + set.size() + " 个元素!"); // 结果:总共有:4 个元素!
  17. for (Person person : set) {
  18. System.out.println(person);
  19. }
  20. }
  21. }

[java] view plaincopyprint?

  1. package test6;
  2. public class Person {
  3. private String username;
  4. private String password;
  5. private int age;
  6. public Person(String username, String password, int age){
  7. this.username = username;
  8. this.password = password;
  9. this.age = age;
  10. }
  11. public String getUsername(){
  12. return username;
  13. }
  14. public void setUsername(String username)  {
  15. this.username = username;
  16. }
  17. public String getPassword() {
  18. return password;
  19. }
  20. public void setPassword(String password)  {
  21. this.password = password;
  22. }
  23. public int getAge()  {
  24. return age;
  25. }
  26. public void setAge(int age)   {
  27. this.age = age;
  28. }
  29. public int hashCode() {
  30. final int prime = 31;
  31. int result = 1;
  32. result = prime * result + age;
  33. result = prime * result + ((password == null) ? 0 : password.hashCode());
  34. result = prime * result + ((username == null) ? 0 : username.hashCode());
  35. return result;
  36. }
  37. public boolean equals(Object obj)  {
  38. if (this == obj)
  39. return true;
  40. if (obj == null)
  41. return false;
  42. if (getClass() != obj.getClass())
  43. return false;
  44. Person other = (Person) obj;
  45. if (age != other.age)
  46. return false;
  47. if (password == null)    {
  48. if (other.password != null)
  49. return false;
  50. }
  51. else if (!password.equals(other.password))
  52. return false;
  53. if (username == null)  {
  54. if (other.username != null)
  55. return false;
  56. }
  57. else if (!username.equals(other.username))
  58. return false;
  59. return true;
  60. }
  61. public String toString()  {
  62. return this.username+"-->"+this.password+"-->"+this.age;
  63. }
  64. }

输出结果如下:

总共有:3 个元素!

总共有:4 个元素!

猪八戒-->pwd3-->2

孙悟空-->pwd2-->26

唐僧-->pwd1-->25

猪八戒-->pwd3-->2

时间: 2024-08-01 10:28:00

Java内存管理第三篇 - 内存可能产生的问题的相关文章

linux内存管理之heap篇

文章来源——博客园绿色冰点 前几次我们分析了Linux系统中用户进程的4G虚存大致分为了几个部分,介绍了3G用户空间中数据段,代码段等静态区域的虚存管理,重点分析了栈的使用.这次我们来分析一下虚存使用中另一个重要部分--堆.前面的介绍中,我们知道编译器,操作系统担负着大量栈分配管理的工作.不论是静态分配的栈空间还是用户动态分配的栈空间,在函数返回的时候就自动释放了.堆的使用比之栈而言更为灵活,允许程序员动态的分配并释放,但也意味着,堆的使用需要程序员更为小心. 4.5 堆的内存管理 在学习"数据

垃圾回收GC:.Net自动内存管理 上(三)终结器

垃圾回收GC:.Net自动内存管理 上(三)终结器 垃圾回收GC:.Net自动内存管理 上(一)内存分配 垃圾回收GC:.Net自动内存管理 上(二)内存算法 垃圾回收GC:.Net自动内存管理 上(三)终结器 前言 .Net下的GC完全解决了开发者跟踪内存使用以及控制释放内存的窘态.然而,你或午想要理解GC是怎么工作的.此系列文章中将会解释内存资源是怎么被合理分配及管理的,并包含非常详细的内在算法描述.同时,还将讨论GC的内存清理流程及什么时清理,怎么样强制清理. 终结器 GC提供了另外一个能

垃圾回收GC:.Net自己主动内存管理 上(三)终结器

垃圾回收GC:.Net自己主动内存管理 上(三)终结器 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己主动内存管理 上(三)终结器 前言 .Net下的GC全然攻克了开发人员跟踪内存使用以及控制释放内存的窘态.然而,你或午想要理解GC是怎么工作的.此系列文章中将会解释内存资源是怎么被合理分配及管理的,并包括很具体的内在算法描写叙述.同一时候,还将讨论GC的内存清理流程及什么时清理,怎么样强制清理. 终结

[Linux内存]linux内存学习(三)——内存管理基础

1,linux内核内存管理 arm体系结构的内存建立是在 kernel/arch/arm/kernel/setup.c文件里~ linux内核设计与实现——内存管理 linux内核中,内核把物理页作为内存管理的基本单元,处理器最小的寻址单位是字节,从虚拟内存角度看,页是最小单位.内核中使用struct page结构来表示每个物理页,系统中每个物理页都有这样的一个结构体.所有的页描述符存在mem_map数组中,每个页描述符(struct page)长度为32字节. [cpp] view plain

黑马程序员---OC基础6【内存管理】【手动内存管理】【单、多个对象的内存管理】【*@property参数】【@class的使用】【NSString类的内存管理】【autorelease使用】

------- iOS培训.Android培训.Java培训.期待与您交流! ---------- [内存管理] 1.内存管理概念 由于移动设备内存及其有限,所以每个app所占的内存也是有限的 需要回收一些不使用的空间 2.OC内存管理的范围 管理任何继承NSOject的对象,对其他的基本数据类型无效 主要管理堆区中的对象的内存管理   3.内存管理的原理 1)对象所有权概念 任何对象都可以能拥有一个或多个所有者,只要一个对象至少还拥有一个所有者,他就会继续存在 cocoasu所有权策略 任何自

七.OC基础加强--1.内存管理 2.野指针,内存泄露 3.set方法的内存管理 [email&#160;protected]参数 [email&#160;protected]和循环retain的使用 6.NSString的内存管理

1,内存管理简单介绍 1,为什么要有内存管理? malloc selloc dealloc```需要回头复习 一般的内存 4s 是512m内存:6 是1024m内存: 当内存过大时,会耗尽内存.出现程序闪退. 2.OC内存管理的范围 : 管理任何继承NSObject的对象,对其他的基本数据类型无效. 3.对象类型是程序运行过程中动态分配的,存储在堆区:内存管理主要是对 堆区中的对象的内存管理. 4.OC内存管理的原理 为了防止内存泄露 对象的引用计数器 : 每个OC对象都有自己的引用计数器,是一

Objective-C 【多个对象内存管理(野指针&amp;内存泄漏)】

------------------------------------------- 多个对象内存管理(野指针&内存泄漏) (注:这一部分知识请结合"单个对象内存管理"去理解) 这一部分的知识比较简单,就牵扯到一个会产生野指针的情形和如何避免内存泄漏问题. 代码: #import <Foundation/Foundation.h> @interface Car : NSObject -(void)run; @end @implementation Car //监控

cortex_m3_stm32嵌入式学习笔记(二十四):内存管理实验(动态内存)

有用过C语言编程的童鞋对动态管理内存肯定有点了解..好处就不多说了 今天实现STM32的动态内存管理 内存管理,是指软件运行时对计算机内存资源的分配和使用的技术.其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源. 内存管理的实现方法有很多种,他们其实最终都是要实现两个函数:malloc 和 free(好熟悉): malloc 函数用于内存申请, free 函数用于内存释放. 实现方式:分块式内存管理 从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成.内存池被等

JAVA高级篇(二、JVM内存模型、内存管理之第二篇)

本文转自https://zhuanlan.zhihu.com/p/25713880. JVM的基础概念 JVM的中文名称叫Java虚拟机,它是由软件技术模拟出计算机运行的一个虚拟的计算机. JVM也充当着一个翻译官的角色,我们编写出的Java程序,是不能够被操作系统所直接识别的,这时候JVM的作用就体现出来了,它负责把我们的程序翻译给系统"听",告诉它我们的程序需要做什么操作. 我们都知道Java的程序需要经过编译后,产生.Class文件,JVM才能识别并运行它,JVM针对每个操作系统