JVM、GC与HashMap
阿里巴巴突然来了个面试邀请电话,问了些java底层的东西,不知所措,所以专门花了些时间做了下学习,顺便记录下,好记性不如烂笔头。
一、对JAVA的垃圾回收机制(GC)的理解
不同于C/C++需要手工释放对象所占的内存,JAVA全部委托给了GC进行处理,能更有效的防止内存泄漏的情况。一个程序对应着一个JVM,每个JVM会单独有一个堆,java中创建的对象与数组是存放在堆中的,堆中的内存由GC进行管理(栈中存储引用变量、局部变量一级基本数据类型,超出作用域就会立即释放内存)。
当一个对象没有再被引用时就会被GC标记为可回收状态,然后在一个不确定的时间对其进行回收(一般是在应用程序空闲或者java堆内存不足是被调用),当然在对一个对象进行回收时,首先会执行其finalize()方法,在finalize()方法中你可再次将该对象变为活跃状态,阻止其回收。
一个对象还有没有再被引用一般有两种判断方式:
1、(JDK1.2之前)每个对象都有一个引用计数器,每多一个引用,计数器+1,少一个则-1,当计数器为0时,则表示该对象没有再被引用了。
2、根搜索算法。这里要盗一张图了:
从GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
java中可作为GC Root的对象有:
1.虚拟机栈中引用的对象(本地变量表)
2.方法区中静态属性引用的对象
3. 方法区中常量引用的对象
4.本地方法栈中引用的对象(Native对象)
GC在回收这些空闲对象时会采用标记-复制算法处理:
标记从根节点出发的所有在用对象,然后按顺序复制到一块新内存上,然后再回收旧内存。这样就不会造成造成内存碎片,但是需要一块额外的内存交换空间来进行复制。
二、对HashMap的理解
HashMap结构其实是一个数组+链表的结构。HashMap有一个叫做Entry的内部类,它用来存储key-value对。如下图:
static class Entry implements Map.Entry { final K key; V value; Entry next; final int hash; ...//More code goes here } `
创建一个HashMap时会首先创建一个叫做table的Entry数组:
table的默认大小是16。当往map里面存数据时,也就是执行put方法时,首先会调用key的HashCode()方法,然后进行hash计算,计算出其对应在table的index,如果table的index位置上已经有Entry了,首先会判断该Entry的key与要put的key是否相同,如果相同则会替换,如果不同则会根据链表继续判断,即与当前位置的Entry的next指向的下一个Entry的key进行比较,直到遇到相同的key进行替换或者在链表末尾创建一个Entry。
1 /** 2 * Associates the specified value with the specified key in this map. If the 3 * map previously contained a mapping for the key, the old value is 4 * replaced. 5 * 6 * @param key 7 * key with which the specified value is to be associated 8 * @param value 9 * value to be associated with the specified key 10 * @return the previous value associated with <tt>key</tt>, or <tt>null</tt> 11 * if there was no mapping for <tt>key</tt>. (A <tt>null</tt> return 12 * can also indicate that the map previously associated 13 * <tt>null</tt> with <tt>key</tt>.) 14 */ 15 public V put(K key, V value) { 16 if (key == null) 17 return putForNullKey(value); 18 int hash = hash(key.hashCode()); 19 int i = indexFor(hash, table.length); 20 for (Entry<k , V> e = table[i]; e != null; e = e.next) { 21 Object k; 22 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 23 V oldValue = e.value; 24 e.value = value; 25 e.recordAccess(this); 26 return oldValue; 27 } 28 } 29 30 modCount++; 31 addEntry(hash, key, value, i); 32 return null; 33 }
从map中取值时也就是执行get()方法也是类似的道理,先计算出key对应table的index,然后在链表上一层一层往下找:
1 /** 2 * Returns the value to which the specified key is mapped, or {@code null} 3 * if this map contains no mapping for the key. 4 * 5 * <p> 6 * More formally, if this map contains a mapping from a key {@code k} to a 7 * value {@code v} such that {@code (key==null ? k==null : 8 * key.equals(k))}, then this method returns {@code v}; otherwise it returns 9 * {@code null}. (There can be at most one such mapping.) 10 * 11 * </p><p> 12 * A return value of {@code null} does not <i>necessarily</i> indicate that 13 * the map contains no mapping for the key; it's also possible that the map 14 * explicitly maps the key to {@code null}. The {@link #containsKey 15 * containsKey} operation may be used to distinguish these two cases. 16 * 17 * @see #put(Object, Object) 18 */ 19 public V get(Object key) { 20 if (key == null) 21 return getForNullKey(); 22 int hash = hash(key.hashCode()); 23 for (Entry<k , V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { 24 Object k; 25 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 26 return e.value; 27 } 28 return null; 29 }
备注:
1、无论你何时实现 equals 方法,你必须同时实现 hashCode 方法。在一个运行的进程中,相等的对象必须要有相同的哈希码。不相等的对象哈希码有可能相同,有同一个哈希值的对象不一定相等。
2、HashMap有两个参数影响其性能:初始容量和加载因子。默认初始容量是16,加载因子是0.75。容量是哈希表中桶(Entry数组)的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,通过调用 rehash 方法将容量翻倍。
希望大家能一起交流,一起学习!
参考文章链接:
http://www.importnew.com/16517.html
http://www.importnew.com/10620.html
http://jbutton.iteye.com/blog/1569746