@@ -2273,14 +2273,13 @@ public class CodeDemo {
22732273
22742274#### 基本介绍
22752275
2276- Object类是Java中的祖宗类,一个类或者默认继承Object类,或者间接继承Object类,Object类的方法是一切子类都可以直接使用
2276+ Object 类是 Java 中的祖宗类,一个类或者默认继承 Object 类,或者间接继承 Object 类,Object 类的方法是一切子类都可以直接使用
22772277
2278- Object类常用方法 :
2278+ Object 类常用方法 :
22792279
2280- * `public String toString()`:
2281- 默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址,例:Student@735b478;
2282- 直接输出对象名称,默认会调用toString()方法,所以省略toString()不写;
2283- 如果输出对象的内容,需要重写toString()方法,toString方法存在的意义是为了被子类重写
2280+ * `public String toString()`:默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址,例:Student@735b478;
2281+ * 直接输出对象名称,默认会调用 toString() 方法,所以省略 toString() 不写;
2282+ * 如果输出对象的内容,需要重写 toString() 方法,toString 方法存在的意义是为了被子类重写
22842283* `public boolean equals(Object o)`:默认是比较两个对象的引用是否相同
22852284* `protected Object clone()`:创建并返回此对象的副本
22862285
@@ -2302,7 +2301,7 @@ public boolean equals(Object o) {
23022301
23032302**面试题**:== 和 equals 的区别
23042303
2305- * == 比较的是变量(栈) 内存中存放的对象的(堆) 内存地址,用来判断两个对象的**地址**是否相同,即是否是指相同一个对象,比较的是真正意义上的指针操作。
2304+ * == 比较的是变量(栈) 内存中存放的对象的(堆) 内存地址,用来判断两个对象的**地址**是否相同,即是否是指相同一个对象,比较的是真正意义上的指针操作
23062305* 重写 equals 方法比较的是两个对象的**内容**是否相等,所有的类都是继承自 java.lang.Object 类,所以适用于所有对象,如果**没有对该方法进行覆盖的话,调用的仍然是 Object 类中的方法,比较两个对象的引用**
23072306
23082307hashCode 的作用:
@@ -2738,12 +2737,9 @@ public class Demo1_25 {
27382737
27392738#### 不可变好处
27402739
2741- * 可以缓存 hash 值
2742- String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,只要进行一次计算
2743- * String Pool 的需要
2744- 如果一个String对象已经被创建过了,就会从 String Pool 中取得引用,只有 String是不可变的,才可能使用 String Pool
2745- * 安全性
2746- String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是
2740+ * 可以缓存 hash 值,例如 String 用做 HashMap 的 key,不可变的特性可以使得 hash 值也不可变,只要进行一次计算
2741+ * String Pool 的需要,如果一个 String 对象已经被创建过了,就会从 String Pool 中取得引用,只有 String 是不可变的,才可能使用 String Pool
2742+ * 安全性,String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是
27472743* String 不可变性天生具备线程安全,可以在多个线程中安全地使用
27482744* 防止子类继承,破坏 String 的 API 的使用
27492745
@@ -5130,7 +5126,7 @@ HashMap继承关系如下图所示:
51305126
51315127* resize()
51325128
5133- 当 HashMap 中的元素个数超过`(数组长度)*loadFactor(负载因子)`或者链表过长时(链表长度 > 8,数组长度 < 64),就会进行数组扩容,创建新的数组,伴随一次重新 hash 分配,并且遍历 hash 表中所有的元素非常耗时,所以要尽量避免 resize
5129+ 当 HashMap 中的元素个数超过 `(数组长度)*loadFactor(负载因子)` 或者链表过长时(链表长度 > 8,数组长度 < 64),就会进行数组扩容,创建新的数组,伴随一次重新 hash 分配,并且遍历 hash 表中所有的元素非常耗时,所以要尽量避免 resize
51345130
51355131 扩容机制为扩容为原来容量的 2 倍:
51365132
@@ -5147,7 +5143,7 @@ HashMap继承关系如下图所示:
51475143 }
51485144 else if (oldThr > 0) // 初始化的threshold赋值给newCap
51495145 newCap = oldThr;
5150- else { // zero initial threshold signifies using defaults
5146+ else {
51515147 newCap = DEFAULT_INITIAL_CAPACITY;
51525148 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
51535149 }
@@ -5161,7 +5157,7 @@ HashMap继承关系如下图所示:
51615157
51625158 
51635159
5164- 普通节点:把所有节点分成两个链表,
5160+ 普通节点:把所有节点分成高低位两个链表,转移到数组
51655161
51665162 ```java
51675163 // 遍历所有的节点
@@ -5213,13 +5209,14 @@ HashMap继承关系如下图所示:
52135209
52145210
52155211* remove()
5216- 删除是首先先找到元素的位置,如果是链表就遍历链表找到元素之后删除。如果是用红黑树就遍历树然后找到之后做删除,树小于6的时候要转链表
5212+ 删除是首先先找到元素的位置,如果是链表就遍历链表找到元素之后删除。如果是用红黑树就遍历树然后找到之后做删除,树小于 6 的时候退化为链表
52175213
52185214 ```java
52195215 final Node<K,V> removeNode(int hash, Object key, Object value,
52205216 boolean matchValue, boolean movable) {
52215217 Node<K,V>[] tab; Node<K,V> p; int n, index;
5222- //节点数组tab不为空、数组长度n大于0、根据hash定位到的节点对象p,该节点为树的根节点或链表的首节点)不为空,从该节点p向下遍历,找到那个和key匹配的节点对象
5218+ // 节点数组tab不为空、数组长度n大于0、根据hash定位到的节点对象p,
5219+ // 该节点为树的根节点或链表的首节点)不为空,从该节点p向下遍历,找到那个和key匹配的节点对象
52235220 if ((tab = table) != null && (n = tab.length) > 0 &&
52245221 (p = tab[index = (n - 1) & hash]) != null) {
52255222 Node<K,V> node = null, e; K k; V v;//临时变量,储存要返回的节点信息
@@ -5267,9 +5264,9 @@ HashMap继承关系如下图所示:
52675264 return null;
52685265 }
52695266 ```
5270-
52715267
5272-
5268+
5269+
52735270* get()
52745271
52755272 1. 通过hash值获取该key映射到的桶
@@ -9626,7 +9623,7 @@ Java 虚拟机栈:Java Virtual Machine Stacks,**每个线程**运行时所
96269623
96279624* 每个方法被执行时,都会在虚拟机栈中创建一个栈帧 stack frame(**一个方法一个栈帧**)
96289625
9629- * java虚拟机规范允许 **Java 栈的大小是动态的或者是固定不变的**
9626+ * Java 虚拟机规范允许 **Java 栈的大小是动态的或者是固定不变的**
96309627
96319628* 虚拟机栈是**每个线程私有的**,每个线程只能有一个活动栈帧,对应方法调用到执行完成的整个过程
96329629
@@ -9650,7 +9647,7 @@ Java 虚拟机栈:Java Virtual Machine Stacks,**每个线程**运行时所
96509647* 栈内存分配越大越大,可用的线程数越少(内存越大,每个线程拥有的内存越大)
96519648
96529649* 方法内的局部变量是否**线程安全**:
9653- * 如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
9650+ * 如果方法内局部变量没有逃离方法的作用访问,它是线程安全的(逃逸分析)
96549651 * 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
96559652
96569653异常:
@@ -10147,10 +10144,10 @@ JVM 是将 TLAB 作为内存分配的首选,但不是所有的对象实例都
1014710144
1014810145#### 逃逸分析
1014910146
10150- 即时编译(Just-in-time Compilation,JIT)是一种通过在运行时将字节码翻译为机器码,从而改善字节码编译语言性能的技术,在HotSpot实现中有多种选择 :C1、C2 和 C1+C2,分别对应 client、server 和分层编译
10147+ 即时编译(Just-in-time Compilation,JIT)是一种通过在运行时将字节码翻译为机器码,从而改善性能的技术,在 HotSpot 实现中有多种选择 :C1、C2 和 C1+C2,分别对应 client、server 和分层编译
1015110148
10152- * C1编译速度快 ,优化方式比较保守;C2编译速度慢 ,优化方式比较激进
10153- * C1+C2在开始阶段采用C1编译,当代码运行到一定热度之后采用G2重新编译
10149+ * C1 编译速度快 ,优化方式比较保守;C2 编译速度慢 ,优化方式比较激进
10150+ * C1+C2 在开始阶段采用 C1 编译,当代码运行到一定热度之后采用 C2 重新编译
1015410151
1015510152**逃逸分析**:并不是直接的优化手段,而是一个代码分析,通过动态分析对象的作用域,为其它优化手段如栈上分配、标量替换和同步消除等提供依据,发生逃逸行为的情况有两种:方法逃逸和线程逃逸
1015610153
@@ -10161,25 +10158,26 @@ JVM 是将 TLAB 作为内存分配的首选,但不是所有的对象实例都
1016110158
1016210159如果不存在逃逸行为,则可以对该对象进行如下优化:同步消除、标量替换和栈上分配
1016310160
10164- * ** 同步消除**
10161+ * 同步消除
1016510162
10166- 线程同步本身比较耗时,如果确定一个对象不会逃逸出线程,不被其它线程访问到,那对象的读写就不会存在竞争,则可以消除对该对象的**同步锁**,通过`-XX:+EliminateLocks`可以开启同步消除 ( - 号关闭)
10163+ 线程同步本身比较耗时,如果确定一个对象不会逃逸出线程,不被其它线程访问到,那对象的读写就不会存在竞争,则可以消除对该对象的**同步锁**,通过 `-XX:+EliminateLocks` 可以开启同步消除 ( - 号关闭)
1016710164
10168- * ** 标量替换**
10165+ * 标量替换
1016910166
1017010167 * 标量替换:如果把一个对象拆散,将其成员变量恢复到基本类型来访问
10171- * 标量 (scalar) :不可分割的量,如基本数据类型和reference类型
10168+ * 标量 (scalar) :不可分割的量,如基本数据类型和 reference 类型
10169+
1017210170 聚合量 (Aggregate):一个数据可以继续分解,对象一般是聚合量
1017310171 * 如果逃逸分析发现一个对象不会被外部访问,并且该对象可以被拆散,那么经过优化之后,并不直接生成该对象,而是将该对象成员变量分解若干个被这个方法使用的成员变量所代替
1017410172 * 参数设置:
1017510173 `-XX:+EliminateAllocations`:开启标量替换
1017610174 `-XX:+PrintEliminateAllocations`:查看标量替换情况
1017710175
10178- * ** 栈上分配**
10176+ * 栈上分配
1017910177
10180- JIT编译器在编译期间根据逃逸分析的结果 ,如果一个对象没有逃逸出方法的话,就可能被优化成栈上分配。分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收,这样就无需GC
10178+ JIT 编译器在编译期间根据逃逸分析的结果 ,如果一个对象没有逃逸出方法的话,就可能被优化成栈上分配。分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收,这样就无需 GC
1018110179
10182- User对象的作用域局限在方法fn中 ,可以使用标量替换的优化手段在栈上分配对象的成员变量,这样就不会生成User对象,大大减轻GC的压力
10180+ User 对象的作用域局限在方法 fn 中 ,可以使用标量替换的优化手段在栈上分配对象的成员变量,这样就不会生成 User 对象,大大减轻 GC 的压力
1018310181
1018410182 ```java
1018510183 public class JVM {
@@ -12166,21 +12164,21 @@ ClassLoader 类常用方法:
1216612164protected Class<?> loadClass(String name, boolean resolve)
1216712165 throws ClassNotFoundException {
1216812166 synchronized (getClassLoadingLock(name)) {
12169- //调用当前类加载器的 findLoadedClass(name),检查当前类加载器是否已加载过指定 name 的类
12167+ // 调用当前类加载器的 findLoadedClass(name),检查当前类加载器是否已加载过指定 name 的类
1217012168 Class c = findLoadedClass(name);
1217112169
12172- //当前类加载器如果没有加载过
12170+ // 当前类加载器如果没有加载过
1217312171 if (c == null) {
1217412172 long t0 = System.nanoTime();
1217512173 try {
12176- //判断当前类加载器是否有父类加载器
12174+ // 判断当前类加载器是否有父类加载器
1217712175 if (parent != null) {
12178- //如果当前类加载器有父类加载器,则调用父类加载器的 loadClass(name,false)
12179- //父类加载器的 loadClass 方法,又会检查自己是否已经加载过
12176+ // 如果当前类加载器有父类加载器,则调用父类加载器的 loadClass(name,false)
12177+ // 父类加载器的 loadClass 方法,又会检查自己是否已经加载过
1218012178 c = parent.loadClass(name, false);
1218112179 } else {
12182- //当前类加载器没有父类加载器,说明当前类加载器是 BootStrapClassLoader
12183- //则调用 BootStrap ClassLoader 的方法加载类
12180+ // 当前类加载器没有父类加载器,说明当前类加载器是 BootStrapClassLoader
12181+ // 则调用 BootStrap ClassLoader 的方法加载类
1218412182 c = findBootstrapClassOrNull(name);
1218512183 }
1218612184 } catch (ClassNotFoundException e) { }
@@ -13476,7 +13474,7 @@ javap -v Demo.class:省略
1347613474
1347713475Java 是**半编译半解释型语言**,将解释执行与编译执行二者结合起来进行:
1347813476
13479- * 解释器:根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“翻译”为对应平台的本地机器指令执行
13477+ * 解释器:根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容翻译为对应平台的本地机器指令执行
1348013478* 即时编译器(JIT : Just In Time Compiler):虚拟机运行时将源代码直接编译成**和本地机器平台相关的机器码**后再执行,并存入 Code Cache,下次遇到相同的代码直接执行,效率高
1348113479
1348213480
@@ -13489,7 +13487,7 @@ Java 是**半编译半解释型语言**,将解释执行与编译执行二者
1348913487
1349013488HotSpot VM 采用**解释器与即时编译器并存的架构**,解释器和即时编译器能够相互协作,去选择最合适的方式来权衡编译本地代码和直接解释执行代码的时间
1349113489
13492- HostSpot JVM的默认执行方式 :
13490+ HostSpot JVM 的默认执行方式 :
1349313491
1349413492* 当程序启动后,解释器可以马上发挥作用立即执行,省去编译器编译的时间(解释器存在的**必要性**)
1349513493* 随着程序运行时间的推移,即时编译器逐渐发挥作用,根据热点探测功能,将有价值的字节码编译为本地机器指令,以换取更高的程序执行效率
@@ -13510,16 +13508,14 @@ HotSpot VM 可以通过 VM 参数设置程序执行方式:
1351013508
1351113509#### 热点探测
1351213510
13513- 热点代码:被 JIT 编译器编译的字节码,根据代码被调用执行的频率而定
13514-
13515- * 一个被多次调用的方法或者一个循环次数较多的循环体都可以被称之为热点代码
13516- * 这种编译方式发生在方法的执行过程中,也称为栈上替换,简称 **OSR (On StackReplacement) 编译**
13511+ 热点代码:被 JIT 编译器编译的字节码,根据代码被调用执行的频率而定,一个被多次调用的方法或者一个循环次数较多的循环体都可以被称之为热点代码
1351713512
13518- OSR 替换循环代码体的入口,C1、C2 替换的是方法调用的入口,OSR 编译后会出现方法的整段代码被编译了,但是只有循环体部分才执行编译后的机器码,其他部分仍是解释执行
13513+ 热点探测:JIT 编译器在运行时会针热点代码做出深度优化,将其直接编译为对应平台的本地机器指令进行缓存,以提升程序执行性能
1351913514
13520- 热点探测: JIT 编译器在运行时会针热点代码做出深度优化,将其直接编译为对应平台的本地机器指令进行缓存,以提升 Java 程序的执行性能
13515+ JIT 编译在默认情况是异步进行的,当触发某方法或某代码块的优化时,先将其放入编译队列,然后由编译线程进行编译,编译之后的代码放在 CodeCache 中,通过 `-XX:-BackgroundCompilation` 参数可以关闭异步编译
1352113516
13522- CodeCache 用于缓存编译后的机器码、动态生成的代码和本地方法代码 JNI,如果 CodeCache 区域被占满,编译器被停用,字节码将不会编译为机器码,应用程序继续运行,但运行速度会降低一个数量级,严重影响系统性能
13517+ * CodeCache 用于缓存编译后的机器码、动态生成的代码和本地方法代码 JNI
13518+ * 如果 CodeCache 区域被占满,编译器被停用,字节码将不会编译为机器码,应用程序继续运行,但运行性能会降低很多
1352313519
1352413520HotSpot VM 采用的热点探测方式是基于计数器的热点探测,为每一个方法都建立 2 个不同类型的计数器:方法调用计数器(Invocation Counter)和回边计数器(BackEdge Counter)
1352513521
@@ -13529,6 +13525,8 @@ HotSpot VM 采用的热点探测方式是基于计数器的热点探测,为每
1352913525
1353013526* 回边计数器:统计一个方法中循环体代码执行的次数,在字节码中控制流向后跳转的指令称为回边
1353113527
13528+ 如果一个方法中的循环体需要执行多次,可以优化为为栈上替换,简称 OSR (On StackReplacement) 编译,**OSR 替换循环代码体的入口,C1、C2 替换的是方法调用的入口**,OSR 编译后会出现方法的整段代码被编译了,但是只有循环体部分才执行编译后的机器码,其他部分仍是解释执行
13529+
1353213530
1353313531
1353413532***
@@ -13568,7 +13566,9 @@ C1 编译器会对字节码进行简单可靠的优化,耗时短,以达到
1356813566
1356913567* 内联缓存:是一种加快动态绑定的优化技术(方法调用部分详解)
1357013568
13571- C2 编译器进行耗时较长的优化以及激进优化,优化的代码执行效率更高,当激进优化的假设不成立时,再退回使用 C1 编译。C2 的优化主要是在全局层面,逃逸分析是优化的基础:标量替换、栈上分配、同步消除
13569+ C2 编译器进行耗时较长的优化以及激进优化,优化的代码执行效率更高,当激进优化的假设不成立时,再退回使用 C1 编译,这也是分层编译比直接使用 C2 逃逸分析进行编译的性能低,也会使用分层编译的原因
13570+
13571+ C2 的优化主要是在全局层面,逃逸分析是优化的基础:标量替换、栈上分配、同步消除
1357213572
1357313573VM 参数设置:
1357413574
@@ -13592,6 +13592,10 @@ VM 参数设置:
1359213592
1359313593
1359413594
13595+ 参考文章:https://www.jianshu.com/p/20bd2e9b1f03
13596+
13597+
13598+
1359513599***
1359613600
1359713601
0 commit comments