知识大全 java内存泄漏的定位与分析

Posted 元素

篇首语:读书不要贪多,而是要多加思索,这样的读书使我获益不少。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 java内存泄漏的定位与分析相关的知识,希望对你有一定的参考价值。

  ArrayList就是传说中的动态数组 就是Array的复杂版本 它提供了如下一些好处 动态的增加和减少元素 灵活的设置数组的大小……

  认真阅读本文 我相信一定会对你有帮助 比如为什么ArrayList里面提供了一个受保护的removeRange方法?提供了其他没有被调用过的私有方法?

  首先看到对ArrayList的定义

  [java]

  public class ArrayList<E> extends AbstractList<E>  implements List<E> RandomAccess Cloneable java io Serializable

  从ArrayList<E>可以看出它是支持泛型的 它继承自AbstractList 实现了List RandomAccess Cloneable java io Serializable接口

  AbstractList提供了List接口的默认实现(个别方法为抽象方法)

  List接口定义了列表必须实现的方法

  RandomAccess是一个标记接口 接口内没有定义任何内容

  实现了Cloneable接口的类 可以调用Object clone方法返回该对象的浅拷贝

  通过实现 java io Serializable 接口以启用其序列化功能 未实现此接口的类将无法使其任何状态序列化或反序列化 序列化接口没有方法或字段 仅用于标识可序列化的语义

  ArrayList的属性

  ArrayList定义只定义类两个私有属性

  [java]

  /**

  * The array buffer into which the elements of the ArrayList are stored

  * The capacity of the ArrayList is the length of this array buffer

  */

  private transient Object[] elementData;

  /**

  * The size of the ArrayList (the number of elements it contains)

  *

  * @serial

  */

  private int size;

  [java]

  很容易理解 elementData存储ArrayList内的元素 size表示它包含的元素的数量

  有个关键字需要解释 transient

  Java的serialization提供了一种持久化对象实例的机制 当持久化对象时 可能有一个特殊的对象数据成员 我们不想用serialization机制来保存它 为了在一个特定对象的一个域上关闭serialization 可以在这个域前加上关键字transient

  ansient是Java语言的关键字 用来表示一个域不是该对象串行化的一部分 当一个对象被串行化的时候 transient型变量的值不包括在串行化的表示中 然而非transient型的变量是被包括进去的

  有点抽象 看个例子应该能明白

  [java]

  public class UserInfo implements Serializable

  private static final long serialVersionUID = L;

  private String name;

  private transient String psw;

  public UserInfo(String name String psw)

  this name = name;

  this psw = psw;

  

  public String toString()

  return name= + name + psw= + psw;

  

  

  public class TestTransient

  public static void main(String[] args)

  UserInfo userInfo = new UserInfo( 张三 )

  System out println(userInfo)

  try

  // 序列化 被设置为transient的属性没有被序列化

  ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(

   UserInfo out ))

  o writeObject(userInfo)

  o close()

   catch (Exception e)

  // TODO: handle exception

  e printStackTrace()

  

  try

  // 重新读取内容

  ObjectInputStream in = new ObjectInputStream(new FileInputStream(

   UserInfo out ))

  UserInfo readUserInfo = (UserInfo) in readObject()

  //读取后psw的内容为null

  System out println(readUserInfo toString())

   catch (Exception e)

  // TODO: handle exception

  e printStackTrace()

  

  

  

  被标记为transient的属性在对象被序列化的时候不会被保存

  

  接着回到ArrayList的分析中……

  ArrayList的构造方法

  看完属性看构造方法 ArrayList提供了三个构造方法

  [java]

  /**

  * Constructs an empty list with the specified initial capacity

  */

  public ArrayList(int initialCapacity)

  super()

  if (initialCapacity < )

  throw new IllegalArgumentException( Illegal Capacity: +

  initialCapacity)

  this elementData = new Object[initialCapacity];

  

  /**

  * Constructs an empty list with an initial capacity of ten

  */

  public ArrayList()

  this( )

  

  /**

  * Constructs a list containing the elements of the specified

  * collection in the order they are returned by the collection s

  * iterator

  */

  public ArrayList(Collection<? extends E> c)

  elementData = c toArray()

  size = elementData length;

  // c toArray might (incorrectly) not return Object[] (see )

  if (elementData getClass() != Object[] class)

  elementData = pyOf(elementData size Object[] class)

  

  第一个构造方法使用提供的initialCapacity来初始化elementData数组的大小 第二个构造方法调用第一个构造方法并传入参数 即默认elementData数组的大小为 第三个构造方法则将提供的集合转成数组返回给elementData(返回若不是Object[]将调用pyOf方法将其转为Object[])

  ArrayList的其他方法

  add(E e)

  add(E e)都知道是在尾部添加一个元素 如何实现的呢?

  [java]

  public boolean add(E e)

  ensureCapacity(size + )   // Increments modCount!!

  elementData[size++] = e;

  return true;

  

  书上都说ArrayList是基于数组实现的 属性中也看到了数组 具体是怎么实现的呢?比如就这个添加元素的方法 如果数组大 则在将某个位置的值设置为指定元素即可 如果数组容量不够了呢?

  看到add(E e)中先调用了ensureCapacity(size+ )方法 之后将元素的索引赋给elementData[size] 而后size自增 例如初次添加时 size为 add将elementData[ ]赋值为e 然后size设置为 (类似执行以下两条语句elementData[ ]=e;size= ) 将元素的索引赋给elementData[size]不是会出现数组越界的情况吗?这里关键就在ensureCapacity(size+ )中了

  根据ensureCapacity的方法名可以知道是确保容量用的 ensureCapacity(size+ )后面的注释可以明白是增加modCount的值(加了俩感叹号 应该蛮重要的 来看看)

  [java]

  /**

  * Increases the capacity of this <tt>ArrayList</tt> instance if

  * necessary to ensure that it can hold at least the number of elements

  * specified by the minimum capacity argument

  *

  * @param   minCapacity   the desired minimum capacity

  */

  public void ensureCapacity(int minCapacity)

  modCount++;

  int oldCapacity = elementData length;

  if (minCapacity > oldCapacity)

  Object oldData[] = elementData;

  int newCapacity = (oldCapacity * )/ + ;

  if (newCapacity < minCapacity)

  newCapacity = minCapacity;

  // minCapacity is usually close to size so this is a win:

  elementData = pyOf(elementData newCapacity)

  

  

  The number of times this list has been structurally modified

  这是对modCount的解释 意为记录list结构被改变的次数(观察源码可以发现每次调用ensureCapacoty方法 modCount的值都将增加 但未必数组结构会改变 所以感觉对modCount的解释不是很到位)

  增加modCount之后 判断minCapacity(即size+ )是否大于oldCapacity(即elementData length) 若大于 则调整容量为max((oldCapacity* )/ + minCapacity) 调整elementData容量为新的容量 即将返回一个内容为原数组元素 大小为新容量的数组赋给elementData;否则不做操作

  所以调用ensureCapacity至少将elementData的容量增加的 所以elementData[size]不会出现越界的情况

  容量的拓展将导致数组元素的复制 多次拓展容量将执行多次整个数组内容的复制 若提前能大致判断list的长度 调用ensureCapacity调整容量 将有效的提高运行速度

  可以理解提前分配好空间可以提高运行速度 但是测试发现提高的并不是很大 而且若list原本数据量就不会很大效果将更不明显

  add(int index E element)

  add(int index E element)在指定位置插入元素

  [java]

  public void add(int index E element)

  if (index > size || index < )

  throw new IndexOutOfBoundsException(

   Index: +index+ Size: +size)

  ensureCapacity(size+ )   // Increments modCount!!

  System arraycopy(elementData index elementData index +

  size index)

  elementData[index] = element;

  size++;

  

  首先判断指定位置index是否超出elementData的界限 之后调用ensureCapacity调整容量(若容量足够则不会拓展) 调用System arraycopy将elementData从index开始的size index个元素复制到index+ 至size+ 的位置(即index开始的元素都向后移动一个位置) 然后将index位置的值指向element

  addAll(Collection<? extends E> c)

  [java]

  public boolean addAll(Collection<? extends E> c)

  Object[] a = c toArray()

  int numNew = a length;

  ensureCapacity(size + numNew)   // Increments modCount

  System arraycopy(a elementData size numNew)

  size += numNew;

  return numNew != ;

  

  先将集合c转换成数组 根据转换后数组的程度和ArrayList的size拓展容量 之后调用System arraycopy方法复制元素到elementData的尾部 调整size 根据返回的内容分析 只要集合c的大小不为空 即转换后的数组长度不为 则返回true

  

  addAll(int index Collection<? extends E> c)

  [java]

  public boolean addAll(int index Collection<? extends E> c)

  if (index > size || index < )

  throw new IndexOutOfBoundsException(

   Index: + index + Size: + size)

  Object[] a = c toArray()

  int numNew = a length;

  ensureCapacity(size + numNew)   // Increments modCount

  int numMoved = size index;

  if (numMoved > )

  System arraycopy(elementData index elementData index + numNew

  numMoved)

  System arraycopy(a elementData index numNew)

  size += numNew;

  return numNew != ;

  

  先判断index是否越界 其他内容与addAll(Collection<? extends E> c)基本一致 只是复制的时候先将index开始的元素向后移动X(c转为数组后的长度)个位置(也是一个复制的过程) 之后将数组内容复制到elementData的index位置至index+X

  clear()

  [java]

  public void clear()

  modCount++;

  // Let gc do its work

  for (int i = ; i < size; i++)

  elementData[i] = null;

  size = ;

  

  clear的时候并没有修改elementData的长度(好不容易申请 拓展来的 凭什么释放 留着搞不好还有用呢 这使得确定不再修改list内容之后最好调用trimToSize来释放掉一些空间) 只是将所有元素置为null size设置为

  clone()

  返回此 ArrayList 实例的浅表副本 (不复制这些元素本身 )

  [java]

  public Object clone()

  try

  ArrayList<E> v = (ArrayList<E>) super clone()

  v elementData = pyOf(elementData size)

  v modCount = ;

  return v;

   catch (CloneNotSupportedException e)

  // this shouldn t happen since we are Cloneable

  throw new InternalError()

  

  

  调用父类的clone方法返回一个对象的副本 将返回对象的elementData数组的内容赋值为原对象elementData数组的内容 将副本的modCount设置为

  contains(Object)

  []

  public boolean contains(Object o)

  return indexOf(o) >= ;

  

  indexOf方法返回值与 比较来判断对象是否在list中 接着看indexOf

  indexOf(Object)

  [java]

  public int indexOf(Object o)

  if (o == null)

  for (int i = ; i < size; i++)

  if (elementData[i]==null)

  return i;

   else

  for (int i = ; i < size; i++)

  if (o equals(elementData[i]))

  return i;

  

  return ;

  

  通过遍历elementData数组来判断对象是否在list中 若存在 返回index([ size ]) 若不存在则返回 所以contains方法可以通过indexOf(Object)方法的返回值来判断对象是否被包含在list中

  既然看了indexOf(Object)方法 接着就看lastIndexOf 光看名字应该就明白了返回的是传入对象在elementData数组中最后出现的index值

  [java]

  public int lastIndexOf(Object o)

  if (o == null)

  for (int i = size ; i >= ; i )

  if (elementData[i]==null)

  return i;

   else

  for (int i = size ; i >= ; i )

  if (o equals(elementData[i]))

  return i;

  

  return ;

  

  采用了从后向前遍历element数组 若遇到Object则返回index值 若没有遇到 返回

  get(int index)

  这个方法看着很简单 应该是返回elementData[index]就完了

  [java]

  public E get(int index)

  RangeCheck(index)

  return (E) elementData[index];

  

  但看代码的时候看到调用了RangeCheck方法 而且还是大写的方法 看看究竟有什么内容吧

  [java]

  /**

  * Checks if the given index is in range

  */

  private void RangeCheck(int index)

  if (index >= size)

  throw new IndexOutOfBoundsException(

   Index: +index+ Size: +size)

  

  就是检查一下是不是超出数组界限了 超出了就抛出IndexOutBoundsException异常 为什么要大写呢???

  isEmpty()

  直接返回size是否等于

  remove(int index)

  [java]

  public E remove(int index)

  RangeCheck(index)

  modCount++;

  E oldValue = (E) elementData[index];

  int numMoved = size index ;

  if (numMoved > )

  System arraycopy(elementData index+ elementData index

  numMoved)

  elementData[ size] = null; // Let gc do its work

  return oldValue;

  

  首先是检查范围 修改modCount 保留将要被移除的元素 将移除位置之后的元素向前挪动一个位置 将list末尾元素置空(null) 返回被移除的元素

  remove(Object o)

  [java]

  public boolean remove(Object o)

  if (o == null)

  for (int index = ; index < size; index++)

  if (elementData[index] == null)

  fastRemove(index)

  return true;

  

   else

  for (int index = ; index < size; index++)

  if (o equals(elementData[index]))

  fastRemove(index)

  return true;

  

  

  return false;

  

  首先通过代码可以看到 当移除成功后返回true 否则返回false remove(Object o)中通过遍历element寻找是否存在传入对象 一旦找到就调用fastRemove移除对象 为什么找到了元素就知道了index 不通过remove(index)来移除元素呢?因为fastRemove跳过了判断边界的处理 因为找到元素就相当于确定了index不会超过边界 而且fastRemove并不返回被移除的元素 下面是fastRemove的代码 基本和remove(index)一致

  [java]

  private void fastRemove(int index)

  modCount++;

  int numMoved = size index ;

  if (numMoved > )

  System arraycopy(elementData index+ elementData index

  numMoved)

  elementData[ size] = null; // Let gc do its work

  

  removeRange(int fromIndex int toIndex)

  [java]

  protected void removeRange(int fromIndex int toIndex)

  modCount++;

  int numMoved = size toIndex;

  System arraycopy(elementData toIndex elementData fromIndex

  numMoved)

  // Let gc do its work

  int newSize = size (toIndex fromIndex)

  while (size != newSize)

  elementData[ size] = null;

  

  执行过程是将elementData从toIndex位置开始的元素向前移动到fromIndex 然后将toIndex位置之后的元素全部置空顺便修改size

  这个方法是protected 及受保护的方法 为什么这个方法被定义为protected呢?

  这是一个解释 但是可能不容易看明白 is javas abstractlists removerange method protected

  

  先看下面这个例子

  [java]

  ArrayList<Integer> ints = new ArrayList<Integer>(Arrays asList(

   ))

  // fromIndex low endpoint (inclusive) of the subList

  // toIndex high endpoint (exclusive) of the subList

  ints subList( ) clear()

  System out println(ints)

  输出结果是[ ] 结果是不是像调用了removeRange(int fromIndex int toIndex)!哈哈哈 就是这样的 但是为什么效果相同呢?是不是调用了removeRange(int fromIndex int toIndex)呢?

  set(int index E element)

  [java]

  public E set(int index E element)

  RangeCheck(index)

  E oldValue = (E) elementData[index];

  elementData[index] = element;

  return oldValue;

  

  首先检查范围 用新元素替换旧元素并返回旧元素

  size()

  size()方法直接返回size

  toArray()

  [java]

  public Object[] toArray()

  return pyOf(elementData size)

  

  调用pyOf将返回一个数组 数组内容是size个elementData的元素 即拷贝elementData从 至size 位置的元素到新数组并返回

  toArray(T[] a)

  [java]

  public <T> T[] toArray(T[] a)

  if (a length < size)

  // Make a new array of a s runtime type but my contents:

  return (T[]) pyOf(elementData size a getClass())

  System arraycopy(elementData a size)

  if (a length > size)

  a[size] = null;

  return a;

  

  如果传入数组的长度小于size 返回一个新的数组 大小为size 类型与传入数组相同 所传入数组长度与size相等 则将elementData复制到传入数组中并返回传入的数组 若传入数组长度大于size 除了复制elementData外 还将把返回数组的第size个元素置为空

  trimToSize()

  [java]

  public void trimToSize()

  modCount++;

  int oldCapacity = elementData length;

  if (size < oldCapacity)

  elementData = pyOf(elementData size)

  

  

  由于elementData的长度会被拓展 size标记的是其中包含的元素的个数 所以会出现size很小但elementData length很大的情况 将出现空间的浪费 trimToSize将返回一个新的数组给elementData 元素内容保持不变 length很size相同 节省空间

  学习Java最好的方式还必须是读源码 读完源码你才会发现这东西为什么是这么玩的 有哪些限制 关键点在哪里等等 而且这些源码都是大牛们写的 你能从中学习到很多

