




已阅读5页,还剩21页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
HashMap存储结构浅析什么是hashcode 分析HashMap之前先介绍下什么Hashcode(散列码)。它是一个int,每个对象都会有一个hashcode,它在内存的存放位置是放在对象的头部(对象头部存放的信息有hashcode,指向Class的引用,和一些有关垃圾回收信息)。具体如何生成hashcode,这个相当复杂,由于我们的主题是“浅析”,所以不深入探讨。有个问题需要讲的是,如果在你的类中覆盖了Object的equals(Object)方法,那么你必须覆盖hashCode方法,不然,当你使用HashMap,HashSet,HashTable时会出现问题。具体原因下文会详细描述。 Java手机网jm.NETString类的hashcode String类重写了Object类中的equals和hashCode方法,原因很简单,Object中的equals方法是指比较两个对象是不是指向同一个引用对象,而String类指需要比较内容相不相等就可以了。所以String覆盖了equals方法,同时覆盖了hashCode方法。这里需要提一下Object规范里规定:如果两个对象根据equals(Object)方式是相等的,那么这两个对象的hashCode一定要相等。 String中的hashcode算法很简单如下: 代码:-for (int i = 0; i h = 97; JAVA手机网h = 31 * 97 + 98 = h = 3105; h = 31 * 3105 + 99 = h = 96354; 所以“abc”的hashCode就是96354 存储方式 列表的存储方式是基于数组,如下图: JAVA手机网 链表的存储方式是基于指针,如下图:(单向链表) JAVA手机网HashMap的存储方式是上面两种的结合,如下图: HashMap的存取 HashMap的功能是通过“键(key)”能够快速的找到“值”。下面我们分析下HashMap存数据的基本流程: 1、 当调用put(key,value)时,首先获取key的hashcode,int hash = key.hashCode(); JAVA手机网 2、 再把hash通过一下运算得到一个int h. hash = (hash 20) (hash 12); int h = hash (hash 7) (hash 4); 为什么要经过这样的运算呢?这就是HashMap的高明之处。先看个例子,一个十进制数32768(二进制1000 0000 0000 0000),经过上述公式运算之后的结果是35080(二进制1000 1001 0000 1000)。看出来了吗?或许这样还看不出什么,再举个数字61440(二进制1111 0000 0000 0000),运算结果是65263(二进制1111 1110 1110 1111),现在应该很明显了,它的目的是让“1”变的均匀一点,散列的本意就是要尽量均匀分布。那这样有什么意义呢?看第3步。 3、 得到h之后,把h与HashMap的承载量(HashMap的默认承载量length是16,可以自动变长。在构造HashMap的时候也可以指定一个长度。这个承载量就是上图所描述的数组的长度。)进行逻辑与运算,即 h & (length-1),这样得到的结果就是一个比length小的正数,我们把这个值叫做index。其实这个index就是索引将要插入的值在数组中的位置。第2步那个算法的意义就是希望能够得出均匀的index,这是HashTable的改进,HashTable中的算法只是把key的hashcode与length相除取余,即hash % length,这样有可能会造成index分布不均匀。还有一点需要说明,HashMap的键可以为null,它的值是放在数组的第一个位置。 4、 我们用tableindex表示已经找到的元素需要存储的位置。先判断该位置上有没有元素(这个元素是HashMap内部定义的一个类Entity,基本结构它包含三个类,key,value和指向下一个Entity的next),没有的话就创建一个Entity对象,在tableindex位置上插入,这样插入结束;如果有的话,通过链表的遍历方式去逐个遍历,看看有没有已经存在的key,有的话用新的value替换老的value;如果没有,则在tableindex插入该Entity,把原来在tableindex位置上的Entity赋值给新的Entity的next,这样插入结束。 再回头看看前面提到的为什么覆盖了equals方法之后一定要覆盖hashCode方法,很简单,比如,String a = new String(“abc”);String b = new String(“abc”);如果不覆盖hashCode的话,那么a和b的hashCode就会不同,把这两个类当做key存到HashMap中的话就会出现问题,就会和key的唯一性相矛盾。 HashMap的原理及遍历【遍历方法一】view plaincopy to clipboardprint?for(Map.Entry entry : hashmap.entrySet() System.out.println(Key=+entry.getKey()+-value=+entry.getValue().toString() for(Map.Entry entry : hashmap.entrySet() System.out.println(Key=+entry.getKey()+-value=+entry.getValue().toString() 【遍历方法二:用keySet遍历】 view plaincopy to clipboardprint?Iterator it=hashmap.keySet().iterator();/这是取得键对象 while(it.hasNext() System.out.println( it.Next数据的值是: +get(it.next(); /获得键所对应的值。 Iterator it=hashmap.keySet().iterator();/这是取得键对象 while(it.hasNext() System.out.println( it.Next数据的值是: +get(it.next(); /获得键所对应的值。 【遍历方法三:用entrySet遍历】view plaincopy to clipboardprint?Iterator i = hasmap.entrySet().iterator(); while(i.hasNext() Entry entry=(Entry)it.next(); Object key=entry.getKey(); Object value=entry.getValue(); Iterator i = hasmap.entrySet().iterator();while(i.hasNext() Entry entry=(Entry)it.next(); Object key=entry.getKey(); Object value=entry.getValue(); 使用HashMap的匿名内部类Entry遍历比使用keySet()效率要高很多,使用forEach循环时要注意不要在循环的过程中改变键值对的任何一方的值,否则出现哈希表的值没有随着键值的改变而改变,到时候在删除的时候会出现问题。 此外,entrySet比keySet快些。对于keySet其实是遍历了2次,一次是转为iterator,一次就从hashmap中取出key所对于的value。而entrySet只是遍历了第一次,他把key和value都放到了entry中,所以就快了。【存取之美 HashMap原理、源码、实践】 HashMap是一种十分常用的数据结构,作为一个应用开发人员,对其原理、实现的加深理解有助于更高效地进行数据存取。本文所用的jdk版本为1.5。 【使用HashMap】 Effective JAVA中认为,99%的情况下,当你覆盖了equals方法后,请务必覆盖hashCode方法。默认情况下,这两者会采用Object的“原生”实现方式,即:Java代码protected native int hashCode(); public boolean equals(Object obj) return (this = obj); hashCode方法的定义用到了native关键字,表示它是由C或C+采用较为底层的方式来实现的,你可以认为它返回了该对象的内存地址;而缺省equals则认为,只有当两者引用同一个对象时,才认为它们是相等的。如果你只是覆盖了equals()而没有重新定义hashCode(),在读取HashMap的时候,除非你使用一个与你保存时引用完全相同的对象作为key值,否则你将得不到该key所对应的值。 另一方面,你应该尽量避免使用“可变”的类作为HashMap的键。如果你将一个对象作为键值并保存在HashMap中,之后又改变了其状态,那么HashMap就会产生混乱,你所保存的值可能丢失(尽管遍历集合可能可以找到)。可参考/developerworks/cn/java/j-jtp02183/ 【HashMap存取机制】 Hashmap实际上是一个数组和链表的结合体,利用数组来模拟一个个桶(类似于Bucket Sort)以快速存取不同hashCode的key,对于相同hashCode的不同key,再调用其equals方法从List中提取出和key所相对应的value。 JAVA中hashMap的初始化主要是为initialCapacity和loadFactor这两个属性赋值。前者表示hashMap中用来区分不同hash值的key空间长度,后者是指定了当hashMap中的元素超过多少的时候,开始自动扩容,。默认情况下initialCapacity为16,loadFactor为0.75,它表示一开始hashMap可以存放16个不同的hashCode,当填充到第12个的时候,hashMap会自动将其key空间的长度扩容到32,以此类推;这点可以从源码中看出来:Java代码void addEntry(int hash, K key, V value, int bucketIndex) Entry e = tablebucketIndex; tablebucketIndex = new Entry(hash, key, value, e); if (size+ = threshold) resize(2 * table.length); 而每当hashMap扩容后,内部的每个元素存放的位置都会发生变化(因为元素的最终位置是其hashCode对key空间长度取模而得),因此resize方法中又会调用transfer函数,用来重新分配内部的元素;这个过程成为rehash,是十分消耗性能的,因此在可预知元素的个数的情况下,一般应该避免使用缺省的initialCapacity,而是通过构造函数为其指定一个值。例如我们可能会想要将数据库查询所得1000条记录以某个特定字段(比如ID)为key缓存在hashMap中,为了提高效率、避免rehash,可以直接指定initialCapacity为2048。 另一个值得注意的地方是,hashMap其key空间的长度一定为2的N次方,这一点可以从一下源码中看出来:Java代码int capacity = 1; while (capacity initialCapacity) capacity = 1; 即使我们在构造函数中指定的initialCapacity不是2的平方数,capacity还是会被赋值为2的N次方。 为什么Sun Microsystem的工程师要将hashMap key空间的长度设为2的N次方呢?这里参考R.W.Floyed给出的衡量散列思想的三个标准: 一个好的hash算法的计算应该是非常快的 一个好的hash算法应该是冲突极小化 如果存在冲突,应该是冲突均匀化 为了将各元素的hashCode保存至长度为Length的key数组中,一般采用取模的方式,即index = hashCode % Length。不可避免的,存在多个不同对象的hashCode被安排在同一位置,这就是我们平时所谓的“冲突”。如果仅仅是考虑元素均匀化与冲突极小化,似乎应该将Length取为素数(尽管没有明显的理论来支持这一点,但数学家们通过大量的实践得出结论,对素数取模的产生结果的无关性要大于其它数字)。为此,Craig Larman and Rhett GuthrieJava Performence中对此也大加抨击。为了弄清楚这个问题,Bruce Eckel(Thinking in JAVA的作者)专程采访了java.util.hashMap的作者Joshua Bloch,并将他采用这种设计的原因放到了网上(/javatutorials/javahashmap.shtml) 。 上述设计的原因在于,取模运算在包括JAVA在内的大多数语言中的效率都十分低下,而当除数为2的N次方时,取模运算将退化为最简单的位运算,其效率明显提升(按照Bruce Eckel给出的数据,大约可以提升58倍) 。看看JDK中是如何实现的:Java代码static int indexFor(int h, int length) return h & (length-1); 当key空间长度为2的N次方时,计算hashCode为h的元素的索引可以用简单的与操作来代替笨拙的取模操作!假设某个对象的hashCode为35(二进制为100011),而hashMap采用默认的initialCapacity(16),那么indexFor计算所得结果将会是100011 & 1111 = 11,即十进制的3,是不是恰好是35 Mod 16。 上面的方法有一个问题,就是它的计算结果仅有对象hashCode的低位决定,而高位被统统屏蔽了;以上面为例,19(10011)、35(100011)、67(1000011)等就具有相同的结果。针对这个问题, Joshua Bloch采用了“防御性编程”的解决方法,在使用各对象的hashCode之前对其进行二次Hash,参看JDK中的源码:Java代码static int hash(Object x) int h = x.hashCode(); h += (h 14); h += (h 10); return h; 采用这种旋转Hash函数的主要目的是让原有hashCode的高位信息也能被充分利用,且兼顾计算效率以及数据统计的特性,其具体的原理已超出了本文的领域。 加快Hash效率的另一个有效途径是编写良好的自定义对象的HashCode,String的实现采用了如下的计算方法:Java代码for (int i = 0; i len; i+) h = 31*h + valoff+; hash = h; 这种方法HashCode的计算方法可能最早出现在Brian W. Kernighan和Dennis M. Ritchie的The C Programming Language中,被认为是性价比最高的算法(又被称为times33算法,因为C中乘数常量为33,JAVA中改为31),实际上,包括List在内的大多数的对象都是用这种方法计算Hash值。 另一种比较特殊的hash算法称为布隆过滤器,它以牺牲细微精度为代价,换来存储空间的大量节俭,常用于诸如判断用户名重复、是否在黑名单上等等,可以参考李开复的数学之美系列第13篇(/2006/08/blog-post.html) 【Fail-Fast机制】 众所周知,HashMap不是线程安全的集合类。但在某些容错能力较好的应用中,如果你不想仅仅因为1%的可能性而去承受hashTable的同步开销,则可以考虑利用一下HashMap的Fail-Fast机制,其具体实现如下:Java代码Entry nextEntry() if (modCount != expectedModCount) throw new ConcurrentModificationException(); 其中modCount为HashMap的一个实例变量,并且被声明为volatile,表示任何线程都可以看到该变量被其它线程修改的结果(根据JVM内存模型的优化,每一个线程都会存一份自己的工作内存,此工作内存的内容与本地内存并非时时刻刻都同步,因此可能会出现线程间的修改不可见的问题) 。使用Iterator开始迭代时,会将modCount的赋值给expectedModCount,在迭代过程中,通过每次比较两者是否相等来判断HashMap是否在内部或被其它线程修改。HashMap的大多数修改方法都会改变ModCount,参考下面的源码:Java代码public V put(K key, V value) K k = maskNull(key); int hash = hash(k); int i = indexFor(hash, table.length); for (Entry e = tablei; e != null; e = e.next) if (e.hash = hash & eq(k, e.key) V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; modCount+; addEntry(hash, k, value, i); return null; 以put方法为例,每次往HashMap中添加元素都会导致modCount自增。其它诸如remove、clear方法也都包含类似的操作。 从上面可以看出,HashMap所采用的Fail-Fast机制本质上是一种乐观锁机制,通过检查状态没有问题则忽略有问题则抛出异常的方式,来避免线程同步的开销,下面给出一个在单线程环境下发生Fast-Fail的例子:Java代码class Test public static void main(String args) java.util.HashMap map=new java.util.HashMap(); map.put(new Object(), a); map.put(new Object(), b); java.util.Iterator it=map.keySet().iterator(); while(it.hasNext() it.next(); map.put(, ); System.out.println(map.size(); 运行上面的代码会抛出java.util.ConcurrentModificationException,因为在迭代过程中修改了HashMap内部的元素导致modCount自增。若将上面代码中 map.put(new Object(), b) 这句注释掉,程序会顺利通过,因为此时HashMap中只包含一个元素,经过一次迭代后已到了尾部,所以不会出现问题,也就没有抛出异常的必要了。 在通常并发环境下,还是建议采用同步机制。这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止意外的非同步访问。 【LinkedHashMap】 遍历HashMap所得到的数据是杂乱无章的,这在某些情况下客户需要特定遍历顺序时是十分有用的。比如,这种数据结构很适合构建 LRU 缓存。调用 put 或 get 方法将会访问相应的条目(假定调用完成后它还存在)。putAll 方法以指定映射的条目集合迭代器提供的键-值映射关系的顺序,为指定映射的每个映射关系生成一个条目访问。Sun提供的J2SE说明文档特别规定任何其他方法均不生成条目访问,尤其,collection 集合类的操作不会影响底层映射的迭代顺序。 LinkedHashMap的实现与 HashMap 的不同之处在于,前者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是集合中元素的插入顺序。该类定义了header、before与after三个属性来表示该集合类的头与前后“指针”,其具体用法类似于数据结构中的双链表,以删除某个元素为例:Java代码private void remove() before.after = after; after.before = before; 实际上就是改变前后指针所指向的元素。 显然,由于增加了维护链接列表的开支,其性能要比 HashMap 稍逊一筹,不过有一点例外:LinkedHashMap的迭代所需时间与其的所包含的元素成比例;而HashMap 迭代时间很可能开支较大,因为它所需要的时间与其容量(分配给Key空间的长度)成比例。一言以蔽之,随机存取用HashMap,顺序存取或是遍历用LinkedHashMap。 LinkedHashMap还重写了removeEldestEntry方法以实现自动清除过期数据的功能,这在HashMap中是无法实现的,因为后者其内部的元素是无序的。默认情况下,LinkedHashMap中的removeEldestEntry的作用被关闭,其具体实现如下:Java代码protected boolean removeEldestEntry(Map.Entry eldest) return false; 可以使用如下的代码覆盖removeEldestEntry:Java代码private static final int MAX_ENTRIES = 100; protected boolean removeEldestEntry(Map.Entry eldest) return size() MAX_ENTRIES; 它表示,刚开始,LinkedHashMap中的元素不断增长;当它内部的元素超过MAX_ENTRIES(100)后,每当有新的元素被插入时,都会自动删除双链表中最前端(最旧)的元素,从而保持LinkedHashMap的长度稳定。 缺省情况下,LinkedHashMap采取的更新策略是类似于队列的FIFO,如果你想实现更复杂的更新逻辑比如LRU(最近最少使用) 等, 可以在构造函数中指定其accessOrder为true,因为的访问元素的方法(get)内部会调用一个“钩子”,即recordAccess,其具体实现如下:Java代码void recordAccess(HashMap m) LinkedHashMap lm = (LinkedHashMap)m; if (lm.accessOrder) lm.modCount+; remove(); addBefore(lm.header); 上述代码主要实现了这样的功能:如果accessOrder被设置为true,则每次访问元素时,都将该元素移至headr的前面,即链表的尾部。将removeEldestEntry与accessOrder一起使用,就可以实现最基本的内存缓存,具体代码可参考/blog/180236。 【WeakHashMap】 99%的JAVA教材教导我们不要去干预JVM的垃圾回收机制,但JAVA中确实存在着与其密切相关的四种引用:强引用、软引用、弱引用以及幻象引用。 JAVA中默认的HashMap采用的是采用类似于强引用的强键来管理的,这意味着即使作为key的对象已经不存在了(指没有任何一个引用指向它),也仍然会保留在HashMap中,在某些情况下(例如内存缓存)中,这些过期的条目可能会造成内存泄漏等问题。 WeakHashMap采用的策略是,只要作为key的对象已经不存在了(超出生命周期),就不会阻止垃圾收集器清空此条目,即使当前机器的内存并不紧张。不过,由于GC是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象,除非你显示地调用它,可以参考下面的例子:Java代码public static void main(String args) Mapmap = new WeakHashMap(); map.put(new String(Alibaba), alibaba); while (map.containsKey(Alibaba) try Thread.sleep(500); catch (InterruptedException ignored) System.out.println(Checking for empty); System.gc(); 上述代码输出一次Checking for empty就退出了主线程,意味着GC在最近的一次垃圾回收周期中清除了new String(“Alibaba”),同时WeakHashMap也做出了及时的反应,将该键对应的条目删除了。如果将map的类型改为HashMap的话,由于其内部采用的是强引用机制,因此即使GC被显示调用,map中的条目依然存在,程序会不断地打出Checking for empty字样。另外,在使用WeakHashMap的情况下,若是将Java代码map.put(new String(Alibaba), alibaba); 改为Java代码map.put(Alibaba, alibaba); 程序还是会不断输出Checking for empty。这与前面我们分析的WeakHashMap的弱引用机制并不矛盾,因为JVM为了减小重复创建和维护多个相同String的开销,其内部采用了蝇量模式(JAVA与模式),此时的“Alibaba”是存放在常量池而非堆中的,因此即使没有对象指向“Alibaba”,它也不会被GC回收。弱引用特别适合以下对象:占用大量内存,但通过垃圾回收功能回收以后很容易重新创建。 介于HashMap和WeakHashMap之中的是SoftHashMap,它所采用的软引用的策略指的是,垃圾收集器并不像其收集弱可及的对象一样尽量地收集软可及的对象,相反,它只在真正 “需要” 内存时才收集软可及的对象。软引用对于垃圾收集器来说是一种“睁一只眼,闭一只眼”方式,即 “只要内存不太紧张,我就会保留该对象。但是如果内存变得真正紧张了,我就会去收集并处理这个对象。” 就这一点看,它其实要比WeakHashMap更适合于实现缓存机制。遗憾的是,JAVA中并没有实现相关的SoftHashMap类(Apache和Google提供了第三方的实现),但它却是提供了两个十分重要的类java.lang.ref.SoftReference以及ReferenceQueue,可以在对象应用状态发生改变是得到通知,可以参考mon.collection.SofthashMap中processQueue方法的实现:Java代码private ReferenceQueue queue = new ReferenceQueue(); ValueCell vc; Map hash = new HashMap(initialCapacity, loadFactor); while (vc = (ValueCell) queue.poll() != null) if (vc.isValid() hash.remove(vc.key); else valueCell.dropped-; processQueue方法会在几乎所有SoftHashMap的方法中被调用到,JVM会通过ReferenceQueue的poll方法通知该对象已经过期并且当前的内存现状需要将它释放,此时我们就可以将其从hashMap中剔除。事实上,默认情况下,Alibaba的MemoryCache所使用的就是SoftHashMap。 本文来自CSDN博客,转载请标明出处:/vozon/archive/2010/04/07/5458370.aspx【JavaEye】从数据结构谈HashMap的实现原文地址: /topic/752549最近看了下java的数据结构,同时又大致看了下hashMap的实现源码。下面和大家分享下hashMap的实现方式。 hashMap用了一个名字为table的数组;还有若干个名字为entry的链表。看hashMap是如何应用这些数据结构的。用插 入举例:hashMap首先会通过key得到其hashCode,具体的hash函数就不说了(因为没多大意义);然后把key的hashCode%table.length,就是拿hashCode模table数组大小,得到的余数就是key所在table数组中的下标(实际不是key的下标,是entry类);但这样做有个问题,可能不同key却有一样的hasdCode,所以求余后其必然会得到相同的下标,那如何 存储了?有两个办法,一种是利用开放地址法,就是说后来相同的hashCode去找先来hashCode所在下标的相邻下标。说的有点绕口,举个例子,比 如已经存在table数组的31的位置上了,再来一个,其通过哈希后说:我也应该在31的位置上, 但是table说,你后来,你再在31附近找个空位安置下吧。当然,具体怎么找,有规则的。另外一种方式就是链地址法,还是拿以上的例子 说,来到时,发现31的位置已经被占了,这时table说:,你带 下;其实就是要把的引用存储了。但是说:我 怎么存储的引用了,我没位置呀。所以table说:我给你们每个壳(entry类)吧,把你们都封装了;于是就有了 entry类。 那hashMap是使用那种方式了。先分析下开放地址和链地址法的优缺点。开放地址法一般需要2倍实际数据大小的空间,因为要留下一定的空闲地址去存储相 同hashCode的;并且查找相邻空闲地址也是一项比较费时间的任务;链地址法,就不需要2倍的空间(table数 组),但是需要存储额外的信息,比如next信息;总体来看,链地址法好点(关键是节省了查找相邻地址的时间),所以,hashMap用的是链地址法。 还有问题,hashMap为什么用数组存储index(hashCode%table.length)了,而不用链表了?因为数组有固定大小限制,而链表 没有,而且map是没有限制大小的?这主要考虑了查找效率的问题。从前面的分析可以看到,因为key的hashCode%table.length直接做 为entry的下标,所以其查询key的速度很快,只要O(1)的时间;如果是链表,要一个一个的排查对比,需要O(N)的时间;这之间的效率,相差太远 了。所以,hashMap用了数组。 最后一个问题,那数组的固定大小如何解决了?hashMap在每次插入数据前,会检查table数组的实际容量,如果实际容量=初始容量,则把 table的初始容量扩为原来的2倍,这时,就需要一个一个复制原来的数据项了,这是比较费时的!所以,初始容量很重要。 后续跟帖的补充:看了楼主的介绍,随便说下以上标注的几个问题:1. hash函数并非没有多大意义。hash(int h)方法根据key的hashCode重新计算一次散列。此算法加入了高位计算,防止低位不变,高位变化时,造成的hash冲突。static int hash(int h) h = (h 20) (h 12); return h (h 7) (h 4); 2. 我看JDK1.6中计算下标的代码是&运算,而非%运算,也可能我和楼主看的版本不同吧。在HashMap中,调用 indexFor(int h, int length) 方法来计算该对象应该保存在 table 数组的哪个索引处。indexFor(int h, int length) 方法的代码如下:static int indexFor(int h, int length) return h & (length-1); 这个方法非常巧妙,它通过 h & (table.length -1) 来得到该对象的保存位,而HashMap底层数组的长度总是 2 的 n 次方,这是HashMap在速度上的优化。 在HashMap 构造器中有如下代码:int capacity = 1; while (capacity initialCapacity) capacity = 1;当length总是 2 的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。3. HashMap中有加载因子loadFactor这个参数的定义,当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。JVM内存管理和JVM垃圾回收机制你对JVM内存组成结构和JVM垃圾回收机制是否熟悉,这里和大家简单分享一下,希望对你的学习有所帮助,首先来看一下JVM内存结构,它是由堆、栈、本地方法栈、方法区等部分组成,结构图如下所示。JVM学习笔记 JVM内存管理和JVM垃圾回收JVM内存组成结构JVM内存结构由堆、栈、本地方法栈、方法区等部分组成,结构图如下所示:1)堆所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成,结构图如下所示:新生代。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例旧生代。用于存放新生代中经过多次垃圾回收仍然存活的对象2)栈每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果3)本地方法栈用于支持native方法的执行,存储了每个native方法调用的状态4)方法区存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用持久代(PermanetGeneration)来存放方法区,可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值。介绍完了JVM内存组成结构,下面我们再来看一下JVM垃圾回收机制。JVM垃圾回收机制JVM分别对新生代和旧生代采用不同的垃圾回收机制新生代的GC:新生代通常存活时间较短,因此基于Copying算法来进行回收,所谓Copying算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和FromSpace或ToSpace之间copy。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到 survivor,最后到旧生代,用javavisualVM来查看,能明显观察到新生代满了后,会把对象转移到旧生代,然后清空继续装载,当旧生代也满了后,就会报outofmemory的异常,如下图所示:在执行机制上JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew)1)串行GC在整个扫描和复制过程采用单线程的方式来进行,适用于单CPU、新生代空间较小及对暂停时间要求不是非常高的应用上,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定2)并行回收GC在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数3)并行GC与旧生代的并发GC配合使用旧生代的GC:旧生代与新生代不同,对象存活的时间比较长,比较稳定,因
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 晋城口碑营销活动策划方案(3篇)
- 淄博隔声板施工方案(3篇)
- 印度农田施工方案(3篇)
- 取样员考试题库及答案
- 心理学考研题目及答案
- 小学整数加减法题目及答案
- 冬请允许我拥抱你250字12篇范文
- 数学课《几何图形变换与性质》教学实践
- 农村科技研发与应用推广合同
- 一条路到达一个地方(14篇)
- 沪教版八年级生物第一册全册完整课件
- 第06章设计美学程能林第4版《工业设计概论》课课件
- DB23-T 3492-2023 工贸企业充电间安全设施技术规范
- 防水工程施工报价表
- 中行bfw框架开发和测试资料课件
- 住院患者非计划性拔管风险评估与护理指导意见
- MSA偏倚分析报告
- 食材配送应急保障配合措施方案
- 泌尿系统结石
- 义务教育语文课程标准(2022)测试题带答案(20套)
- 瞬时弹性成像技术在肝病领域临床应用课件
评论
0/150
提交评论