hashcode原理与jvm内部结构
list集合
ArrayList
ArrayList底层是数组,它是不断的扩容的,当你增加了新的数据超过了他所设定的容量,他会创建一个更大的新的数组,将之前的数组的数据复制到这个新的数组中去,再添加新的元素
LinkedList
LinkedList底层是链表,链表是没有容量的概念的
总结
对于大批量写操作,用LinkedList实现比较快,因为它不需要扩容;对于大批量的读操作,用ArrayList实现比较快,因为它可以使用下标直接定位到某个元素
Hashcode
HashMap集合
HashMap集合集中ArrayList和LinkedList的优点,既有查询快,也有修改添加快的优点。
HashMap的底层是Node数组,Node数组里面存放的是Node对象,Node对象里面有如下属性:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //hash值
final K key; //key
V value; //value
Node<K,V> next; //指向下一个Node对象的引用
}
HashMap如何保证key的值唯一:
//传入key的新hash值,key,value
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//用key的hash值往右移动,再让keyhash值低的16位异或高16位得到新hash值
static final int hash(Object key) {
int h;
//hashCode方法是Object类的方法,每一个类都是默认继承Object类的,可以直接调用,hash值取的内存地址
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//首先判断map的底层Node数组是否为空,如果是空的就让他重新分配数组的容量
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//根据key传过来的(key的新hash)异或(Node数组的容量 - 1)得到这个数据在Node数组中的位置,并且判断这个位置有没有数据
if ((p = tab[i = (n - 1) & hash]) == null)
//如果没有,就将这个数据放到这个位置上
tab[i] = newNode(hash, key, value, null);
else {
//如果有,就判断这个位置上数据的hash跟传递过来的hash是否相等并且(这个位置上的key等不等与传递过来的key的内存地址或者这个位置上的key等不等与传递过来的key的内容,所以当Map的key是自己定义的对象类型的时候,是需要重写 equals方法的)
Node<K,V> e; K k;
//判断key相等的语句
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//相等就覆盖原来的值
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
Hashset集合
HashSet集合是线程不安全的,存取速度快,HashSet判断数据不重复底层是通过HashMap的key来实现的
JVM的分区
JVM分为堆区和栈区
栈区
栈区是方法存储区,调用方法的执行叫压栈,如果调用的方法太多,会引起栈溢出报错,递归调用就很容易引起栈溢出,报java.lang.StackOverflowError的错误
模拟栈的溢出:
public static void main(String[] args) {
callSelf(0);
}
public static void callSelf(int n){
System.out.println(n++);
callSelf(n);
}
idea编译器调整jvm虚拟机栈的大小的步骤:
- run -> run
- 选择你要运行的程序,点击小箭头,选择edit编辑
- 选择VM options那一行,输入命令 -Xss加上需要分配给栈的内存
堆区
堆区是虚拟机用来存放对象和数组的地方,也是垃圾回收的主要区域
查看程序垃圾回收机制
- 在cmd命令中输入以下命令打开虚拟机查看工具
jvisualvm
- 点击工具 -->插件 --> 可用插件 --> visual gc --> 安装
- 然后回到应用程序,双击org.jetbrains.idea.maven.server.RemoteMavenServer,点击visual gc 即可查看idea运行的java代码在jvm内存运行情况
堆区的划分:
- 堆:
- 年轻代:伊甸区 + 幸存一区 + 幸存二区
- 年老代
- 非堆:(jdk1.8之前叫永久区,jdk1.8之后叫Metaspace)
JVM内存堆区的划分
- memory:内存
- off-heap:离堆,JVM之外的内存,操作系统的内存
- non-heap:非堆,虚拟机里面堆面的空间,主要存放方法的区域
- heap:堆
- old gen:年老代
- young gen:年轻代
- eden:伊甸区,对象的创建所在区
- s0:幸存一区
- s1:幸存二区
总结: JVM垃圾回收器会先去伊甸区回收,如果伊甸区回收不掉再去幸存一区和幸存二区回收,如果幸存区回收不掉会再去年老代回收,如果年老代回收不掉程序就会报错,堆溢出
jVM调参
- 打开黑窗口,输入java,有一个输出非标选项的帮助
- 输入 java -X,里面有设置堆大小,栈大小的命令语句
另外还有一些更细的参数设置:
-Xms | 堆大小设置,初始值默认为物理内存的1/64 |
---|---|
-Xmx | 堆最大值设置 |
-Xmn | 年轻代大小 |
-XX:NewSize=n | 设置年轻代大小 |
-XX:NewRatio=n | 设置年轻代和年老代的比例,例如3,年轻代比年老代为1:3 |
-XX: SurvivorRatio=n | 设置伊甸区相对单个幸存区的倍数,伊甸区要大于等于单个幸存区 |
-XX:MetaspaceSize=n | 设置非堆的大小 |
类加载
加载过的类不会再加载,即便是使用Class.forName也是一样的,不会重复加载,类中的静态代码块,在类加载的时候会也会被加载,所以说静态代码块只会被执行一次。