如果有大量的FGC就要查询是否有内存泄漏的问题了 图中的FGC数量就比较大 并且执行时间较长 这样就会导致系统的响应时间较长 如果对jvm的内存设置较大 那么执行一次FGC的时间可能会更长 如果为了更好的证明FGC对服务器性能的影响 我们可以使用java visualVM来查看一下 从上图可以发现执行FGC的情况 下午 : 分之前是没有FGC的 之后出现大量的FGC 上图是jvm堆内存的使用情况 下午 : 分之前的内存回收还是比较合理 但是之后大量内存无法回收 最后导致内存越来越少 导致大量的full gc 下面我们在看看大量full GC 对服务器性能的影响 下面是我用loadrunner对我们项目进行压力测试相应时间的截图 从图中可以发现有 在进行full GC 后系统的相应时间有了明显的增加 点击率和吞吐量也有了明显的下降 所以java内存泄漏对系统性能的影响是不可忽视的   定位内存泄漏 当然通过上面几种方法我们可以发现java的内存泄漏问题 但是作为一名合格的高级工程师 肯定不甘心就把这样的结论交给开发 当然这也的结论交给开发 开发也很难定位问题 为了更好的提供自己在公司的地位 我们必须给开发工程师提供更深入的测试结论 下面就来认识一下MemoryAnalyzer exe java内存泄漏检查工具利器 首先我们必须对jvm的堆内存进行dump 只有拿到这个文件我们才能分析出jvm堆内存中到底存了些什么内容 到底在做什么? MemoryAnalyzer的用户我在这里就不一一说明了 我的博客里也有说明 下面就展示我测试的成功图 其中深蓝色的部分就为内存泄漏的部分 java的堆内存一共只有 M而内存泄漏的部分独自占有了 M所以本次的内存泄漏很明显 那么我就来看看那个方法导致的内存泄漏 从上图我们可以发现红线圈著的方法占用了堆内存的 % 如果能把这个测试结果交给开发 开发是不是应该很好定位呢 所以作为一名高级测试工程师 我们需要学习的东西太多 本人刚刚完成了云存储架构师的培训学习(包括了linux的内核了解 shell的高级编程 linux安全的学习重点iptables和tcp/ip等各种协议的抓包分析 linux的集群 性能调优等接下来还有dba的课程等待着我挑战)     cha138/Article/program/Java/hx/201311/26553

相关参考

知识大全 Java内存泄漏调试

Java内存泄漏调试  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  JDK本身为调试内存泄漏问题

知识大全 Java 程序里的内存泄漏

Java程序里的内存泄漏  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  Java程序里的内存泄漏

知识大全 Java人员谈论内存泄露问题分析

Java人员谈论内存泄露问题分析  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  很多人在谈论内存

知识大全 Java内存结构与模型

Java内存结构与模型  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  一java内存结构  He

知识大全 Java垃圾收集算法与内存泄露

Java垃圾收集算法与内存泄露  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  垃圾收集算法的核心

知识大全 DataTable.NewRow 内存泄漏问题

DataTable.NewRow内存泄漏问题  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  先看

知识大全 未释放事件Handler可能导致内存泄漏

未释放事件Handler可能导致内存泄漏  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  以前曾看

知识大全 java语言与c++相比,有哪些优点?

java语言与c++相比,有哪些优点?主要是这两种开发者对内存地址变量的不同理解,使两种语言在编程的思维上有所差异。C中存在指针变量,而且每个指针对应着一个真实的物理内存地址,他可以对相应地址中的内存

知识大全 深入Java核心 Java内存分配原理精讲

深入Java核心Java内存分配原理精讲  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  Java

知识大全 Java中的堆栈内存浅析

Java中的堆栈内存浅析  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  Java把内存划分成两种