├── README.md ├── lib ├── asm-7.1.jar ├── cglib-3.3.0.jar ├── jol-core-0.16.jar └── lombok-1.18.20.jar └── src ├── com ├── poplar │ ├── bytecode │ │ ├── AutoBoxing.java │ │ ├── BasicStackExecutionProcess.java │ │ ├── ByteCodeTest1.java │ │ ├── ByteCodeTest2.java │ │ ├── ByteCodeTest3.java │ │ ├── ByteCodeTest4.java │ │ ├── DynamicDispatch.java │ │ ├── GenericType.java │ │ ├── IPlusPlusAndPlusPlusITest.java │ │ ├── JITCompiler.java │ │ ├── StaticDispatch.java │ │ └── Test.java │ ├── classload │ │ ├── BreakClassLoaderDefaultMechanism.java │ │ ├── ClassLoadTest.java │ │ ├── ClassLoadTest10.java │ │ ├── ClassLoadTest12.java │ │ ├── ClassLoadTest17_1.java │ │ ├── ClassLoadTest17_2.java │ │ ├── ClassLoadTest18.java │ │ ├── ClassLoadTest19.java │ │ ├── ClassLoadTest2.java │ │ ├── ClassLoadTest20.java │ │ ├── ClassLoadTest21.java │ │ ├── ClassLoadTest22.java │ │ ├── ClassLoadTest23.java │ │ ├── ClassLoadTest24.java │ │ ├── ClassLoadTest25.java │ │ ├── ClassLoadTest26.java │ │ ├── ClassLoadTest27.java │ │ ├── ClassLoadTest3.java │ │ ├── ClassLoadTest4.java │ │ ├── ClassLoadTest5.java │ │ ├── ClassLoadTest6.java │ │ ├── ClassLoadTest7.java │ │ ├── ClassLoadTest9.java │ │ ├── CustomClassLoader.java │ │ ├── CustomClassLoader2.java │ │ ├── DeadLoopClass.java │ │ ├── MyCat.java │ │ ├── MyPerson.java │ │ └── Simple.java │ ├── compiler │ │ └── JItAndInterpreterTest.java │ ├── concurrent │ │ ├── AtomicTest.java │ │ ├── Singleton.java │ │ ├── VectorTest.java │ │ ├── VectorTestImprove.java │ │ └── VolatileTest.java │ ├── gc │ │ ├── CMSGCTest.java │ │ ├── G1LogAnalysis.java │ │ ├── GCTest1.java │ │ ├── GCTest2.java │ │ ├── GCTest3.java │ │ └── GCTest4.java │ ├── jmm │ │ ├── MESICacheLinePaddingTest1.java │ │ └── MESICacheLinePaddingTest2.java │ └── memory │ │ ├── DeadLock.java │ │ ├── DirectMemoryOOM.java │ │ ├── FinalizeEscapeGC.java │ │ ├── MemoryTest4.java │ │ ├── OutOfHeapErrorTest.java │ │ ├── OutOfMataSpaceErrorTest.java │ │ ├── ReferenceCountGC.java │ │ ├── StackInfoTest.java │ │ └── StackOverflowErrorTest.java └── v2 │ ├── bean │ ├── Amos.java │ └── Eliza.java │ ├── bytecode │ └── ObjectMeta.java │ ├── classload │ └── CustomizeClassLoader.java │ └── memory │ └── AllotOnStack.java └── resources ├── JVM类加载器学习.md ├── images ├── 1574823017674.gif ├── 1574824343266.gif ├── G1.png ├── Snipaste_2019-11-07_14-51-04.png ├── acc.png ├── byte.png ├── classload.png ├── classload2.png ├── cms.png ├── compute_jmm.png ├── drrrr.png ├── dss.png ├── fieled.png ├── fieled1.png ├── gcroot.png ├── gg.png ├── hff.png ├── java_jmm.png ├── jvm.png ├── kernel_thread.png ├── loadmethod.png ├── memory.png ├── mix_thread.png ├── parnew.png ├── pool.png ├── qqq.png ├── re.png ├── region.png ├── rew.png ├── san.png ├── san2.png ├── san3.png ├── sanmark.gif ├── sans1.png ├── sans2.png ├── sans3.png ├── serial.png ├── stack.png ├── thread_status.png ├── user_thread.png └── vb.png ├── java内存模型.md ├── java内容结构.md └── java字节码.md /README.md: -------------------------------------------------------------------------------- 1 | # jvm-learn 2 | - 学习圣思源张龙的JVM学习笔记 3 | - 学习深入理解JVM周志明那本书后对学习张龙视频的补充 4 | - v2包中代码是最近学习图灵学院第四期代码 5 | -------------------------------------------------------------------------------- /lib/asm-7.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/lib/asm-7.1.jar -------------------------------------------------------------------------------- /lib/cglib-3.3.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/lib/cglib-3.3.0.jar -------------------------------------------------------------------------------- /lib/jol-core-0.16.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/lib/jol-core-0.16.jar -------------------------------------------------------------------------------- /lib/lombok-1.18.20.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/lib/lombok-1.18.20.jar -------------------------------------------------------------------------------- /src/com/poplar/bytecode/AutoBoxing.java: -------------------------------------------------------------------------------- 1 | package com.poplar.bytecode; 2 | 3 | /** 4 | * Created BY poplar ON 2019/12/4 5 | * 自动装箱与拆箱 6 | */ 7 | public class AutoBoxing { 8 | public static void main(String[] args) { 9 | Integer a = 1; 10 | Integer b = 2; 11 | Integer c = 3; 12 | Integer d = 3; 13 | Integer e = 321; 14 | Integer f = 321; 15 | Long g = 1L; 16 | //包装类的“==”运算再不遇到算数运算的晴空下不会自动拆箱。以及他们的equals方法不处理数据类型转型的关系 17 | //Integer值判断是否相等问题 要用equals判断不要用“==”判断 18 | System.out.println(c == d); //true 19 | System.out.println(e == f);//false 20 | System.out.println(c == (a + b));//false 21 | System.out.println(c.equals((a + b)));//true 22 | System.out.println(g == (a + b));//false 23 | System.out.println(g.equals((a + b)));//false 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/com/poplar/bytecode/BasicStackExecutionProcess.java: -------------------------------------------------------------------------------- 1 | package com.poplar.bytecode; 2 | 3 | /** 4 | * Created BY poplar ON 2019/12/4 5 | * 基于栈的解释器的执行过程概念模型 6 | */ 7 | public class BasicStackExecutionProcess { 8 | 9 | public int calc() { 10 | int a = 100; 11 | int b = 200; 12 | int c = 300; 13 | return (a + b) * c; 14 | 15 | /* 16 | public int calc(); 17 | descriptor: ()I 18 | flags: ACC_PUBLIC 19 | Code: 20 | stack=2, locals=4, args_size=1 21 | 0: bipush 100 执行地址偏移量为0 将100推送至栈顶 22 | 2: istore_1 执行地址偏移量为2 将栈顶的100出栈并存放到第一个局部变量Slot中 23 | 3: sipush 200 24 | 6: istore_2 25 | 7: sipush 300 26 | 10: istore_3 27 | 11: iload_1 执行地址偏移量为11 将局部变量中第一个Slot中的整型值复制到栈顶 28 | 12: iload_2 29 | 13: iadd 将栈顶的两个元素出栈并作整形加法,然后把结果重新入栈 30 | 14: iload_3 31 | 15: imul 将栈顶的两个元素出栈并作整形乘法,然后把结果重新入栈 32 | 16: ireturn 结束方法并将栈顶的值返回给方法调用者 33 | LineNumberTable: 34 | line 10: 0 35 | line 11: 3 36 | line 12: 7 37 | line 13: 11 38 | LocalVariableTable: 39 | Start Length Slot Name Signature 40 | 0 17 0 this Lcom/poplar/bytecode/BasicStackExecutionProcess; 41 | 3 14 1 a I 42 | 7 10 2 b I 43 | 11 6 3 c I 44 | */ 45 | } 46 | 47 | public static void main(String[] args) { 48 | BasicStackExecutionProcess process = new BasicStackExecutionProcess(); 49 | int res = process.calc(); 50 | System.out.println(res); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/com/poplar/bytecode/ByteCodeTest1.java: -------------------------------------------------------------------------------- 1 | package com.poplar.bytecode; 2 | 3 | /** 4 | * Created By poplar on 2019/11/9 5 | */ 6 | public class ByteCodeTest1 { 7 | private int a = 1; 8 | 9 | public int getA() { 10 | return a; 11 | } 12 | 13 | public void setA(int a) { 14 | this.a = a; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/com/poplar/bytecode/ByteCodeTest2.java: -------------------------------------------------------------------------------- 1 | package com.poplar.bytecode; 2 | 3 | /** 4 | * Created By poplar on 2019/11/9 5 | * 从字节码分析得出的结论: 6 | * 成员变量的初始化是在构造方法中完成的,有多少个构造方法,初始化指令就会调用几次 7 | * 静态成员变量同样是在clinit方法完成的,不管有多少个静态变量都是在该方法完成初始化 8 | */ 9 | public class ByteCodeTest2 { 10 | 11 | String str = "Welcome"; 12 | 13 | private int x = 5; 14 | 15 | public static Integer in = 10; 16 | 17 | public ByteCodeTest2(String str) { 18 | this.str = str; 19 | } 20 | 21 | public ByteCodeTest2(String str, int x) { 22 | this.str = str; 23 | this.x = x; 24 | } 25 | 26 | public ByteCodeTest2() { 27 | 28 | } 29 | 30 | public static void main(String[] args) { 31 | ByteCodeTest2 byteCodeTest2 = new ByteCodeTest2(); 32 | byteCodeTest2.setX(8); 33 | in = 20; 34 | } 35 | 36 | private synchronized void setX(int x) { 37 | this.x = x; 38 | } 39 | 40 | public void test(String str) { 41 | synchronized (this) {//给当前对象上锁 42 | System.out.println("Hello World"); 43 | } 44 | } 45 | 46 | //给类字节码码上锁 47 | public static synchronized void test() { 48 | } 49 | 50 | static { 51 | System.out.println(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/com/poplar/bytecode/ByteCodeTest3.java: -------------------------------------------------------------------------------- 1 | package com.poplar.bytecode; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.FileNotFoundException; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.net.ServerSocket; 8 | 9 | /** 10 | * Created By poplar on 2019/11/10 11 | * 对于Java类中的每一个实例方法(非static方法) ,其在编译后所生成的字节码当中,方法参数的数量总是会比源代码中方法数的数量多一个(this) , 12 | * 它位于方法的第一个参数位置处;这样,我们就可以在Java的实例方法中使用this来去访问当前对象的属性以及其他方法。 13 | * 这个操作是在编译期间完成的,即由javac编译器在编译的时候将对this的访问转化为对一个普通实例方法参数的访问; 14 | * 接下来在运行期间由JVM在调用实例方法时,自动向实例方法传入this参数.所以,在实例方法的局部变量表中,至少会有一个指向当前对象的局部变量 15 | */ 16 | 17 | /** 18 | * Java字节码对于异常的处理方式: 19 | * 1.统一采用异常表的方式来对异常进行处理; 20 | * 2.在jdk1.4.2之前的版本中,并不是使用异常表的方式对异常进行处理的,而是采用特定的指令方式; 21 | * 3.当异常处理存在finally语句块时,现代化的JVM采取的处理方式是将finally语句内的字节码拼接到每个catch语句块后面。 22 | * 也就是说,程序中存在多少个catch,就存在多少个finally块的内容。 23 | */ 24 | public class ByteCodeTest3{ 25 | 26 | public void test() throws IOException, FileNotFoundException { 27 | 28 | try { 29 | InputStream is = new FileInputStream("test.txt"); 30 | 31 | ServerSocket serverSocket = new ServerSocket(9999); 32 | serverSocket.accept(); 33 | throw new RuntimeException(); 34 | 35 | } catch (FileNotFoundException ex) { 36 | 37 | } catch (IOException ex) { 38 | 39 | } catch (Exception ex) { 40 | 41 | } finally { 42 | System.out.println("finally"); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/com/poplar/bytecode/ByteCodeTest4.java: -------------------------------------------------------------------------------- 1 | package com.poplar.bytecode; 2 | 3 | /** 4 | * Created By poplar on 2019/11/10 5 | * 静态解析的四种场:静态方法、父类方法、构造方法、私有方法。 6 | * 以上四种方法称为非虚方法,在类加载阶段将符号引用转换为直接引用。 7 | */ 8 | 9 | /** 10 | * 方法的静态分派。 11 | * Grandpa g1 = new Father(); 12 | * 以上代码, g1的静态类型是Grandpa,而g1的实际类型(真正指向的类型)是Father. 13 | * 我们可以得出这样一个结论:变量的静态类型是不会发生变化的,而变量的实际类型则是可以发生变化的(多态的一种体现) 14 | * 实际变量是在运行期方可确定 15 | */ 16 | public class ByteCodeTest4 { 17 | 18 | public void test(Grandpa grandpa) { 19 | System.out.println("Grandpa"); 20 | } 21 | 22 | public void test(Father father) { 23 | System.out.println("father"); 24 | } 25 | 26 | public void test(Son son) { 27 | System.out.println("Son"); 28 | } 29 | 30 | public static void main(String[] args) { 31 | ByteCodeTest4 byteCodeTest4 = new ByteCodeTest4(); 32 | //方法重载,是一种静态的行为,编译期就可以完全确定 33 | Grandpa g1 = new Father(); 34 | Grandpa g2 = new Son(); 35 | byteCodeTest4.test(g1);//Grandpa 36 | byteCodeTest4.test(g2);//Grandpa 37 | } 38 | } 39 | 40 | class Grandpa { 41 | 42 | } 43 | 44 | class Father extends Grandpa { 45 | 46 | } 47 | 48 | class Son extends Father { 49 | 50 | } -------------------------------------------------------------------------------- /src/com/poplar/bytecode/DynamicDispatch.java: -------------------------------------------------------------------------------- 1 | package com.poplar.bytecode; 2 | 3 | /** 4 | * Created BY poplar ON 2019/12/4 5 | * 动态分派的演示与证明: 6 | * 在动态分派中虚拟机是如何知道要调用那个方法的? 7 | */ 8 | public class DynamicDispatch { 9 | 10 | static abstract class Human { 11 | public abstract void hello(); 12 | } 13 | 14 | static class Man extends Human { 15 | @Override 16 | public void hello() { 17 | System.out.println("Hello Man"); 18 | } 19 | } 20 | 21 | static class Woman extends Human { 22 | @Override 23 | public void hello() { 24 | System.out.println("Hello Woman"); 25 | } 26 | } 27 | 28 | public static void main(String[] args) { 29 | Human man = new Man(); 30 | Human woMan = new Woman(); 31 | man.hello(); 32 | woMan.hello(); 33 | 34 | man = new Woman(); 35 | man.hello(); 36 | 37 | /*public static void main(java.lang.String[]); 38 | descriptor: ([Ljava/lang/String;)V 39 | flags: ACC_PUBLIC, ACC_STATIC 40 | Code: 41 | stack=2, locals=3, args_size=1 42 | 0: new #2 // class main/java/com/poplar/bytecode/DynamicDispatch$Man 43 | 3: dup 44 | 4: invokespecial #3 // Method main/java/com/poplar/bytecode/DynamicDispatch$Man."":()V 45 | 7: astore_1 46 | 8: new #4 // class main/java/com/poplar/bytecode/DynamicDispatch$Woman 47 | 11: dup 48 | 12: invokespecial #5 // Method main/java/com/poplar/bytecode/DynamicDispatch$Woman."":()V 49 | 15: astore_2 50 | 16: aload_1 从局部变量加载一个引用 aload1是加载索引为1的引用(man),局部变量有三个(0:args; 1 :man ; 2 :woMan) 51 | 17: invokevirtual #6 // Method main/java/com/poplar/bytecode/DynamicDispatch$Human.hello:()V 52 | 20: aload_2 加载引用woMan 53 | 21: invokevirtual #6 // Method main/java/com/poplar/bytecode/DynamicDispatch$Human.hello:()V 54 | 24: new #4 // class main/java/com/poplar/bytecode/DynamicDispatch$Woman 55 | 27: dup 56 | 28: invokespecial #5 // Method main/java/com/poplar/bytecode/DynamicDispatch$Woman."":()V 57 | 31: astore_1 58 | 32: aload_1 59 | 33: invokevirtual #6 // Method main/java/com/poplar/bytecode/DynamicDispatch$Human.hello:()V 60 | 36: return 61 | LineNumberTable: 62 | line 28: 0 63 | line 29: 8 64 | line 30: 16 65 | line 31: 20 66 | line 33: 24 67 | line 34: 32 68 | line 36: 36 69 | LocalVariableTable: 70 | Start Length Slot Name Signature 71 | 0 37 0 args [Ljava/lang/String; 72 | 8 29 1 man Lmain/java/com/poplar/bytecode/DynamicDispatch$Human; 73 | 16 21 2 woMan Lmain/java/com/poplar/bytecode/DynamicDispatch$Human; 74 | } 75 | invokevirtual 运行期执行的时候首先: 76 | 找到操作数栈顶的第一个元素它所指向对象的实际类型,在这个类型里边,然后查找和常量里边Human的方法描述符和方法名称都一致的 77 | 方法,如果在这个类型下,常量池里边找到了就会返回实际对象方法的直接引用。 78 | 79 | 如果找不到,就会按照继承体系由下往上(Man–>Human–>Object)查找,查找匹配的方式就是 80 | 上面描述的方式,一直找到位为止。如果一直找不到就会抛出异常。 81 | 82 | 比较方法重载(overload)和方法重写(overwrite),我们可以得出这样的结论: 83 | 方法重载是静态的,是编译器行为;方法重写是动态的,是运行期行为。 84 | ———————————————— 85 | 版权声明:本文为CSDN博主「魔鬼_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 86 | 原文链接:https://blog.csdn.net/wzq6578702/article/details/82712042 87 | */ 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/com/poplar/bytecode/GenericType.java: -------------------------------------------------------------------------------- 1 | package com.poplar.bytecode; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | /** 8 | * Created BY poplar ON 2019/12/4 9 | * java伪泛型存在的问题 10 | * 下啊面的代码不能编译 11 | */ 12 | public class GenericType { 13 | 14 | //由于编译器类型擦除导致方法参数签名相同成为方法重载失败的部分原因 15 | /* public static void method(List list) { 16 | System.out.println("list1"); 17 | }*/ 18 | 19 | public static void method(List list) { 20 | System.out.println("list2"); 21 | } 22 | 23 | public static void main(String[] args) { 24 | 25 | Map map = new HashMap<>(); 26 | map.put("hello", "你好"); 27 | System.out.println(map.get("hello")); 28 | //擦除法仅仅堆方法code属性中的字节码进行擦除,元数据还保留了原信息,这也是我们能够通过反射取得参数化类型的依据 29 | /* LocalVariableTypeTable: 30 | Start Length Slot Name Signature 31 | 8 29 1 map Ljava/util/Map;*/ 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/com/poplar/bytecode/IPlusPlusAndPlusPlusITest.java: -------------------------------------------------------------------------------- 1 | package com.poplar.bytecode; 2 | 3 | /** 4 | * Create BY poplar ON 2021/1/13 5 | * 通过字节码理解 i++ 和 ++i 此处使用的是 jclasslib bytecode view插件 6 | */ 7 | public class IPlusPlusAndPlusPlusITest { 8 | 9 | public static void main(String[] args) { 10 | int i = 1; 11 | //i = i++; 12 | i = ++i; 13 | System.out.println(i); 14 | } 15 | // i++字节码: 16 | /* 0 iconst_1 把1压栈 17 | 1 istore_1 把1从栈中弹出来放到局部变量表索引为1的变量上 18 | 2 iload_1 把局部变量表索引为1的值压入栈中 19 | 3 iinc 1 by 1 把局部变量表索引为1的值+1 20 | 6 istore_1 21 | 7 getstatic #2 22 | 10 iload_1 23 | 11 invokevirtual #3 24 | 14 return*/ 25 | 26 | // ++i的字节码 27 | /* 28 | 0 iconst_1 29 | 1 istore_1 30 | 2 iinc 1 by 1 31 | 5 iload_1 32 | 6 istore_1 33 | 7 getstatic #2 34 | 10 iload_1 35 | 11 invokevirtual #3 36 | 14 return*/ 37 | } 38 | -------------------------------------------------------------------------------- /src/com/poplar/bytecode/JITCompiler.java: -------------------------------------------------------------------------------- 1 | package com.poplar.bytecode; 2 | 3 | /** 4 | * Created BY poplar ON 2019/12/5 5 | * 测试即使编译 6 | * VM参数:-XX:+PrintCompilation 要求虚拟机在即时编译时将被编译成本地代码的 方法名称打印出来 其中带有“%”的输出说明是由回边计数器触发的 OSR编译 7 | */ 8 | public class JITCompiler { 9 | 10 | public static final int NUM = 15000; 11 | 12 | public static int doubleValue(int j) { 13 | for (int i = 0; i < 100000; i++) ; 14 | return j * 2; 15 | } 16 | 17 | public static long calcSum() { 18 | long sum = 0; 19 | for (int i = 0; i < 100; i++) { 20 | sum += doubleValue(i); 21 | } 22 | return sum; 23 | } 24 | 25 | public static void main(String[] args) { 26 | 27 | for (int i = 0; i < NUM; i++) { 28 | calcSum(); 29 | } 30 | } 31 | 32 | /* 260 31 % 3 com.poplar.bytecode.JITCompiler::doubleValue @ 2 (18 bytes) 33 | 260 32 3 com.poplar.bytecode.JITCompiler::doubleValue (18 bytes) 34 | 260 33 % 4 com.poplar.bytecode.JITCompiler::doubleValue @ 2 (18 bytes) 35 | 261 31 % 3 com.poplar.bytecode.JITCompiler::doubleValue @ -2 (18 bytes) made not entrant 36 | 261 34 4 com.poplar.bytecode.JITCompiler::doubleValue (18 bytes) 37 | 262 32 3 com.poplar.bytecode.JITCompiler::doubleValue (18 bytes) made not entrant 38 | 262 35 3 com.poplar.bytecode.JITCompiler::calcSum (26 bytes) 39 | 263 36 % 4 com.poplar.bytecode.JITCompiler::calcSum @ 4 (26 bytes) 40 | 265 37 4 com.poplar.bytecode.JITCompiler::calcSum (26 bytes) 41 | 267 35 3 com.poplar.bytecode.JITCompiler::calcSum (26 bytes) made not entrant*/ 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/com/poplar/bytecode/StaticDispatch.java: -------------------------------------------------------------------------------- 1 | package com.poplar.bytecode; 2 | 3 | /** 4 | * Created BY poplar ON 2019/12/4 5 | * 静态分派的演示与证明: 6 | */ 7 | public class StaticDispatch { 8 | 9 | static abstract class Human { 10 | 11 | } 12 | 13 | static class Man extends Human { 14 | 15 | } 16 | 17 | static class Woman extends Human { 18 | 19 | } 20 | 21 | public void hello(Human param) { 22 | System.out.println("Hello Human"); 23 | } 24 | 25 | public void hello(Man param) { 26 | System.out.println("Hello Man"); 27 | } 28 | 29 | public void hello(Woman param) { 30 | System.out.println("Hello Woman"); 31 | } 32 | 33 | public static void main(String[] args) { 34 | StaticDispatch dispatch = new StaticDispatch(); 35 | /*Human man = new Man(); 36 | Human woMan = new Woman(); 37 | dispatch.hello(man); 38 | dispatch.hello(woMan);*/ 39 | 40 | Human human = new Woman(); 41 | human = new Man(); 42 | dispatch.hello((Woman) human); 43 | dispatch.hello((Man) human); 44 | //java.lang.ClassCastException: main.java.com.poplar.bytecode.WoMan cannot be cast to main.java.com.poplar.bytecode.Man 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/com/poplar/bytecode/Test.java: -------------------------------------------------------------------------------- 1 | package com.poplar.bytecode; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | /** 8 | * Created BY poplar ON 2019/12/3 9 | * 由于编译器收集的顺序是由语句的源文件中出现的顺序决定的 10 | */ 11 | public class Test { 12 | static { 13 | i = 0; 14 | // System.out.println(i);//illegal forward reference 15 | } 16 | 17 | static int i = 1; 18 | 19 | public static void main(String[] args) { 20 | // int[][][] arr = new int[0][1][-1];//NegativeArraySizeException 21 | // System.out.println(arr); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/com/poplar/classload/BreakClassLoaderDefaultMechanism.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | 8 | /** 9 | * Create BY poplar ON 2021/1/13 10 | * 主要为了测试打破默认的双亲委派机制,有些时候这是很重要的 11 | * 可参考:https://www.cnblogs.com/june0816/p/10090428.html 12 | */ 13 | public class BreakClassLoaderDefaultMechanism extends ClassLoader { 14 | 15 | //由于双亲委派机制是在ClassLoader的这个方法中使用模板方法模式写死的,所以如果要打破默认的类加载机制,就必须重写这个方法 16 | @Override 17 | public Class loadClass(String name) throws ClassNotFoundException { 18 | File file = new File("E:\\logs\\", name.replace(".", File.separator).concat(".class")); 19 | 20 | if (!file.exists()) { 21 | return super.loadClass(name); 22 | } 23 | 24 | try { 25 | 26 | InputStream is = new FileInputStream(file); 27 | 28 | byte[] b = new byte[is.available()]; 29 | is.read(b); 30 | return defineClass(name, b, 0, b.length); 31 | } catch (IOException e) { 32 | e.printStackTrace(); 33 | } 34 | 35 | return super.loadClass(name); 36 | } 37 | 38 | public static void main(String[] args) throws ClassNotFoundException { 39 | //我加载E:\logs\目录下的同一个文件 40 | BreakClassLoaderDefaultMechanism loader = new BreakClassLoaderDefaultMechanism(); 41 | Class clazz = loader.loadClass("com.poplar.bean.User"); 42 | 43 | loader = new BreakClassLoaderDefaultMechanism(); 44 | Class clazzNew = loader.loadClass("com.poplar.bean.User"); 45 | 46 | System.out.println(clazz == clazzNew);//false 47 | System.out.println(clazz.getClassLoader()); 48 | System.out.println(clazzNew.getClassLoader()); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | *

对于静态字段来说,只有直接定义了该字段的类才会被初始化 5 | * 当一个类在初始化时,要求父类全部都已经初始化完毕

6 | *

-XX:+TraceClassLoading,用于追踪类的加载信息并打印出来

7 | *

jvm参数设置

8 | *

-XX:+option,表示开启option选项

9 | *

-XX:-option,表示关闭option选项

10 | *

-XX:option=value,表示将option的值设置为value

11 | */ 12 | public class ClassLoadTest { 13 | 14 | public static void main(String[] args) throws ClassNotFoundException { 15 | System.out.println(Parent.str1); 16 | ClassLoadTest.class.getClassLoader().loadClass(""); 17 | } 18 | } 19 | 20 | class Parent { 21 | static String str1 = "welcome Parent"; 22 | 23 | static { 24 | System.out.println("from Parent class"); 25 | } 26 | } 27 | 28 | class Child extends Parent { 29 | static String str2 = "welcome Child"; 30 | 31 | static { 32 | System.out.println("from Child class"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest10.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/7 5 | */ 6 | public class ClassLoadTest10 { 7 | 8 | static { 9 | System.out.println("ClassLoadTest10"); 10 | } 11 | 12 | public static void main(String[] args) { 13 | Parent2 parent2; 14 | parent2 = new Parent2(); 15 | System.out.println(Parent2.a); 16 | System.out.println(Child2.b); 17 | /*执行结果:由于父类已经初始化过了所以Parent2只输出一次 18 | * ClassLoadTest10 19 | * Parent2 20 | * 2 21 | * Child2 22 | * 3 23 | */ 24 | } 25 | } 26 | 27 | class Parent2 { 28 | static int a = 2; 29 | 30 | static { 31 | System.out.println("Parent2"); 32 | } 33 | } 34 | 35 | class Child2 extends Parent2 { 36 | static int b = 3; 37 | 38 | static { 39 | System.out.println("Child2"); 40 | } 41 | } -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest12.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/7 5 | * 调用类的loadClass并不是主使实用类,不会导致类的初始化 6 | */ 7 | public class ClassLoadTest12 { 8 | public static void main(String[] args) throws ClassNotFoundException { 9 | 10 | ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 11 | Class loadClass = classLoader.loadClass("com.poplar.classload.G"); 12 | System.out.println("-------------------------------"); 13 | Class clazz = Class.forName("com.poplar.classload.G");//反射会导致一个类的初始化 14 | System.out.println(clazz); 15 | //输出结果: 16 | //G 17 | //class com.poplar.classload.G 18 | } 19 | } 20 | 21 | class G { 22 | static { 23 | System.out.println("G"); 24 | } 25 | } -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest17_1.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/8 5 | */ 6 | public class ClassLoadTest17_1 { 7 | public static void main(String[] args) throws Exception { 8 | CustomClassLoader2 loader = new CustomClassLoader2(); 9 | 10 | Class clazz = loader.loadClass("com.poplar.classload.Simple"); 11 | System.out.println(clazz.hashCode()); 12 | //如果注释掉该行,就并不会实例化MySample对象,不会加载MyCat(可能预先加载) 13 | Object instance = clazz.newInstance();//实列化Simple和MyCat 14 | //MyCat是由加载MySample的加载器去加载的: 15 | //如果只删除classpath下的MyCat,则会报错,NoClassDefFoundError; 16 | //如果只删除classpath下的MySample,则由自定义加载器加载桌面上的MySample,由系统应用加载器加载MyCat。 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest17_2.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/8 5 | */ 6 | public class ClassLoadTest17_2 { 7 | public static void main(String[] args) throws Exception { 8 | CustomClassLoader2 loader = new CustomClassLoader2(); 9 | Class clazz = loader.loadClass("com.poplar.classload.Simple2"); 10 | System.out.println(clazz.hashCode()); 11 | //如果注释掉该行,就并不会实例化MySample对象,不会加载MyCat(可能预先加载) 12 | Object instance = clazz.newInstance();//实列化Simple和MyCat 13 | //修改MyCat2后,仍然删除classpath下的Simple2,留下MyCat2,程序报错 14 | //因为命名空间,父加载器找不到子加载器所加载的类,因此MyCat2找不到 15 | } 16 | } 17 | 18 | class MyCat2 { 19 | public MyCat2() { 20 | System.out.println("MyCat by load " + MyCat.class.getClassLoader()); 21 | System.out.println(Simple.class); 22 | } 23 | } 24 | 25 | class Simple2 { 26 | public Simple2() { 27 | System.out.println("Simple by Load " + Simple.class.getClassLoader()); 28 | new MyCat(); 29 | System.out.println(MyCat.class); 30 | } 31 | } -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest18.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/8 5 | */ 6 | public class ClassLoadTest18 { 7 | public static void main(String[] args) { 8 | System.out.println(System.getProperty("sun.boot.class.path"));//根加载器路径 9 | System.out.println(System.getProperty("java.ext.dirs"));//扩展类加载器路径 10 | System.out.println(System.getProperty("java.class.path"));//应用类加载器路径 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest19.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | import com.sun.crypto.provider.AESKeyGenerator; 4 | 5 | /** 6 | * Created By poplar on 2019/11/8 7 | * 各加载器的路径是可以修改的,修改后会导致运行失败,ClassNotFoundExeception 8 | */ 9 | public class ClassLoadTest19 { 10 | public static void main(String[] args) { 11 | //该类默认有扩展类加载器加载的,但是如果我们把该类默认的加载路劲修改后,就会报错 12 | AESKeyGenerator aesKeyGenerator = new AESKeyGenerator(); 13 | System.out.println(aesKeyGenerator.getClass().getClassLoader()); //ExtClassLoader@232204a1 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest2.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/6 5 | *

常量在编译阶段会存入到调用这个常量的方法所在的类的常量池中 6 | * 本质上,调用类并没有直接调用到定义常量的类,因此并不会触发定义常量的类的初始化 7 | * 注意:这里指的是将常量存到ClassLoadTest2的常量池中,之后ClassLoadTest2和Student就没有任何关系了。 8 | * 甚至我们可以将MyParent2的class文件删除

9 | *

常见的注记符

10 | *

助记符 ldc:表示将int、float或者String类型的常量值从常量池中推送至栈顶

11 | *

助记符 bipush:表示将单字节(-128-127)的常量值推送到栈顶

12 | *

助记符 sipush:表示将一个短整型值(-32768-32369)推送至栈顶

13 | *

助记符 iconst_1:表示将int型的1推送至栈顶(iconst_m1到iconst_5)

14 | */ 15 | public class ClassLoadTest2 { 16 | public static void main(String[] args) { 17 | System.out.println(Student.m); 18 | } 19 | } 20 | 21 | class Student { 22 | static final String str = "hello world"; 23 | static final short n = 127; 24 | static final int s = 1; 25 | static final int m = 6; 26 | 27 | static { 28 | System.out.println("Student static blocking"); 29 | } 30 | } -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest20.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | /** 6 | * Created By poplar on 2019/11/8 7 | */ 8 | public class ClassLoadTest20 { 9 | public static void main(String[] args) throws Exception { 10 | CustomClassLoader2 loader1 = new CustomClassLoader2(); 11 | CustomClassLoader2 loader2 = new CustomClassLoader2(); 12 | Class clazz1 = loader1.loadClass("com.poplar.classload.Person"); 13 | Class clazz2 = loader2.loadClass("com.poplar.classload.Person"); 14 | 15 | //clazz1和clazz均由应用类加载器加载的,第二次不会重新加载,结果为true 16 | System.out.println(clazz1 == clazz2); 17 | 18 | Object object1 = clazz1.newInstance(); 19 | Object object2 = clazz2.newInstance(); 20 | 21 | Method method = clazz1.getMethod("setPerson", Object.class); 22 | method.invoke(object1, object2); 23 | } 24 | } 25 | 26 | class Person { 27 | 28 | private Person person; 29 | 30 | public void setPerson(Object object) { 31 | this.person = (Person) object; 32 | } 33 | } -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest21.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | /** 6 | * Created By poplar on 2019/11/8 7 | * 1.每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类构成; 8 | * 2.在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类; 9 | * 3.在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类; 10 | * 4.同一命名空间内的类是互相可见的,非同一命名空间内的类是不可见的; 11 | * 5.子加载器可以见到父加载器加载的类,父加载器也不能见到子加载器加载的类。 12 | */ 13 | public class ClassLoadTest21 { 14 | 15 | public static void main(String[] args) throws Exception { 16 | CustomClassLoader2 loader1 = new CustomClassLoader2(); 17 | CustomClassLoader2 loader2 = new CustomClassLoader2(); 18 | Class clazz1 = loader1.loadClass("com.poplar.bean.User"); 19 | Class clazz2 = loader2.loadClass("com.poplar.bean.ClassLoaderTest"); 20 | //由于clazz1和clazz2分别有不同的类加载器所加载,所以他们处于不同的名称空间里 21 | System.out.println(clazz1 == clazz2);//false 22 | 23 | Object object1 = clazz1.newInstance(); 24 | Object object2 = clazz2.newInstance(); 25 | System.out.println(object1.getClass().getClassLoader()); 26 | System.out.println(object2.getClass().getClassLoader()); 27 | //Method method = clazz1.getMethod("setMyPerson", Object.class); 28 | //此处报错,loader1和loader2所处不用的命名空间 29 | //method.invoke(object1, object2); 30 | 31 | /* 32 | 输出结果: 33 | findClass,输出这句话说明我们自己的类加载器加载了指定的类 34 | findClass,输出这句话说明我们自己的类加载器加载了指定的类 35 | false 36 | Exception in thread "main" java.lang.reflect.InvocationTargetException 37 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 38 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 39 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 40 | at java.lang.reflect.Method.invoke(Method.java:498) 41 | at com.poplar.classload.ClassLoadTest21.main(ClassLoadTest21.java:25) 42 | Caused by: java.lang.ClassCastException: com.poplar.classload.MyPerson cannot be cast to com.poplar.classload.MyPerson*/ 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest22.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/8 5 | */ 6 | public class ClassLoadTest22 { 7 | 8 | static { 9 | System.out.println("ClassLoadTest22 invoked"); 10 | } 11 | 12 | //扩展类加载器只加载jar包,需要把class文件打成jar 13 | //此列子中将扩展类加载的位置改成了当前的classes目录 14 | public static void main(String[] args) { 15 | System.out.println(ClassLoadTest22.class.getClassLoader()); 16 | System.out.println(ClassLoadTest2.class.getClassLoader()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest23.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | import sun.misc.Launcher; 4 | 5 | /** 6 | * Created By poplar on 2019/11/8 7 | * 在运行期,一个Java类是由该类的完全限定名(binary name)和用于加载该类的定义类加载器所共同决定的。 8 | * 如果同样名字(完全相同限定名)是由两个不同的加载器所加载,那么这些类就是不同的,即便.class文件字节码相同,并且从相同的位置加载亦如此。 9 | * 在oracle的hotspot,系统属性sun.boot.class.path如果修改错了,则运行会出错: 10 | * Error occurred during initialization of VM 11 | * java/lang/NoClassDeFoundError: java/lang/Object 12 | */ 13 | public class ClassLoadTest23 { 14 | public static void main(String[] args) { 15 | System.out.println(System.getProperty("sun.boot.class.path"));//根加载器路径 16 | System.out.println(System.getProperty("java.ext.dirs"));//扩展类加载器路径 17 | System.out.println(System.getProperty("java.class.path"));//应用类加载器路径 18 | 19 | System.out.println(ClassLoader.class.getClassLoader()); 20 | //此处由于系统和扩展类加载器都是Launcher其内部静态类,但又都是非public的, 21 | // 所以不能直接获取他们的类加载器,方法就是通过获取他们的外部类加载器是谁?从而确当他们的类加载器。 22 | System.out.println(Launcher.class.getClassLoader()); 23 | System.out.println(Launcher.getLauncher()); 24 | 25 | //下面的系统属性指定系统类加载器,默认是AppClassLoader 26 | System.out.println(System.getProperty("java.system.class.loader")); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest24.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/9 5 | */ 6 | public class ClassLoadTest24 { 7 | 8 | /** 9 | * 当前类加载器(Current ClassLoader) 10 | * 每个类都会尝试使用自己的类加载器去加载依赖的类。 11 | *

12 | * 线程上下文类加载器(Context ClassLoader) 13 | * 线程上下文加载器 @ jdk1.2 14 | * 线程类中的 getContextClassLoader() 与 setContextClassLoader(ClassLoader c) 15 | * 如果没有通过setContextClassLoader()方法设置,线程将继承父线程的上下文类加载器, 16 | * java应用运行时的初始线程的上下文类加载器是系统类加载器。该线程中运行的代码可以通过该类加载器加载类和资源。 17 | *

18 | * 线程上下文类加载器的作用: 19 | * SPI:Service Provide Interface 20 | * 父ClassLoader可以使用当前线程Thread.currentThread().getContextClassLoader()所制定的ClassLoader加载的类, 21 | * 这就改变了父加载器加载的类无法使用子加载器或是其他没有父子关系的ClassLoader加载的类的情况,即改变了双亲委托模型。 22 | *

23 | * 在双亲委托模型下,类加载是由下至上的,即下层的类加载器会委托父加载器进行加载。但是对于SPI来说, 24 | * 有些接口是Java核心库所提供的的(如JDBC),Java核心库是由启动类记载器去加载的,而这些接口的实现却来自不同的jar包(厂商提供), 25 | * Java的启动类加载器是不会加载其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求。通过给当前线程设置上下文类加载器, 26 | * 就可以由设置的上下文类加载器来实现对于接口实现类的加载。 27 | */ 28 | public static void main(String[] args) { 29 | System.out.println(Thread.currentThread().getContextClassLoader()); 30 | System.out.println(Thread.class.getClassLoader()); 31 | System.out.println(System.getProperty("java.system.class.loader")); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest25.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/9 5 | */ 6 | public class ClassLoadTest25 implements Runnable { 7 | 8 | private Thread thread; 9 | 10 | public ClassLoadTest25() { 11 | thread = new Thread(this); 12 | thread.start(); 13 | } 14 | 15 | @Override 16 | public void run() { 17 | ClassLoader classLoader = thread.getContextClassLoader(); 18 | thread.setContextClassLoader(classLoader); 19 | System.out.println("Class: " + classLoader.getClass()); //Class: class sun.misc.Launcher$AppClassLoader 20 | System.out.println("Parent " + classLoader.getParent()); // Parent sun.misc.Launcher$ExtClassLoader@5b74b597 21 | } 22 | 23 | public static void main(String[] args) { 24 | new ClassLoadTest25(); 25 | } 26 | } 27 | //源码: 28 | /* public Launcher() { 29 | Launcher.ExtClassLoader var1; 30 | try { 31 | var1 = Launcher.ExtClassLoader.getExtClassLoader(); 32 | } catch (IOException var10) { 33 | throw new InternalError("Could not create extension class loader", var10); 34 | } 35 | 36 | try { 37 | //获取到系统类加载器 38 | this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); 39 | } catch (IOException var9) { 40 | throw new InternalError("Could not create application class loader", var9); 41 | } 42 | //把系统类加载器设置到当前线程的上下文类加载器中 43 | Thread.currentThread().setContextClassLoader(this.loader); 44 | }*/ -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest26.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | import java.sql.Driver; 4 | import java.util.Iterator; 5 | import java.util.ServiceLoader; 6 | 7 | /** 8 | * Created By poplar on 2019/11/9 9 | *

10 | * 线程上下文类加载器的一般使用模式:(获取-使用-还原) 11 | * 伪代码: 12 | * ClassLoader classLoader=Thread.currentThread().getContextLoader(); 13 | * try{ 14 | * Thread.currentThread().setContextLoader(targetTccl); 15 | * myMethod(); 16 | * }finally{ 17 | * Thread.currentThread().setContextLoader(classLoader); 18 | * } 19 | * 在myMethod中调用Thread.currentThread().getContextLoader()做某些事情 20 | * ContextClassLoader的目的就是为了破坏类加载委托机制 21 | *

22 | * 在SPI接口的代码中,使用线程上下文类加载器就可以成功的加载到SPI的实现类。 23 | *

24 | * 当高层提供了统一的接口让底层去实现,同时又要在高层加载(或实例化)底层的类时, 25 | * 就必须通过上下文类加载器来帮助高层的ClassLoader找到并加载该类。 26 | */ 27 | public class ClassLoadTest26 { 28 | public static void main(String[] args) { 29 | 30 | //一旦加入下面此行,将使用ExtClassLoader去加载Driver.class, ExtClassLoader不会去加载classpath,因此无法找到MySql的相关驱动。 31 | //Thread.getCurrentThread().setContextClassLoader(MyTest26.class.getClassLoader().parent()); 32 | 33 | //ServiceLoader服务提供者,加载实现的服务 34 | ServiceLoader loader = ServiceLoader.load(Driver.class); 35 | Iterator iterator = loader.iterator(); 36 | while (iterator.hasNext()) { 37 | Driver driver = iterator.next(); 38 | System.out.println("driver:" + driver.getClass() + ",loader" + driver.getClass().getClassLoader()); 39 | } 40 | System.out.println("当前上下文加载器" + Thread.currentThread().getContextClassLoader()); 41 | System.out.println("ServiceLoader的加载器" + ServiceLoader.class.getClassLoader()); 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest27.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | import java.net.URL; 4 | import java.util.Enumeration; 5 | 6 | /** 7 | * Created By poplar on 2019/11/9 8 | */ 9 | public class ClassLoadTest27 { 10 | //源码: 11 | /* private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) { 12 | boolean result = false; 13 | if(driver != null) { 14 | Class aClass = null; 15 | try { 16 | //到这儿时其实已经加载过了,再次加载主要是名称空间的问题,确保是在同一名称空间下 17 | aClass = Class.forName(driver.getClass().getName(), true, classLoader); 18 | } catch (Exception ex) { 19 | result = false; 20 | } 21 | result = ( aClass == driver.getClass() ) ? true : false; 22 | } 23 | 24 | return result; 25 | }*/ 26 | public static void main(String[] args) throws Exception { 27 | //Class clazz = Class.forName("com.mysql.jdbc.Driver"); 28 | //Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/user", "root", "123456"); 29 | 30 | /* jar hell问题以及解决办法 31 | 当一个类或者一个资源文件存在多个jar中,就会存在jar hell问题。 32 | 可通过以下代码解决问题:*/ 33 | ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 34 | String resource = "java/lang/String.class"; 35 | Enumeration urls = classLoader.getResources(resource); 36 | while (urls.hasMoreElements()) { 37 | URL element = urls.nextElement(); 38 | System.out.println(element); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest3.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * Created By poplar on 2019/11/6 7 | *

8 | * 当一个常量的值并非编译期间可以确定的,那么其值就不会放到调用类的常量池中 9 | * 这时在程序运行时,会导致主动使用这个常量所在的类,显然会导致这个类被初始化 10 | *

11 | */ 12 | public class ClassLoadTest3 { 13 | 14 | public static void main(String[] args) { 15 | System.out.println(Student2.str); //Student2, 6dbb23... 16 | } 17 | } 18 | 19 | class Student2 { 20 | static final String str = UUID.randomUUID().toString(); 21 | 22 | static { 23 | System.out.println("Student2"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest4.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/6 5 | *

对于数组实例来说,其类型是由JVM在运行期动态生成的,表示为 [L com.hisense.classloader.Student3 这种形式。 6 | * 对于数组来说,JavaDoc经构成数据的元素成为Component,实际上是将数组降低一个维度后的类型。 7 | *

8 | *

助记符:anewarray:表示创建一个引用类型(如类、接口)的数组,并将其引用值压入栈顶

9 | *

助记符:newarray:表示创建一个指定原始类型(int boolean float double)的数组,并将其引用值压入栈顶

10 | */ 11 | public class ClassLoadTest4 { 12 | public static void main(String[] args) { 13 | Student3 student3 = new Student3(); //创建类的实例,属于主动使用,会导致类的初始化 14 | Student3[] studentArr = new Student3[1]; //不是主动使用 15 | System.out.println(student3.getClass()); //输出 [com.poplar.classload.Student3 16 | System.out.println(studentArr.getClass().getSuperclass()); //输出Object 17 | System.out.println("----------------------------"); 18 | int[] i = new int[1]; 19 | System.out.println(i.getClass()); //输出 [ I 20 | System.out.println(i.getClass().getSuperclass()); //输出Object 21 | } 22 | } 23 | 24 | class Student3 { 25 | static { 26 | System.out.println("Student3 static block"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest5.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | /** 6 | * Created By poplar on 2019/11/7 7 | *

8 | * 当一个接口在初始化时,并不要求其父接口都完成了初始化 9 | * 只有在真正使用到父接口的时候(如引用接口中定义的常量),才会初始化 10 | *

11 | */ 12 | public class ClassLoadTest5 { 13 | public static void main(String[] args) { 14 | System.out.println(MyChild.b); 15 | } 16 | 17 | } 18 | 19 | interface Student5 { 20 | 21 | int a = 9; //前面省了public static final 22 | 23 | Thread thread = new Thread() { 24 | { 25 | System.out.println("thread 初始化了");//如果父接口初始化了这句应该输出 26 | } 27 | }; 28 | } 29 | 30 | interface MyChild extends Student5 { //接口属性默认是 public static final 31 | String b = LocalDateTime.now().toString(); 32 | } 33 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest6.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/7 5 | * 准备和初始化阶段静态变量赋值问题 6 | */ 7 | public class ClassLoadTest6 { 8 | 9 | public static void main(String[] args) { 10 | System.out.println(Singleton.getInstance()); 11 | System.out.println(Singleton.a);//1 12 | System.out.println(Singleton.b);//0 13 | System.out.println(ClassLoadTest6.class.getClassLoader()); 14 | } 15 | } 16 | 17 | class Singleton { 18 | 19 | public static int a; 20 | 21 | private static Singleton instance = new Singleton(); 22 | 23 | private Singleton() { 24 | a++; 25 | b++; 26 | System.out.println(a);//1 27 | System.out.println(b);//1 28 | } 29 | 30 | public static int b = 0; 31 | 32 | public static Singleton getInstance() { 33 | return instance; 34 | } 35 | } -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest7.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/7 5 | */ 6 | public class ClassLoadTest7 { 7 | public static void main(String[] args) { 8 | System.out.println(String.class.getClassLoader());//null 由于String是由根加载器加载,在rt.jar包下 9 | System.out.println(C.class.getClassLoader());//sun.misc.Launcher$AppClassLoader@73d16e93 10 | } 11 | } 12 | 13 | class C { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/com/poplar/classload/ClassLoadTest9.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/7 5 | */ 6 | public class ClassLoadTest9 { 7 | 8 | static { 9 | System.out.println("ClassLoadTest9"); 10 | } 11 | 12 | public static void main(String[] args) { 13 | System.out.println(Child1.a); 14 | } 15 | } 16 | 17 | class Parent1 { 18 | static int a = 9; 19 | 20 | static { 21 | System.out.println("Parent1"); 22 | } 23 | } 24 | 25 | class Child1 extends Parent1 { 26 | static int b = 0; 27 | 28 | static { 29 | System.out.println("Child1"); 30 | } 31 | } 32 | 33 | //最后输出顺序 34 | //ClassLoadTest9 35 | // Parent1 36 | //9 -------------------------------------------------------------------------------- /src/com/poplar/classload/CustomClassLoader.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | import java.io.*; 4 | 5 | /** 6 | * Created By poplar on 2019/11/7 7 | * 自定义类加载器 8 | */ 9 | public class CustomClassLoader extends ClassLoader { 10 | 11 | private String classLoaderName; 12 | 13 | private static final String filePost = ".class"; 14 | 15 | public CustomClassLoader(ClassLoader parent, String classLoaderName) { 16 | super(parent);//显示指定该类的父类加载器 17 | this.classLoaderName = classLoaderName; 18 | } 19 | 20 | public CustomClassLoader(String classLoaderName) { 21 | super();//将系统类加载器当作该类的父类加载器 22 | this.classLoaderName = classLoaderName; 23 | } 24 | 25 | @Override 26 | public Class findClass(String name) { 27 | System.out.println("findClass,输出这句话说明我们自己的类加载器加载了指定的类"); 28 | byte[] b = loadClassData(name); 29 | return defineClass(name, b, 0, b.length); 30 | } 31 | 32 | private byte[] loadClassData(String name) { 33 | InputStream is = null; 34 | byte[] data = null; 35 | ByteArrayOutputStream byteArrayOutputStream = null; 36 | 37 | try { 38 | name = name.replace(".", File.separator);//File.separator根据操作系统而变化 39 | is = new FileInputStream(new File(name + filePost)); 40 | byteArrayOutputStream = new ByteArrayOutputStream(); 41 | int len = 0; 42 | while (-1 != (len = is.read())) { 43 | byteArrayOutputStream.write(len); 44 | } 45 | data = byteArrayOutputStream.toByteArray(); 46 | 47 | } catch (Exception e) { 48 | e.printStackTrace(); 49 | } finally { 50 | try { 51 | is.close(); 52 | byteArrayOutputStream.close(); 53 | } catch (IOException e) { 54 | e.printStackTrace(); 55 | } 56 | return data; 57 | } 58 | } 59 | 60 | public static void test(ClassLoader classLoader) throws Exception { 61 | Class clazz = classLoader.loadClass("com.poplar.classload.ClassLoadTest"); 62 | Object instance = clazz.newInstance(); 63 | System.out.println(instance); 64 | } 65 | 66 | public static void main(String[] args) throws Exception { 67 | CustomClassLoader classLoader = new CustomClassLoader("load1"); 68 | test(classLoader); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/com/poplar/classload/CustomClassLoader2.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | import java.io.*; 4 | 5 | /** 6 | * Created By poplar on 2019/11/7 7 | * 自定义类加载器 8 | */ 9 | public class CustomClassLoader2 extends ClassLoader { 10 | 11 | private static final String filePost = ".class"; 12 | 13 | 14 | public static void main(String[] args) throws Exception { 15 | //类的卸载 16 | CustomClassLoader2 Loader2 = new CustomClassLoader2(); 17 | test1(Loader2); 18 | System.gc(); 19 | //Thread.sleep(10000); //jvisualvm 查看当前java进程 -XX:+TraceClassUnloading这个用于追踪类卸载的信息 20 | CustomClassLoader2 Loader3 = new CustomClassLoader2(); 21 | test1(Loader3); 22 | /* 23 | 执行结果: 24 | findClass,输出这句话说明我们自己的类加载器加载了指定的类 25 | com.poplar.classload.CustomClassLoader2@15db9742 26 | 2018699554 27 | ------------------------------------- 28 | findClass,输出这句话说明我们自己的类加载器加载了指定的类 29 | com.poplar.classload.CustomClassLoader2@4e25154f 30 | 1550089733*/ 31 | } 32 | 33 | private static void test1(CustomClassLoader2 loader2) throws InstantiationException, IllegalAccessException, ClassNotFoundException { 34 | Class clazz = loader2.loadClass("com.poplar.bean.User"); 35 | Object instance = clazz.newInstance(); 36 | System.out.println(instance.getClass().getClassLoader()); 37 | System.out.println(instance.hashCode()); 38 | System.out.println("-------------------------------------"); 39 | //运行结果:(此处测试建议把源码文件先删掉,不然idea会重新生成classes,还是会导致系统类加载器加载) 40 | } 41 | 42 | @Override 43 | public Class findClass(String name) throws ClassNotFoundException { 44 | System.out.println("findClass,输出这句话说明我们自己的类加载器加载了指定的类"); 45 | byte[] b = loadClassData(name); 46 | if (b == null) { 47 | //直接抛 ClassNotFoundException 48 | return super.findClass(name); 49 | } 50 | return defineClass(name, b, 0, b.length); 51 | } 52 | 53 | /** 54 | * 获取到指定路径为类文件的二进制字节数组 55 | * 56 | * @param name 57 | * @return 58 | */ 59 | private byte[] loadClassData(String name) { 60 | InputStream is = null; 61 | ByteArrayOutputStream baos = null; 62 | 63 | try { 64 | //File.separator根据操作系统而变化 65 | File file = new File("E:\\logs\\", name.replace(".", File.separator).concat(filePost)); 66 | is = new FileInputStream(file); 67 | baos = new ByteArrayOutputStream(); 68 | int len = 0; 69 | while (-1 != (len = is.read())) { 70 | baos.write(len); 71 | } 72 | return baos.toByteArray(); 73 | 74 | } catch (Exception e) { 75 | e.printStackTrace(); 76 | } finally { 77 | try { 78 | is.close(); 79 | baos.close(); 80 | } catch (IOException e) { 81 | e.printStackTrace(); 82 | } 83 | } 84 | return null; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/com/poplar/classload/DeadLoopClass.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created BY poplar ON 2019/12/3 5 | * JVM会保证一个类的方法在多线程的情况下被正确的枷锁,同步 6 | */ 7 | public class DeadLoopClass { 8 | static { 9 | if (true) {//此处如果不加if编译器会提示 initializer dose not complete normallyr 10 | System.out.println(Thread.currentThread() + " init DeadLoopClass"); 11 | while (true) { 12 | 13 | } 14 | } 15 | } 16 | 17 | public static void main(String[] args) { 18 | String string= new String(); 19 | new Thread(DeadLoopClass::test).start(); 20 | 21 | new Thread(DeadLoopClass::test).start(); 22 | } 23 | 24 | private static void test() { 25 | System.out.println(Thread.currentThread() + "start"); 26 | DeadLoopClass deadLoopClass = new DeadLoopClass(); 27 | System.out.println(Thread.currentThread() + "run over"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/com/poplar/classload/MyCat.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/8 5 | */ 6 | public class MyCat { 7 | public MyCat() { 8 | System.out.println("MyCat by load " + MyCat.class.getClassLoader()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/com/poplar/classload/MyPerson.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/8 5 | */ 6 | public class MyPerson { 7 | 8 | private MyPerson person; 9 | 10 | public void setMyPerson(Object object) { 11 | this.person = (MyPerson) object; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/com/poplar/classload/Simple.java: -------------------------------------------------------------------------------- 1 | package com.poplar.classload; 2 | 3 | /** 4 | * Created By poplar on 2019/11/8 5 | */ 6 | public class Simple { 7 | public Simple() { 8 | System.out.println("Simple by Load " + Simple.class.getClassLoader()); 9 | new MyCat(); 10 | } 11 | 12 | public void out(){ 13 | System.out.println("out.."); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/com/poplar/compiler/JItAndInterpreterTest.java: -------------------------------------------------------------------------------- 1 | package com.poplar.compiler; 2 | 3 | /** 4 | * Create BY poplar ON 2021/1/12 5 | * JVM的执行模式默认为混合模式 6 | *
 7 |  * C:\Users\poplar>java -version
 8 |  * java version "1.8.0_201"
 9 |  * Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
10 |  * Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)
11 |  * 
12 | * -Xint 禁止编译执行 13 | * -Xcomp 强制虚拟机运行于“编译模式” 14 | */ 15 | public class JItAndInterpreterTest { 16 | 17 | //inter i5-6200U,内存:8G 18 | //混合用时:4125 4309 4778 19 | //解释执行:过了几分钟都没完成 20 | //编译执行:4163 4030 3969 21 | 22 | public static void main(String[] args) { 23 | for (int i = 0; i < 10_0000; i++) { 24 | func(); 25 | } 26 | 27 | long start = System.currentTimeMillis(); 28 | for (int i = 0; i < 10_0000; i++) { 29 | func(); 30 | } 31 | long end = System.currentTimeMillis(); 32 | System.out.println(end - start); 33 | } 34 | 35 | 36 | public static void func() { 37 | for (long i = 0; i < 10_0000L; i++) { 38 | long j = i % 3; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/com/poplar/concurrent/AtomicTest.java: -------------------------------------------------------------------------------- 1 | package com.poplar.concurrent; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | 5 | /** 6 | * Created BY poplar ON 2019/12/7 7 | * Atomic变量自增测试 8 | */ 9 | public class AtomicTest { 10 | 11 | public static AtomicInteger race = new AtomicInteger(0); 12 | 13 | public static void increase() { 14 | race.incrementAndGet(); 15 | } 16 | 17 | public static final int THREAD_COUNT = 20; 18 | 19 | public static void main(String[] args) { 20 | for (int i = 0; i < THREAD_COUNT; i++) { 21 | new Thread(() -> { 22 | for (int j = 0; j < 10000; j++) { 23 | increase(); 24 | } 25 | }).start(); 26 | } 27 | 28 | //等待所有累加线程都结束,如果还有线程在运行,主线程就让出cpu资源 29 | while (Thread.activeCount() > 2) {//由于idea原因此处不能为一 30 | Thread.yield(); 31 | } 32 | System.out.println(race); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/poplar/concurrent/Singleton.java: -------------------------------------------------------------------------------- 1 | package com.poplar.concurrent; 2 | 3 | /** 4 | * Created BY poplar ON 2019/12/8 5 | * volatile静止指令重排序演示代码 6 | */ 7 | public class Singleton { 8 | 9 | private volatile static Singleton instance; 10 | 11 | private Singleton() { 12 | } 13 | 14 | public static Singleton getInstance() { 15 | if (instance == null) { 16 | synchronized (Singleton.class) { 17 | if (instance == null) { 18 | instance = new Singleton(); 19 | } 20 | } 21 | } 22 | return instance; 23 | } 24 | 25 | public static void main(String[] args) { 26 | Singleton.getInstance(); 27 | } 28 | } 29 | /* 30 | instance = new Singleton(); 31 | 这段代码可以分为如下的三个步骤: 32 | memory = allocate(); // 1:分配对象的内存空间 33 | ctorInstance(memory); // 2:初始化对象 34 | instance = memory; // 3:设置instance指向刚分配的内存地址 35 | 我们知道,编辑器和处理器会进行代码优化,而其中重要的一点是会将指令进行重排序。 36 | 上边的代码经过重排序后可能会变为: 37 | memory = allocate(); // 1:分配对象的内存空间 38 | instance = memory; // 3:设置instance指向刚分配的内存地址 39 | // 注意:此时对象尚未初始化 40 | ctorInstance(memory); // 2:初始化对象 41 | 42 | 代码对应的汇编的执行过程 43 | * 0x01a3de0f:mov $0x3375cdb0,%esi ;……beb0cd75 33 44 | ;{oop('Singleton')} 45 | 0x01a3de14:mov %eax,0x150(%esi) ;……89865001 0000 46 | 0x01a3de1a:shr $0x9,%esi ;……c1ee09 47 | 0x01a3de1d:movb $0x0,0x1104800(%esi) ;……c6860048 100100 48 | 0x01a3de24:lock addl$0x0,(%esp) ;……f0830424 00 49 | ;*put static instance 50 | ;- 51 | Singleton:getInstance@24 52 | 53 | 生成汇编码是lock addl $0x0, (%rsp), 在写操作(put static instance)之前使用了lock前缀,锁住了总线和对应的地址,这样其他的CPU写和读都要等待锁的释放。 54 | 当写完成后,释放锁,把缓存刷新到主内存。 55 | 加了 volatile之后,volatile在最后加了lock前缀,把前面的步骤锁住了,这样如果你前面的步骤没做完是无法执行最后一步刷新到内存的, 56 | 换句话说只要执行到最后一步lock,必定前面的操作都完成了。那么即使我们完成前面两步或者三步了,还没执行最后一步lock,或者前面一步执行了就切换线程2了, 57 | 线程B在判断的时候也会判断实例为空,进而继续进来由线程B完成后面的所有操作。当写完成后,释放锁,把缓存刷新到主内存。 58 | ———————————————— 59 | 版权声明:本文为CSDN博主「夏洛克卷」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 60 | 原文链接:https://blog.csdn.net/zx48822821/article/details/86589753 61 | 62 | * */ -------------------------------------------------------------------------------- /src/com/poplar/concurrent/VectorTest.java: -------------------------------------------------------------------------------- 1 | package com.poplar.concurrent; 2 | 3 | import java.util.Vector; 4 | 5 | /** 6 | * Created BY poplar ON 2019/12/8 7 | * 对vector线程安全的测试,通过对源码debug测试发现会出现 ArrayIndexOutOfBoundsException 8 | * 尽管这里使用到的Vector的get()、remove()和size()方法都是同步的, 但是在多线程的环境中, 9 | * 如果不在方法调用端做额外的同步措施的话,使用这段代码仍然是 不安全的,因为如果另一个线程恰好在错误的时间里删除了一个元素, 10 | * 导致序号i已经不再 可用的话,再用i访问数组就会抛出一个ArrayIndexOutOfBoundsException 11 | */ 12 | public class VectorTest { 13 | 14 | private static Vector vector = new Vector<>(); 15 | 16 | public static void main(String[] args) { 17 | while (true) { 18 | for (int i = 0; i < 10; i++) { 19 | vector.add(i); 20 | } 21 | 22 | new Thread(() -> { 23 | for (int i = 0; i < vector.size(); i++) { 24 | vector.remove(i); 25 | } 26 | }).start(); 27 | 28 | new Thread(() -> { 29 | for (int i = 0; i < vector.size(); i++) { 30 | System.out.println(vector.get(i)); 31 | } 32 | }).start(); 33 | 34 | while (Thread.activeCount() > 90) ; 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/com/poplar/concurrent/VectorTestImprove.java: -------------------------------------------------------------------------------- 1 | package com.poplar.concurrent; 2 | 3 | import java.util.Vector; 4 | 5 | /** 6 | * Created BY poplar ON 2019/12/8 7 | * 改进后debug源码未发现异常情况 8 | */ 9 | public class VectorTestImprove { 10 | private static Vector vector = new Vector<>(); 11 | 12 | public static void main(String[] args) { 13 | while (true) { 14 | for (int i = 0; i < 10; i++) { 15 | vector.add(i); 16 | } 17 | 18 | new Thread(() -> { 19 | synchronized (vector) { 20 | for (int i = 0; i < vector.size(); i++) { 21 | vector.remove(i); 22 | } 23 | } 24 | }).start(); 25 | 26 | new Thread(() -> { 27 | synchronized (vector) { 28 | for (int i = 0; i < vector.size(); i++) { 29 | System.out.println(vector.get(i)); 30 | } 31 | } 32 | }).start(); 33 | 34 | while (Thread.activeCount() > 90) ; 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/com/poplar/concurrent/VolatileTest.java: -------------------------------------------------------------------------------- 1 | package com.poplar.concurrent; 2 | 3 | /** 4 | * Created BY poplar ON 2019/12/7 5 | * volatile在并发环境下并非原子操作 6 | */ 7 | public class VolatileTest { 8 | 9 | public static volatile int race = 0; 10 | 11 | public static void increase() { 12 | race++; 13 | } 14 | 15 | public static final int THREAD_COUNT = 20; 16 | 17 | public static void main(String[] args) { 18 | for (int i = 0; i < THREAD_COUNT; i++) { 19 | new Thread(() -> { 20 | for (int j = 0; j < 10000; j++) { 21 | increase(); 22 | } 23 | }).start(); 24 | } 25 | 26 | //等待所有累加线程都结束,如果还有线程在运行,主线程就让出cpu资源 27 | while (Thread.activeCount() > 2) {//由于idea原因此处不能为一 28 | Thread.yield(); 29 | } 30 | System.out.println(race); 31 | } 32 | 33 | /* 34 | public static void increase(); 35 | descriptor: ()V 36 | flags: ACC_PUBLIC, ACC_STATIC 37 | Code: 38 | stack=2, locals=0, args_size=0 39 | 0: getstatic #2 // Field race:I 40 | 3: iconst_1 41 | 4: iadd 42 | 5: putstatic #2 // Field race:I 43 | 8: return 44 | 当getstatic指令把race的值取到操作栈顶时,volatile关键字保证了race的值在此 时是正确的,但是在执行iconst_1、iadd这些指令的时候, 45 | 其他线程可能已经把race的值加大 了,而在操作栈顶的值就变成了过期的数据,所以putstatic指令执行后就可能把较小的race值 同步回主内存之中 46 | */ 47 | } 48 | -------------------------------------------------------------------------------- /src/com/poplar/gc/CMSGCTest.java: -------------------------------------------------------------------------------- 1 | package com.poplar.gc; 2 | 3 | /** 4 | * Created BY poplar ON 2019/11/28 5 | */ 6 | public class CMSGCTest { 7 | public static void main(String[] args) { 8 | int size = 1024 * 1024; 9 | byte[] bytes1 = new byte[4 * size]; 10 | System.out.println("111111"); 11 | 12 | byte[] bytes2 = new byte[4 * size]; 13 | System.out.println("2222222"); 14 | 15 | byte[] bytes3 = new byte[4 * size]; 16 | System.out.println("33333333"); 17 | 18 | byte[] bytes4 = new byte[4 * size]; 19 | System.out.println("4444444"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/com/poplar/gc/G1LogAnalysis.java: -------------------------------------------------------------------------------- 1 | package com.poplar.gc; 2 | 3 | /** 4 | * Created BY poplar ON 2019/11/30 5 | * G1日志分析 6 | * 虚拟机相关参数: 7 | * -verbose:gc 8 | * -Xms10m 9 | * -Xmx10m 10 | * -XX:+UseG1GC 表示垃圾收集器使用G1 11 | * -XX:+PrintGCDetails 12 | * -XX:+PrintGCDateStamps 13 | * -XX:MaxGCPauseMillis=200m 设置垃圾收集最大停顿时间 14 | */ 15 | public class G1LogAnalysis { 16 | 17 | public static void main(String[] args) { 18 | int size = 1024 * 1024; 19 | byte[] bytes1 = new byte[size]; 20 | byte[] bytes2 = new byte[size]; 21 | byte[] bytes3 = new byte[size]; 22 | byte[] bytes4 = new byte[size]; 23 | System.out.println("hello world"); 24 | } 25 | } 26 | /** 27 | * GC日志: 28 | * 2019-11-30T16:13:41.663+0800: [GC pause (G1 Humongous Allocation【说明分配的对象超过了region大小的50%】) (young) (initial-mark), 0.0014516 secs] 29 | * [Parallel Time: 1.1 ms, GC Workers: 4【GC工作线程数】] 30 | * [GC Worker Start (ms): Min: 167.0, Avg: 167.1, Max: 167.1, Diff: 0.1]【几个垃圾收集工作的相关信息统计】 31 | * [Ext Root Scanning (ms): Min: 0.4, Avg: 0.4, Max: 0.4, Diff: 0.1, Sum: 1.6] 32 | * [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] 33 | * [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0] 34 | * [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] 35 | * [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] 36 | * [Object Copy (ms): Min: 0.6, Avg: 0.6, Max: 0.6, Diff: 0.0, Sum: 2.4] 37 | * [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] 38 | * [Termination Attempts: Min: 1, Avg: 1.3, Max: 2, Diff: 1, Sum: 5] 39 | * 【上面的几个步骤为YOUNG GC的固定执行步骤】 40 | * 阶段1:根扫描 41 | * 静态和本地对象被描 42 | * 阶段2:更新RS 43 | * 处理dirty card队列更新RS 44 | * 阶段3:处理RS 45 | * 检测从年轻代指向老年代的对象 46 | * 阶段4:对象拷贝 47 | * 拷贝存活的对象到survivor/old区域 48 | * 阶段5:处理引用队列 49 | * 软引用,弱引用,虚引用处理 50 | * [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2] 51 | * [GC Worker Total (ms): Min: 1.0, Avg: 1.1, Max: 1.1, Diff: 0.1, Sum: 4.2] 52 | * [GC Worker End (ms): Min: 168.1, Avg: 168.1, Max: 168.1, Diff: 0.0] 53 | * [Code Root Fixup: 0.0 ms] 54 | * [Code Root Purge: 0.0 ms] 55 | * [Clear CT: 0.1 ms]【清楚cardTable所花费时间】 56 | * [Other: 0.3 ms] 57 | * [Choose CSet: 0.0 ms] 58 | * [Ref Proc: 0.1 ms] 59 | * [Ref Enq: 0.0 ms] 60 | * [Redirty Cards: 0.1 ms] 61 | * [Humongous Register: 0.0 ms] 62 | * [Humongous Reclaim: 0.0 ms] 63 | * [Free CSet: 0.0 ms] 64 | * [Eden: 2048.0K(4096.0K)->0.0B【新生代清理后】(2048.0K) Survivors: 0.0B->1024.0K Heap: 3800.2K(10.0M)->2752.1K(10.0M)] 65 | * [Times: user=0.00 sys=0.00, real=0.01 secs] 66 | * 2019-11-30T16:13:41.671+0800: [GC concurrent-root-region-scan-start] 67 | * 2019-11-30T16:13:41.671+0800: [GC concurrent-root-region-scan-end, 0.0008592 secs] 68 | * 2019-11-30T16:13:41.671+0800: [GC concurrent-mark-start] 69 | * 2019-11-30T16:13:41.672+0800: [GC concurrent-mark-end, 0.0000795 secs] 70 | * 2019-11-30T16:13:41.672+0800: [GC remark 2019-11-30T16:13:41.672+0800: [Finalize Marking, 0.0001170 secs] 2019-11-30T16:13:41.672+0800: [GC ref-proc, 0.0002159 secs] 2019-11-30T16:13:41.672+0800: [Unloading, 0.0005800 secs], 0.0011024 secs] 71 | * [Times: user=0.00 sys=0.00, real=0.00 secs] 72 | * 2019-11-30T16:13:41.673+0800: [GC cleanup 4800K->4800K(10M), 0.0003239 secs] 73 | * [Times: user=0.00 sys=0.00, real=0.00 secs] 74 | * hello world 75 | * Heap 76 | * garbage-first heap total 10240K, used 4800K [0x00000000ff600000, 0x00000000ff700050, 0x0000000100000000) 77 | * region size 1024K【说明region默认大小】, 2 young (2048K), 1 survivors (1024K) 78 | * Metaspace used 3224K, capacity 4496K, committed 4864K, reserved 1056768K 79 | * class space used 350K, capacity 388K, committed 512K, reserved 1048576K 80 | */ -------------------------------------------------------------------------------- /src/com/poplar/gc/GCTest1.java: -------------------------------------------------------------------------------- 1 | package com.poplar.gc; 2 | 3 | /** 4 | * Created BY poplar ON 2019/11/27 5 | * 垃圾回收测试 6 | */ 7 | public class GCTest1 { 8 | 9 | /* 10 | -verbose:gc 输出冗余的gc信息 11 | -Xms20M 堆初始化大最小容量 12 | -Xmx20M 堆初始化最大容量 13 | -Xmn10M 新生代容量 14 | -XX:+PrintGCDetails 15 | -XX:SurvivorRatio=8 配置新生代和survivor的大小比例为8:1:1 16 | */ 17 | 18 | public static void main(String[] args) { 19 | int size = 1024 * 1024; 20 | byte[] bytes1 = new byte[2 * size]; 21 | byte[] bytes2 = new byte[2 * size]; 22 | byte[] bytes3 = new byte[3 * size]; 23 | byte[] bytes4 = new byte[3 * size]; 24 | //当需要分配内存的对象的大小超出了新生代的容量时,对象会被直接分配到老年代 25 | System.out.println("hello world"); 26 | 27 | /* 28 | [GC (Allocation Failure)(表示发生GC的原因) [PSYoungGen(PS表示收集器类型): 8144K(收集前)->728K(收集后)(9216K) 29 | (新生代总的容量)] 8144K(推收集前)->6872K(堆收集后)(19456K)(堆总的容量), 0.0087417 secs(所用时间)] [Times: user(用户态收集所用时间)=0.02 sys=0.02系统态收集所用时间), real=0.01 secs] 30 | [Full GC (Ergonomics) [PSYoungGen: 728K->0K(9216K)] [ParOldGen: 6144K->6774K(10240K)] 6872K->6774K(19456K), [Metaspace: 3217K->3217K(1056768K)], 0.0070323 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 31 | hello world 32 | Heap 33 | PSYoungGen total 9216K, used 2287K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) 34 | eden space 8192K, 27% used [0x00000000ff600000,0x00000000ff83be00,0x00000000ffe00000) 35 | from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) 36 | to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) 37 | ParOldGen total 10240K, used 6774K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) 38 | object space 10240K, 66% used [0x00000000fec00000,0x00000000ff29daa8,0x00000000ff600000) 39 | Metaspace used 3225K, capacity 4496K, committed 4864K, reserved 1056768K 40 | class space used 350K, capacity 388K, committed 512K, reserved 1048576K 41 | */ 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/com/poplar/gc/GCTest2.java: -------------------------------------------------------------------------------- 1 | package com.poplar.gc; 2 | 3 | /** 4 | * Created BY poplar ON 2019/11/27 5 | *

6 | * -verbose:gc 输出冗余的gc信息 7 | * -Xms20M 堆初始化大最小容量 8 | * -Xmx20M 堆初始化最大容量 9 | * -Xmn10M 新生代容量 10 | * -XX:+PrintGCDetails 11 | * -XX:SurvivorRatio=8 配置新生代和survivor的大小比例为8:1:1 12 | * -XX:PretenureSizeThreshold=4194304 设置对象超过多大时直接分配到老年代 13 | * -XX:+UseSerialGC 表示指定垃圾收集器为SerialGC 14 | */ 15 | public class GCTest2 { 16 | 17 | public static void main(String[] args) { 18 | int size = 1024 * 1024; 19 | //GC发生在对象创建时,由于空间不足,JVM就会尝试执行垃圾回收,如果回收后空间还是不足,直接抛出异常OutOfMemoryError: Java heap space 20 | byte[] bytes1 = new byte[7 * size]; 21 | try { 22 | Thread.sleep(100000); 23 | } catch (InterruptedException e) { 24 | e.printStackTrace(); 25 | } 26 | System.out.println("hello world"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/com/poplar/gc/GCTest3.java: -------------------------------------------------------------------------------- 1 | package com.poplar.gc; 2 | 3 | /** 4 | * Created BY poplar ON 2019/11/27 5 | * MaxTenuringThreshold作用:在可以自动调节对象晋升(Promote) 到老年代阀值的GC中,设置该阀值的最大值。 6 | * 该参数的默认值为15,CMS中默认值为6, G1中默认为15 (在JVM中,该数值是由4个bit来表示的, 所以最大值1111,即15) 7 | * 经历了多次GC后,存活的对象会在From. Survivor与To Survivor之间来回存放,而这里面的一个前提则是这两个空间有足够的大小来存放这些数据,在GC算法中, 8 | * 会计算每个对象年龄的大小,如果达到某个年龄后发现总大小已经大子了Survivor空间的50%,那么这时就需要调整阀值,不能再继续等到默认的15次GC后才完成普升, 9 | * 因为这样会导致Survivor空间不足,所以需要调整阀值,让这些存活对象尽快完成晋升。 10 | */ 11 | public class GCTest3 { 12 | public static void main(String[] args) { 13 | int size = 1024 * 1024; 14 | byte[] bytes1 = new byte[2 * size]; 15 | byte[] bytes2 = new byte[2 * size]; 16 | byte[] bytes3 = new byte[3 * size]; 17 | byte[] bytes4 = new byte[3 * size]; 18 | System.out.println("hello world"); 19 | 20 | /* 21 | -verbose:gc 22 | -Xms20M 23 | -Xmx28M 24 | -Xmn10M 25 | -XX:+PrintGCDetails 26 | -XX:+PrintCommandLineFlags 打印虚拟机启动参数 27 | -Xx:SurvivorRatio=8 28 | -XX:MaxTenuringThreshold=5 29 | -XX:+PrintTenuringDistribution 30 | 31 | * */ 32 | //Desired survivor size 1048576 bytes, new threshold 5(该值jvm会动态调整但是不会超过max) (max 5) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/poplar/gc/GCTest4.java: -------------------------------------------------------------------------------- 1 | package com.poplar.gc; 2 | 3 | /** 4 | * Created BY poplar ON 2019/11/28 5 | * -verbose:gc 6 | * -Xmx200M 7 | * -Xmn50M 8 | * -XX:TargetSurvivorRatio=60 表明所有age的survivor space对象的大小如果超过Desired survivor size,则重新计算threshold 9 | * -XX:+PrintTenuringDistribution 打印对象年龄 10 | * -XX:+PrintGCDetails 11 | * -XX:+PrintGCDateStamps 打印收集时间 12 | * -XX:+UseConcMarkSweepGC 老年代使用cms收集器 13 | * -XX:+UseParNewGC 新生代使用ParNew收集器 14 | * -XX:MaxTenuringThreshold=3 设置晋升到老年代的阈值 15 | */ 16 | public class GCTest4 { 17 | 18 | public static void main(String[] args) throws InterruptedException { 19 | byte[] bytes1 = new byte[1024 * 1024]; 20 | byte[] bytes2 = new byte[1024 * 1024]; 21 | 22 | method(); 23 | Thread.sleep(1000); 24 | System.out.println("11111111"); 25 | 26 | method(); 27 | Thread.sleep(1000); 28 | System.out.println("222222222"); 29 | 30 | method(); 31 | Thread.sleep(1000); 32 | System.out.println("3333333333"); 33 | 34 | method(); 35 | Thread.sleep(1000); 36 | System.out.println("4444444444"); 37 | 38 | byte[] bytes3 = new byte[1024 * 1024]; 39 | byte[] bytes4 = new byte[1024 * 1024]; 40 | byte[] bytes5 = new byte[1024 * 1024]; 41 | 42 | method(); 43 | Thread.sleep(1000); 44 | System.out.println("5555555"); 45 | 46 | method(); 47 | Thread.sleep(1000); 48 | System.out.println("666666"); 49 | } 50 | 51 | public static void method() { 52 | for (int i = 0; i < 40; i++) { 53 | byte[] bytes = new byte[1024 * 1024]; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/com/poplar/jmm/MESICacheLinePaddingTest1.java: -------------------------------------------------------------------------------- 1 | package com.poplar.jmm; 2 | 3 | /** 4 | * Create BY poplar ON 2021/1/13 5 | * MESI相关概念参考:https://www.cnblogs.com/z00377750/p/9180644.html 6 | * inter一个缓存行为 64bit 7 | */ 8 | public class MESICacheLinePaddingTest1 { 9 | 10 | private static final int COUNT = 1000_0000; 11 | 12 | private static class T { 13 | public volatile long x = 0L; 14 | } 15 | 16 | public static T[] arr = new T[2]; 17 | 18 | //arr[0]和arr[1]在同一个缓存行中概率非常大 19 | static { 20 | arr[0] = new T(); 21 | arr[1] = new T(); 22 | } 23 | 24 | public static void main(String[] args) throws Exception { 25 | test(); 26 | } 27 | 28 | //使用两个线程去分别修改arr[0]和arr[1]中的值 29 | private static void test() throws InterruptedException { 30 | Thread t1 = new Thread(()->{ 31 | for (long i = 0; i < COUNT; i++) { 32 | arr[0].x = i; 33 | } 34 | }); 35 | 36 | Thread t2 = new Thread(()->{ 37 | for (long i = 0; i < COUNT; i++) { 38 | arr[1].x = i; 39 | } 40 | }); 41 | 42 | final long start = System.nanoTime(); 43 | t1.start(); 44 | t2.start(); 45 | t1.join(); 46 | t2.join(); 47 | System.out.println((System.nanoTime() - start)/100_0000);//inter i5-6200U 8G 时间: 292 279 249 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/com/poplar/jmm/MESICacheLinePaddingTest2.java: -------------------------------------------------------------------------------- 1 | package com.poplar.jmm; 2 | 3 | /** 4 | * Create BY poplar ON 2021/1/13 5 | * MESI相关概念参考:https://www.cnblogs.com/z00377750/p/9180644.html 6 | * inter一个缓存行为 64bit 7 | */ 8 | public class MESICacheLinePaddingTest2 { 9 | 10 | private static final int COUNT = 1000_0000; 11 | 12 | private static class Padding { 13 | public volatile long p1, p2, p3, p4, p5, p6, p7; 14 | } 15 | 16 | private static class T extends Padding { 17 | public volatile long x = 0L; 18 | } 19 | 20 | public static T[] arr = new T[2]; 21 | 22 | //保证arr[0]和arr[1]在不同的缓存行中 23 | static { 24 | arr[0] = new T(); 25 | arr[1] = new T(); 26 | } 27 | 28 | public static void main(String[] args) throws Exception { 29 | test(); 30 | } 31 | 32 | private static void test() throws InterruptedException { 33 | Thread t1 = new Thread(() -> { 34 | for (long i = 0; i < COUNT; i++) { 35 | arr[0].x = i; 36 | } 37 | }); 38 | 39 | Thread t2 = new Thread(() -> { 40 | for (long i = 0; i < COUNT; i++) { 41 | arr[1].x = i; 42 | } 43 | }); 44 | 45 | final long start = System.nanoTime(); 46 | t1.start(); 47 | t2.start(); 48 | t1.join(); 49 | t2.join(); 50 | System.out.println((System.nanoTime() - start) / 100_0000);//inter i5-6200U 8G 时间: 156 117 139 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/com/poplar/memory/DeadLock.java: -------------------------------------------------------------------------------- 1 | package com.poplar.memory; 2 | 3 | /** 4 | * Created BY poplar ON 2019/11/25 5 | * 死锁检测 6 | */ 7 | public class DeadLock { 8 | 9 | public static void main(String[] args) { 10 | 11 | new Thread(A::method, "Thread A").start(); 12 | 13 | new Thread(B::method, "Thread B").start(); 14 | } 15 | } 16 | 17 | class A { 18 | 19 | public synchronized static void method() { 20 | System.out.println("method from A"); 21 | try { 22 | Thread.sleep(3000);//为了让另外一个线程有机会拿到锁 23 | B.method(); 24 | } catch (InterruptedException e) { 25 | e.printStackTrace(); 26 | } 27 | } 28 | } 29 | 30 | class B { 31 | 32 | public synchronized static void method() { 33 | System.out.println("method from B"); 34 | try { 35 | Thread.sleep(3000); 36 | A.method(); 37 | } catch (InterruptedException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/com/poplar/memory/DirectMemoryOOM.java: -------------------------------------------------------------------------------- 1 | package com.poplar.memory; 2 | 3 | import sun.misc.Unsafe; 4 | 5 | import java.lang.reflect.Field; 6 | 7 | /** 8 | * Created BY poplar ON 2019/12/1 9 | * 本机直接内存溢出测试 10 | */ 11 | public class DirectMemoryOOM { 12 | 13 | private static final int _1MB = 1024 * 1024; 14 | 15 | public static void main(String[] args) throws IllegalAccessException { 16 | Field field = Unsafe.class.getDeclaredFields()[0]; 17 | System.out.println(field); 18 | field.setAccessible(true); 19 | Unsafe unsafe = (Unsafe) field.get(null); 20 | //并没有真正调用操作系统申请分配内存,只是通过计算得知内存无法分配,于是手动抛出异常 21 | while (true) { 22 | unsafe.allocateMemory(_1MB);//Exception in thread "main" java.lang.OutOfMemoryError 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/com/poplar/memory/FinalizeEscapeGC.java: -------------------------------------------------------------------------------- 1 | package com.poplar.memory; 2 | 3 | /** 4 | * Created BY poplar ON 2019/12/1 5 | * 此代码演示两点: 6 | * 1,对象可以在GC时自我拯救 7 | * 2,这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统调用一次 8 | */ 9 | public class FinalizeEscapeGC { 10 | 11 | private static FinalizeEscapeGC SAVE_HOOK = null; 12 | 13 | public void isLive() { 14 | System.out.println("yes,i am still alive :)"); 15 | } 16 | 17 | @Override 18 | protected void finalize() throws Throwable { 19 | super.finalize(); 20 | System.out.println("finalize method executed"); 21 | FinalizeEscapeGC.SAVE_HOOK = this; 22 | } 23 | 24 | public static void main(String[] args) throws InterruptedException { 25 | SAVE_HOOK = new FinalizeEscapeGC(); 26 | 27 | //对象第一次成功拯救自己 28 | SAVE_HOOK = null; 29 | System.gc(); 30 | 31 | //因为finalize()方法优先级很低,所以暂停0.5m 32 | Thread.sleep(500); 33 | if (SAVE_HOOK != null) { 34 | SAVE_HOOK.isLive(); 35 | } else { 36 | System.out.println("no, i am dead :("); 37 | } 38 | 39 | //和上面的代码一样,但是这次自我拯救失败了 40 | SAVE_HOOK = null; 41 | System.gc(); 42 | 43 | //因为finalize()方法优先级很低,所以暂停0.5m 44 | Thread.sleep(500); 45 | if (SAVE_HOOK != null) { 46 | SAVE_HOOK.isLive(); 47 | } else { 48 | System.out.println("no, i am dead :("); 49 | } 50 | 51 | //最后输出结果: 52 | //finalize method executed 该方法只执行了一次 53 | //yes,i am still alive :) 54 | //no, i am dead :( 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/com/poplar/memory/MemoryTest4.java: -------------------------------------------------------------------------------- 1 | package com.poplar.memory; 2 | 3 | /** 4 | * Created BY poplar ON 2019/11/26 5 | * jmam命令的使用 -clstats进程id to print class loader statistics 6 | * jmap -clstats 3740 7 | *

8 | * jstat -gc 3740 9 | * S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 10 | * 512.0 512.0 0.0 0.0 24064.0 9626.0 86016.0 1004.1 4864.0 3758.2 512.0 409.1 144 0.064 0 0.000 0.064 11 | * MC元空间总大小,MU元空间已使用的大小 12 | */ 13 | public class MemoryTest4 { 14 | public static void main(String[] args) { 15 | while (true) 16 | System.out.println("hello world"); 17 | } 18 | //查看java进程id jps -l 19 | // 使用jcmd查看当前进程的可用参数:jcmd 10368 help 20 | //查看jvm的启动参数 jcmd 10368 VM.flags 21 | // 10368:-XX:CICompilerCount=3 -XX:InitialHeapSize=132120576 -XX:MaxHeapSize=2111832064 -XX:MaxNewSize=703594496 22 | // -XX:MinHeapDeltaBytes=524288 -XX:NewSize=44040192 -XX:OldSize=88080384 -XX:+UseCompressedClassPointers 23 | // -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 24 | 25 | public void method() { 26 | Object object = new Object(); 27 | 28 | /*生成了2部分的内存区域,1)object这个引用变量,因为 29 | 是方法内的变量,放到JVM Stack里面,2)真正Object 30 | class的实例对象,放到Heap里面 31 | 上述 的new语句一共消耗12个bytes, JVM规定引用占4 32 | 个bytes (在JVM Stack), 而空对象是8个bytes(在Heap) 33 | 方法结束后,对应Stack中的变量马上回收,但是Heap 34 | 中的对象要等到GC来回收、*/ 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/com/poplar/memory/OutOfHeapErrorTest.java: -------------------------------------------------------------------------------- 1 | package com.poplar.memory; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Created BY poplar ON 2019/11/25 8 | * 关于Java对象创建的过程: 9 | * new关键字创建对象的3个步骤: 10 | * 1.在堆内存中创建出对象的实例。 11 | * 2.为对象的实例成员变量赋初值。 12 | * 3.将对象的引用返回 13 | * 指针碰撞(前提是堆中的空间通过一个指针进行分割,一侧是已经被占用的空间,另一侧是未被占用的空间) 14 | * 空闲列表(前提是堆内存空间中已被使用与未被使用的空间是交织在一起的,这时,虚拟机就需要通过一个列表来记录哪些空间是可以使用的, 15 | * 哪些空间是已被使用的,接下来找出可以容纳下新创建对象的且未被使用的空间,在此空间存放该对象,同时还要修改列表上的记录) 16 | * 对象在内存中的布局: 17 | * 1.对象头. 18 | * 2.实例数据(即我们在一个类中所声明的各项信息) 19 | * 3.对齐填充(可选) ! 20 | * 引用访问对象的方式: 21 | * 1.使用句柄的方式。 22 | * 2.使用直接指针的方式。 23 | * -verbose:gc 24 | * -Xms10m 25 | * -Xmx10m 26 | * -Xmn10m 27 | * -XX:+PrintGCDetails 28 | * -XX:SurvivorRatio=8 29 | */ 30 | public class OutOfHeapErrorTest { 31 | public static void main(String[] args) { 32 | //-Xms5m -Xmx5m -XX:+HeapDumpOnOutOfMemoryError 设置jvm对空间最小和最大以及遇到错误时把堆存储文件打印出来 33 | //打开jvisualvm装在磁盘上的转存文件 34 | List list = new ArrayList<>(); 35 | while (true) { 36 | list.add(new OutOfHeapErrorTest()); 37 | System.out.println("hello world"); 38 | //System.gc(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/com/poplar/memory/OutOfMataSpaceErrorTest.java: -------------------------------------------------------------------------------- 1 | package com.poplar.memory; 2 | 3 | import net.sf.cglib.proxy.Enhancer; 4 | import net.sf.cglib.proxy.MethodInterceptor; 5 | 6 | /** 7 | * Created BY poplar ON 2019/11/26 8 | * 元空间内存溢出测试 9 | * 设置元空间大小:-XX:MaxMetaspaceSize=100m 10 | * 关于元空间参考:https://www.infoq.cn/article/java-permgen-Removed 11 | */ 12 | public class OutOfMataSpaceErrorTest { 13 | public static void main(String[] args) { 14 | //使用动态代理动态生成类 15 | while (true) { 16 | Enhancer enhancer = new Enhancer(); 17 | enhancer.setSuperclass(OutOfMataSpaceErrorTest.class); 18 | enhancer.setUseCache(false); 19 | enhancer.setCallback((MethodInterceptor) (obj, method, ags, proxy) -> proxy.invokeSuper(obj, ags)); 20 | System.out.println("Hello World"); 21 | enhancer.create();// java.lang.OutOfMemoryError: Metaspace 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/com/poplar/memory/ReferenceCountGC.java: -------------------------------------------------------------------------------- 1 | package com.poplar.memory; 2 | 3 | /** 4 | * Created BY poplar ON 2019/12/1 5 | * 测试jvm是否是使用引用计数法判断对象是否存活 6 | * -verbose:gc 7 | * -Xms20M 8 | * -Xmx20M 9 | * -Xmn10M 10 | * -XX:+PrintGCDetails 11 | */ 12 | public class ReferenceCountGC { 13 | 14 | private Object instance = null; 15 | 16 | private static final int _1MB = 1024 * 1024; 17 | 18 | //只是为了占内存以便查看Gc日志 19 | private byte[] bytes = new byte[2 * _1MB]; 20 | 21 | public static void main(String[] args) { 22 | 23 | test();//6096K->744K说明虚拟机并没有因为这两个对象互相引用就不回收他们 24 | // [GC (System.gc()) [PSYoungGen: 6096K->744K(9216K)] 6096K->752K(19456K), 0.0074672 secs] 25 | // [Full GC (System.gc()) [PSYoungGen: 744K->0K(9216K)] [ParOldGen: 8K->630K(10240K)] 752K->6 26 | 27 | } 28 | 29 | private static void test() { 30 | ReferenceCountGC referenceCountGC1 = new ReferenceCountGC(); 31 | ReferenceCountGC referenceCountGC2 = new ReferenceCountGC(); 32 | referenceCountGC1.instance = referenceCountGC2; 33 | referenceCountGC2.instance = referenceCountGC1; 34 | 35 | referenceCountGC1 = null; 36 | referenceCountGC2 = null; 37 | 38 | System.gc(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/com/poplar/memory/StackInfoTest.java: -------------------------------------------------------------------------------- 1 | package com.poplar.memory; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Created BY poplar ON 2019/12/2 7 | * 下面的代码相当于jstack命令的大部分功能 8 | * jstack用于生成虚拟机当前时刻的快照 9 | */ 10 | public class StackInfoTest { 11 | public static void main(String[] args) { 12 | for (Map.Entry stackTrace : Thread.getAllStackTraces().entrySet()) { 13 | Thread thread = stackTrace.getKey(); 14 | StackTraceElement[] traceElements = stackTrace.getValue(); 15 | if (Thread.currentThread().equals(thread)) { 16 | continue; 17 | } 18 | System.out.println("线程名: " + thread.getName()); 19 | for (StackTraceElement element : traceElements) { 20 | //Each element represents a single stack frame 21 | System.out.println("\t" + element + "\n"); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/com/poplar/memory/StackOverflowErrorTest.java: -------------------------------------------------------------------------------- 1 | package com.poplar.memory; 2 | 3 | /** 4 | * Created BY poplar ON 2019/11/25 5 | * 虚拟机栈溢出测试 6 | * -Xss100k设置栈大小 7 | */ 8 | public class StackOverflowErrorTest { 9 | 10 | private int length; 11 | 12 | public int getLength() { 13 | return length; 14 | } 15 | 16 | public void test() throws InterruptedException { 17 | length++; 18 | Thread.sleep(1); 19 | test(); 20 | } 21 | 22 | public static void main(String[] args) { 23 | //测试调整虚拟机栈内存大小为: -Xss160k,此处除了可以使用JVisuale监控程序运行状况外还可以使用jconsole 24 | StackOverflowErrorTest stackOverflowErrorTest = new StackOverflowErrorTest(); 25 | try { 26 | stackOverflowErrorTest.test(); 27 | } catch (Throwable e) { 28 | System.out.println(stackOverflowErrorTest.getLength());//打印最终的最大栈深度为:2587 29 | e.printStackTrace(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/com/v2/bean/Amos.java: -------------------------------------------------------------------------------- 1 | package com.v2.bean; 2 | 3 | /** 4 | * BY Alex CREATED 2021/9/1 5 | */ 6 | 7 | 8 | public class Amos { 9 | public void out() { 10 | System.out.println(" Amos v2"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/com/v2/bean/Eliza.java: -------------------------------------------------------------------------------- 1 | package com.v2.bean; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * BY Alex CREATED 2021/9/3 7 | * Amos 人名 8 | */ 9 | 10 | @Data 11 | public class Eliza { 12 | 13 | private int id; 14 | 15 | private String name; 16 | 17 | private String[] hobby; 18 | 19 | private byte sex; 20 | } 21 | -------------------------------------------------------------------------------- /src/com/v2/bytecode/ObjectMeta.java: -------------------------------------------------------------------------------- 1 | package com.v2.bytecode; 2 | 3 | import com.v2.bean.Eliza; 4 | import org.openjdk.jol.info.ClassLayout; 5 | 6 | /** 7 | * BY Alex CREATED 2021/9/3 8 | * 类和类对象,对象相关 9 | * 10 | *

11 | * 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、 实例数据(Instance Data) 12 | * 和对齐填充(Padding)。 HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据, 如哈 13 | * 希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时 间戳等。对象头的另外一部分 14 | * 是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 15 | *

16 | *

17 | * 对象头在hotspot的C++源码markOop.hpp文件里的注释如下: 18 | *
19 | * 32 bits: 20 | * ‐‐‐‐‐‐‐‐ 21 | * hash:25 ‐‐‐‐‐‐‐‐‐‐‐‐>| age:4 biased_lock:1 lock:2 (normal object) 22 | * JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object) 23 | * size:32 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐>| (CMS free block) 24 | * PromotedObject*:29 ‐‐‐‐‐‐‐‐‐‐>| promo_bits:3 ‐‐‐‐‐>| (CMS promoted object) 25 | *

26 | * 64 bits: 27 | * ‐‐‐‐‐‐‐‐ 28 | * unused:25 hash:31 ‐‐>| unused:1 age:4 biased_lock:1 lock:2 (normal object) 29 | * JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object) 30 | * PromotedObject*:61 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐>| promo_bits:3 ‐‐‐‐‐>| (CMS promoted object) 31 | * size:64 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐>| (CMS free block) 32 | *

33 | * unused:25 hash:31 ‐‐>| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object) 34 | * JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object) 35 | * narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ‐‐‐‐‐>| (COOPs && CMS promoted object) 36 | * unused:21 size:35 ‐‐>| cms_free:1 unused:7 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐>| (COOPs && CMS free block) 37 | */ 38 | 39 | 40 | public class ObjectMeta { 41 | 42 | public static void main(String[] args) { 43 | ClassLayout layout = ClassLayout.parseInstance(new Object()); 44 | System.out.println(layout.toPrintable()); 45 | 46 | System.out.println(); 47 | ClassLayout layout1 = ClassLayout.parseInstance(new int[]{}); 48 | System.out.println(layout1.toPrintable()); 49 | 50 | System.out.println(); 51 | ClassLayout layout2 = ClassLayout.parseInstance(new Eliza()); 52 | System.out.println(layout2.toPrintable()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/com/v2/classload/CustomizeClassLoader.java: -------------------------------------------------------------------------------- 1 | package com.v2.classload; 2 | 3 | import java.io.FileInputStream; 4 | import java.lang.reflect.Method; 5 | 6 | /** 7 | * BY Alex CREATED 2021/9/1 8 | *

9 | * 打破双亲委派机制 10 | *
11 | * 自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是 12 | * loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空 13 | * 方法,所以我们自定义类加载器主要是重写findClass方法。而要打破双亲模型则需要重写 loadClass 14 | *

15 | *

16 | * 为什么要设计双亲委派机制? 17 | *
18 | * 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心 19 | * API库被随意篡改,例如:即使把 Object类拷贝出来,用自己写的类加载器加载,也是无法加载到 20 | * 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一 21 | * 次,保证被加载类的唯一性 22 | *

23 | * 注意:同一个JVM内,两个相同包名和类名的类对象可以共存,因为他们的类加载器可以不一 24 | * 样,所以看两个类对象是否是同一个,除了看类的包名和类名是否都相同之外,还需要他们的类 25 | * 加载器也是同一个才能认为他们是同一个。 26 | */ 27 | 28 | 29 | public class CustomizeClassLoader extends ClassLoader { 30 | 31 | private String classPath; 32 | 33 | private byte[] loadByte(String name) throws Exception { 34 | name = name.replaceAll("\\.", "/"); 35 | FileInputStream fis = new FileInputStream(getClassPath() + "/" + name + ".class"); 36 | int len = fis.available(); 37 | byte[] data = new byte[len]; 38 | fis.read(data); 39 | fis.close(); 40 | return data; 41 | } 42 | 43 | protected Class findClass(String name) throws ClassNotFoundException { 44 | try { 45 | byte[] data = loadByte(name); 46 | //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组 47 | return defineClass(name, data, 0, data.length); 48 | } catch (Exception e) { 49 | e.printStackTrace(); 50 | throw new ClassNotFoundException(); 51 | } 52 | } 53 | 54 | 55 | public static void main(String[] args) throws Exception { 56 | CustomizeClassLoader classLoader1 = new CustomizeClassLoader(); 57 | //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载 58 | //器设置为应用程序类加载器AppClassLoader 59 | classLoader1.setClassPath("E:/test"); 60 | 61 | Class clazz = classLoader1.loadClass("com.v2.bean.Amos"); 62 | Object obj = clazz.newInstance(); 63 | Method method = clazz.getDeclaredMethod("out", null); 64 | method.invoke(obj, null); 65 | System.out.println(clazz.getClassLoader().getClass()); 66 | 67 | //模拟tomcat 打破双亲模型和解决不同项目间存在的版本问题,对于每一个 项目都会生成一个对于的类加载器 68 | CustomizeClassLoader classLoader2 = new CustomizeClassLoader(); 69 | classLoader2.setClassPath("E:/test2"); 70 | clazz = classLoader2.loadClass("com.v2.bean.Amos"); 71 | obj = clazz.newInstance(); 72 | method = clazz.getDeclaredMethod("out", null); 73 | method.invoke(obj, null); 74 | System.out.println(clazz.getClassLoader().getClass()); 75 | 76 | /*String v1 77 | class com.v2.classload.CustomizeClassLoader 78 | String v2 79 | class com.v2.classload.CustomizeClassLoader*/ 80 | } 81 | 82 | //重写 loadClass 83 | @Override 84 | protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { 85 | synchronized (getClassLoadingLock(name)) { 86 | // First, check if the class has already been loaded 87 | Class c = findLoadedClass(name); 88 | if (c == null) { 89 | long t0 = System.nanoTime(); 90 | //非自定义的类,还是走以前的双亲模型 91 | if (!name.contains("com.v2")) { 92 | c = this.getParent().loadClass(name); 93 | } else { 94 | c = findClass(name); 95 | } 96 | long t1 = System.nanoTime(); 97 | 98 | // this is the defining class loader; record the stats 99 | sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 100 | sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 101 | sun.misc.PerfCounter.getFindClasses().increment(); 102 | } 103 | if (resolve) { 104 | resolveClass(c); 105 | } 106 | return c; 107 | } 108 | } 109 | 110 | public String getClassPath() { 111 | return classPath; 112 | } 113 | 114 | public void setClassPath(String classPath) { 115 | this.classPath = classPath; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/com/v2/memory/AllotOnStack.java: -------------------------------------------------------------------------------- 1 | package com.v2.memory; 2 | 3 | import com.v2.bean.Eliza; 4 | 5 | /** 6 | * BY Alex CREATED 2021/9/3 7 | * 栈上分配内存测试
8 | * 9 | *

10 | * 对象栈上分配 11 | * 我们通过JVM内存分配可以知道JAVA中的对象都是在堆上进行分配,当对象没有被引用的时候,需要依靠GC进行回收内存,如果对象数量较多的时候, 12 | * 会给GC带来较大压力,也间接影响了应用的性能。为了减少临时对象在堆内分配的数量,JVM通过逃逸分析确定该对象不会被外部访问。 13 | * 如果不会逃逸可以将该对象在栈上分配内存,这样该对象所占用的内存空间就可以随栈帧出栈而销毁,就减轻了垃圾回收的压力。 14 | * 对象逃逸分析:就是分析对象动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中。 15 | *
16 | */ 17 | 18 | 19 | public class AllotOnStack { 20 | 21 | /** 22 | * 栈上分配,标量替换 23 | * 代码调用了1亿次alloc(),如果是分配到堆上,大概需要1GB以上堆空间,如果堆空间小于该值,必然会触发GC。 24 | *

25 | * 使用如下参数不会发生GC 26 | * -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations 27 | * 使用如下参数都会发生大量GC 28 | * -Xmx15m -Xms15m -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations 29 | * -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations 30 | */ 31 | public static void main(String[] args) { 32 | long start = System.currentTimeMillis(); 33 | for (int i = 0; i < 100000000; i++) { 34 | alloc(); 35 | } 36 | long end = System.currentTimeMillis(); 37 | System.out.println(end - start); 38 | //测试发现jdk1.8似乎是默认开启的 39 | } 40 | 41 | private static void alloc() { 42 | Eliza eliza = new Eliza(); 43 | eliza.setId(1); 44 | eliza.setHobby(new String[]{}); 45 | eliza.setName("el"); 46 | eliza.setSex((byte) 1); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/resources/JVM类加载器学习.md: -------------------------------------------------------------------------------- 1 | # JVM类加载器学习 2 | 3 | ### 在以下几种情况下: 4 | 5 | - 执行了System。exit()方法 6 | - 程序正常执行结束 7 | - 程序在执行过程中遇到了异常或者错误而异常终止 8 | - 由于操作系统出现错误而导致Java虚拟机进程终止 9 | 10 | ### 类的加载执行与初始化 11 | 12 | - 加载:查找并加载类的二进制数据 13 | - 链接 14 | - 验证:确保被加载的类的正确性 15 | - 准备:为类的静态变量分配内存,并将其初始化为默认值 16 | - 解析:把类中的符号引用转换为直接引用 17 | 18 | - 初始化:为类的静态变量赋予正确的初始值 19 | 20 | - 值得注意的是:准备阶段即使我们为静态变量赋值为任意的数值,但是该静态变量还是会被初始化为他的默认值,最后的初始化时才会把我们赋予的值设为该静态变量的值。 21 | 22 | 23 | 24 | ### Java程序对类的使用可以分为两种 25 | 26 | 1. 主动使用 27 | - 创建类的实例 28 | - 访问某个类或接口的静态变量,或者对该静态变量赋值 29 | - 调用该类的静态方法 30 | - 反射 31 | - 初始化一个类的子类 32 | - Java虚拟机启动时被标为启动类的类(Java Test) 33 | 2. 被动使用 34 | 3. 所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们 35 | 36 | ------ 37 | 38 | 39 | 40 | - 类的加载:指的是将类的。class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个`Java.lang.Class`对象(规范并没有说明Class对象位于哪里,HotSpot虚拟机将其放在了方法区中)用来封装类在方法区的数据结构 41 | 42 | - 加载类的方式 43 | - 从本地系统中直接加载 44 | - 通过网络下载.class文件 45 | - 从zip,jar等归档文件中加载.class文件 46 | - 从专有数据库中提取.class文件 47 | - 将java源文件动态编译为.class文件(将JAVA源文件动态编译这种情况会在动态代理和web开发中jsp转换成Servlet) 48 | 49 | ### 完整加载过程 50 | 51 | 加载 52 | 连接(验证、准备、解析) 53 | 初始化 54 | 类的是例化:为新的对象分配内存,为实例变量赋默认值,为实例变量赋正确的初始值 55 | 56 | java编译器在它编译的每一个类都至少生成一个实例化的方法,在java的class文件中,这个实例化方法被称为。针对源代码中每一个类的构造方法,java编译器都会产生一个“”方法。 57 | 58 | ![完整加载过程](./images/classload.png) 59 | 60 | **时序图** 61 | 62 | ![时序图](./images/classload2.png) 63 | 64 | ### 有两种类型的类加载器 65 | 66 | 1. **Java虚拟机自带的加载器** 67 | 68 | - **根类加载器**(Bootstrap):该加载器没有父加载器,它负责加载虚拟机中的核心类库。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。类加载器的实现依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有集成java.lang.ClassLoader类。 69 | - **扩展类加载器**(Extension):它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre\lib\ext子目录(扩展目录)下加载类库,如果把用户创建的jar文件放在这个目录下,也会自动由扩展类加载器加载,扩展类加载器是纯java类,是java.lang.ClassLoader的子类。 70 | - **系统应用类加载器**(AppClassLoader/System):也称为应用类加载器,它的父加载器为扩展类加载器,它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,他是用户自定义的类加载器的默认父加载器。系统类加载器时纯java类,是java.lang.ClassLoader的子类。 71 | 72 | 1. **用户自定义的类加载器** 73 | 74 | - java.lang.ClassLoader的子类 75 | - 用户可以定制类的加载方式 76 | 77 | 根类加载器–>扩展类加载器–>系统应用类加载器–>自定义类加载器 78 | 类加载器并不需要等到某个类被“首次主动使用”时再加载它 79 | 80 | ![类加载顺序](./images/Snipaste_2019-11-07_14-51-04.png) 81 | 82 | JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在**程序首次主动**使用该类才报告错误(LinkageError错误),如果这个类没有被程序主动使用,那么类加载器就不会报告错误。 83 | 84 | 类加载器用来把类加载到java虚拟机中。从JDK1.2版本开始,类的加载过程采用父亲委托机制,这种机制能更好地保证Java平台的安全。在此委托机制中,除了java虚拟机自带的根类加载器以外,其余的类加载器都有且只有一个父加载器。当java程序请求加载器loader1加载Sample类时,loader1首先委托自己的父加载器去加载Sample类,若父加载器能加载,则有父加载器完成加载任务,否则才由加载器loader1本身加载Sample类。 85 | 86 | 类被加载后,就进入连接阶段。连接阶段就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。 87 | 88 | - 类的连接-验证 89 | 1)类文件的结构检查 90 | 2)语义检查 91 | 3)字节码验证 92 | 4)二进制兼容性的验证 93 | - 类的连接-准备 94 | 在准备阶段,java虚拟机为类的静态变量分配内存,并设置默认的初始值。例如对于以下Sample类,在准备阶段,将为int类型的静态变量a分配4个字节的内存空间,并且赋予默认值0,为long类型的静态变量b分配8个字节的内存空间,并且赋予默认值0; 95 | 96 | ```java 97 | public class Sample{ 98 | private static int a=1; 99 | public static long b; 100 | public static long c; 101 | static { 102 | b=2; 103 | } 104 | } 105 | 106 | ``` 107 | 108 | **初始化** 109 | 在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。在程序中,静态变量的初始化有两种途径:(1)在静态变量的声明处进行初始化;(2)在静态代码块中进行初始化。 110 | **类的初始化步骤**: 111 | (1)假如这个类还没有被加载和连接,那就先进行加载和连接 112 | (2)假如类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类 113 | (3)假如类中存在初始化语句,那就依次执行这些初始化语句 114 | 当java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,**但是这条规则不适用于接口**。因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定的接口的静态变量时,才会导致该接口的初始化。 115 | 116 | ``` 117 | * Created By poplar on 2019/11/7 118 | *

119 | * 当一个接口在初始化时,并不要求其父接口都完成了初始化 120 | * 只有在真正使用到父接口的时候(如引用接口中定义的常量),才会初始化 121 | *

122 | */ 123 | public class ClassLoadTest5 { 124 | public static void main(String[] args) { 125 | System.out.println(MyChild.b); 126 | } 127 | 128 | } 129 | 130 | interface Student5 { 131 | 132 | int a = 9; //前面省了public static final 133 | 134 | Thread thread = new Thread() { 135 | { 136 | System.out.println("thread 初始化了");//如果父接口初始化了这句应该输出 137 | } 138 | }; 139 | } 140 | 141 | interface MyChild extends Student5 { //接口属性默认是 public static final 142 | String b = LocalDateTime.now().toString(); 143 | } 144 | ``` 145 | - 获取类加载起的方法 146 | 147 | ![](./images/vb.png) 148 | 149 | 调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。 150 | 151 | ### 类加载器的(双亲委派机制)父亲委托机制 152 | 153 | 在父亲委托机制中,各个加载器按照父子关系形成了树形结构,除了根加载器之外,其余的类加载器都有一个父加载器 154 | 155 | - 若有一个类能够成功加载Test类,那么这个类加载器被称为**定义类加载器**,所有能成功返回Class对象引用的类加载器(包括定义类加载器)称为**初始类加载器**。 156 | 157 | ![类加载器的(双亲委派机制)父亲委托机制](./images/loadmethod.png) 158 | 159 | ### 类加载器测试7 160 | 161 | ```java 162 | package com.poplar.classload; 163 | 164 | /** 165 | * Created By poplar on 2019/11/7 166 | */ 167 | public class ClassLoadTest7 { 168 | public static void main(String[] args) { 169 | System.out.println(String.class.getClassLoader());//null 由于String是由根加载器加载,在rt.jar包下 170 | System.out.println(C.class.getClassLoader());//sun.misc.Launcher$AppClassLoader@73d16e93 171 | } 172 | } 173 | 174 | class C { 175 | 176 | } 177 | ``` 178 | 179 | ### 测试9: 180 | 181 | ```java 182 | package com.poplar.classload; 183 | 184 | /** 185 | * Created By poplar on 2019/11/7 186 | */ 187 | public class ClassLoadTest9 { 188 | 189 | static { 190 | System.out.println("ClassLoadTest9"); 191 | } 192 | 193 | public static void main(String[] args) { 194 | System.out.println(Child1.a); 195 | } 196 | } 197 | 198 | class Parent1 { 199 | static int a = 9; 200 | 201 | static { 202 | System.out.println("Parent1"); 203 | } 204 | } 205 | 206 | class Child1 extends Parent1 { 207 | static int b = 0; 208 | 209 | static { 210 | System.out.println("Child1"); 211 | } 212 | } 213 | 214 | //最后输出顺序 215 | //ClassLoadTest9 216 | // Parent1 217 | //9 218 | ``` 219 | 220 | ### 测试10: 221 | 222 | ```java 223 | package com.poplar.classload; 224 | 225 | /** 226 | * Created By poplar on 2019/11/7 227 | */ 228 | public class ClassLoadTest10 { 229 | 230 | static { 231 | System.out.println("ClassLoadTest10"); 232 | } 233 | 234 | public static void main(String[] args) { 235 | Parent2 parent2; 236 | parent2 = new Parent2(); 237 | System.out.println(Parent2.a); 238 | System.out.println(Child2.b); 239 | /*执行结果:由于父类已经初始化过了所以Parent2只输出一次 240 | * ClassLoadTest10 241 | * Parent2 242 | * 2 243 | * Child2 244 | * 3 245 | */ 246 | } 247 | } 248 | 249 | class Parent2 { 250 | static int a = 2; 251 | 252 | static { 253 | System.out.println("Parent2"); 254 | } 255 | } 256 | 257 | class Child2 extends Parent2 { 258 | static int b = 3; 259 | 260 | static { 261 | System.out.println("Child2"); 262 | } 263 | } 264 | ``` 265 | 266 | ### 测试12: 267 | 268 | ```java 269 | package com.poplar.classload; 270 | 271 | /** 272 | * Created By poplar on 2019/11/7 273 | * 调用类的loadClass并不是主使实用类,不会导致类的初始化 274 | */ 275 | public class ClassLoadTest12 { 276 | public static void main(String[] args) throws ClassNotFoundException { 277 | 278 | ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 279 | Class loadClass = classLoader.loadClass("com.poplar.classload.G"); 280 | System.out.println("-------------------------------"); 281 | Class clazz = Class.forName("com.poplar.classload.G");//反射会导致一个类的初始化 282 | System.out.println(clazz); 283 | //输出结果: 284 | //G 285 | //class com.poplar.classload.G 286 | } 287 | } 288 | 289 | class G { 1 290 | static { 291 | System.out.println("G"); 292 | } 293 | } 294 | ``` 295 | 296 | ### 测试16: 297 | 298 | ```java 299 | package com.poplar.classload; 300 | 301 | import java.io.*; 302 | 303 | /** 304 | * Created By poplar on 2019/11/7 305 | * 自定义类加载器 306 | */ 307 | public class CustomClassLoader extends ClassLoader { 308 | 309 | private String classLoaderName; 310 | private static final String filePost = ".class"; 311 | 312 | public CustomClassLoader(ClassLoader parent, String classLoaderName) { 313 | super(parent);//显示指定该类的父类加载器 314 | this.classLoaderName = classLoaderName; 315 | } 316 | 317 | public CustomClassLoader(String classLoaderName) { 318 | super();//将系统类加载器当作该类的父类加载器 319 | this.classLoaderName = classLoaderName; 320 | } 321 | 322 | @Override 323 | public Class findClass(String name) { 324 | System.out.println("findClass,输出这句话说明我们自己的类加载器加载了指定的类"); 325 | byte[] b = loadClassData(name); 326 | return defineClass(name, b, 0, b.length); 327 | } 328 | 329 | private byte[] loadClassData(String name) { 330 | InputStream is = null; 331 | byte[] data = null; 332 | ByteArrayOutputStream byteArrayOutputStream = null; 333 | 334 | try { 335 | name = name.replace(".", File.separator);//File.separator根据操作系统而变化 336 | is = new FileInputStream(new File(name + filePost)); 337 | byteArrayOutputStream = new ByteArrayOutputStream(); 338 | int len = 0; 339 | while (-1 != (len = is.read())) { 340 | byteArrayOutputStream.write(len); 341 | } 342 | data = byteArrayOutputStream.toByteArray(); 343 | 344 | } catch (Exception e) { 345 | e.printStackTrace(); 346 | } finally { 347 | try { 348 | is.close(); 349 | byteArrayOutputStream.close(); 350 | } catch (IOException e) { 351 | e.printStackTrace(); 352 | } 353 | return data; 354 | } 355 | } 356 | 357 | public static void test(ClassLoader classLoader) throws Exception { 358 | Class clazz = classLoader.loadClass("com.poplar.classload.ClassLoadTest"); 359 | Object instance = clazz.newInstance(); 360 | System.out.println(instance); 361 | } 362 | 363 | public static void main(String[] args) throws Exception { 364 | CustomClassLoader classLoader = new CustomClassLoader("load1"); 365 | test(classLoader); 366 | } 367 | } 368 | //这个列子中最后的类加载器是系统类加载器,而非我们自己的类加载器,是因为我们要加载的类刚好在系统类加载器的加载范围 369 | ``` 370 | 371 | ### 测试16改进: 372 | 373 | ```java 374 | package com.poplar.classload; 375 | 376 | import java.io.*; 377 | 378 | /** 379 | * Created By poplar on 2019/11/7 380 | * 自定义类加载器 381 | */ 382 | public class CustomClassLoader2 extends ClassLoader { 383 | 384 | private String classLoaderName; 385 | 386 | private String path; 387 | 388 | public void setPath(String path) { 389 | this.path = path; 390 | } 391 | 392 | private static final String filePost = ".class"; 393 | 394 | public CustomClassLoader2(ClassLoader parent, String classLoaderName) { 395 | super(parent);//显示指定该类的父类加载器 396 | this.classLoaderName = classLoaderName; 397 | } 398 | 399 | public CustomClassLoader2(String classLoaderName) { 400 | super();//将系统类加载器当作该类的父类加载器 401 | this.classLoaderName = classLoaderName; 402 | } 403 | 404 | @Override 405 | public Class findClass(String name) { 406 | System.out.println("findClass,输出这句话说明我们自己的类加载器加载了指定的类"); 407 | byte[] b = loadClassData(name); 408 | return defineClass(name, b, 0, b.length); 409 | } 410 | 411 | private byte[] loadClassData(String name) { 412 | InputStream is = null; 413 | byte[] data = null; 414 | ByteArrayOutputStream byteArrayOutputStream = null; 415 | 416 | try { 417 | name = name.replace(".", File.separator);//File.separator根据操作系统而变化 418 | is = new FileInputStream(new File(path + name + filePost)); 419 | byteArrayOutputStream = new ByteArrayOutputStream(); 420 | int len = 0; 421 | while (-1 != (len = is.read())) { 422 | byteArrayOutputStream.write(len); 423 | } 424 | data = byteArrayOutputStream.toByteArray(); 425 | 426 | } catch (Exception e) { 427 | e.printStackTrace(); 428 | } finally { 429 | try { 430 | is.close(); 431 | byteArrayOutputStream.close(); 432 | } catch (IOException e) { 433 | e.printStackTrace(); 434 | } 435 | return data; 436 | } 437 | } 438 | 439 | 440 | public static void main(String[] args) throws Exception { 441 | 442 | CustomClassLoader2 Loader2 = new CustomClassLoader2("load2"); 443 | test1(loader2) 444 | CustomClassLoader2 Loader3 = new CustomClassLoader2("load3"); 445 | test1(loader3) 446 | /* 447 | 执行结果: 448 | findClass,输出这句话说明我们自己的类加载器加载了指定的类 449 | com.poplar.classload.CustomClassLoader2@15db9742 450 | 2018699554 451 | ------------------------------------- 452 | findClass,输出这句话说明我们自己的类加载器加载了指定的类 453 | com.poplar.classload.CustomClassLoader2@4e25154f 454 | 1550089733*/ 455 | 456 | } 457 | private static void test1(CustomClassLoader2 loader2) throws ClassNotFoundException, InstantiationException, IllegalAccessException { 458 | loader2.setPath("C:\\Users\\poplar\\Desktop\\"); 459 | Class clazz = loader2.loadClass("com.poplar.classload.ClassLoadTest"); 460 | Object instance = clazz.newInstance(); 461 | System.out.println(instance.getClass().getClassLoader()); 462 | System.out.println(instance.hashCode()); 463 | System.out.println("-------------------------------------"); 464 | //运行结果:(此处测试建议把源码文件先删掉,不然idea会重新生成classes,还是会导致系统类加载器加载) 465 | } 466 | 467 | ``` 468 | 469 | ### 命名空间(上面的列子中加载同一个文件) 470 | 471 | - 每个类加载器都有自己的命名空间,**命名空间由该加载器及所有父加载器所加载的类构成**; 472 | - 在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类; 473 | - 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类; 474 | - 同一命名空间内的类是互相可见的,**非同一命名空间内的类是不可见的**; 475 | - 子加载器可以见到父加载器加载的类,**父加载器也不能见到子加载器加载的类**。 476 | 477 | ### 类的卸载 478 | 479 | - 当一个类被加载、连接和初始化之后,它的生命周期就开始了。当此类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,类在方法区内的数据也会被卸载。 480 | - 一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期。 481 | - 由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。Java虚拟机本身会始终引用这些加载器,而这些类加载器则会始终引用他们所加载的类的Class对象,因此这些Class对象是可触及的。 482 | - 由用户自定义的类加载器所加载的类是可以被卸载的。(**jvisualvm 查看当前java进程 -XX:+TraceClassUnloading这个用于追**) 483 | 484 | ```java 485 | public static void main(String[] args) throws Exception { 486 | CustomClassLoader2 Loader2 = new CustomClassLoader2("load2"); 487 | test1(Loader2); 488 | Loader2 = null; 489 | System.gc(); 490 | Thread.sleep(10000); //jvisualvm 查看当前java进程 -XX:+TraceClassUnloading这个用于追踪类卸载的信息 491 | CustomClassLoader2 Loader3 = new CustomClassLoader2("load2"); 492 | test1(Loader3); 493 | /* 494 | 执行结果: 495 | findClass,输出这句话说明我们自己的类加载器加载了指定的类 496 | com.poplar.classload.CustomClassLoader2@15db9742 497 | 2018699554 498 | ------------------------------------- 499 | [Unloading class com.poplar.classload.ClassLoadTest 0x0000000100060828] 500 | findClass,输出这句话说明我们自己的类加载器加载了指定的类 501 | com.poplar.classload.CustomClassLoader2@4e25154f 502 | 1550089733*/ 503 | } 504 | ``` 505 | 506 | ### 测试17: 507 | 508 | ```java 509 | package com.poplar.classload; 510 | 511 | /** 512 | * Created By poplar on 2019/11/8 513 | */ 514 | public class MyCat { 515 | public MyCat() { 516 | System.out.println("MyCat by load " + MyCat.class.getClassLoader()); 517 | } 518 | } 519 | 520 | 521 | /** 522 | * Created By poplar on 2019/11/8 523 | */ 524 | public class Simple { 525 | public Simple() { 526 | System.out.println("Simple by Load " + Simple.class.getClassLoader()); 527 | new MyCat(); 528 | } 529 | } 530 | 531 | 532 | /** 533 | * Created By poplar on 2019/11/8 534 | */ 535 | public class ClassLoadTest17 { 536 | public static void main(String[] args) throws Exception { 537 | CustomClassLoader2 loader = new CustomClassLoader2("loader"); 538 | Class clazz = loader.loadClass("com.poplar.classload.Simple"); 539 | System.out.println(clazz.hashCode()); 540 | //如果注释掉该行,就并不会实例化MySample对象,不会加载MyCat(可能预先加载) 541 | Object instance = clazz.newInstance();//实列化Simple和MyCat 542 | } 543 | } 544 | ``` 545 | 546 | **测试17-1**: 547 | 548 | ```java 549 | public class ClassLoadTest17_1 { 550 | public static void main(String[] args) throws Exception { 551 | CustomClassLoader2 loader = new CustomClassLoader2("loader"); 552 | loader.setPath("C:\\Users\\poplar\\Desktop\\"); 553 | Class clazz = loader.loadClass("com.poplar.classload.Simple"); 554 | System.out.println(clazz.hashCode()); 555 | //如果注释掉该行,就并不会实例化MySample对象,不会加载MyCat(可能预先加载) 556 | Object instance = clazz.newInstance();//实列化Simple和MyCat 557 | //MyCat是由加载MySample的加载器去加载的: 558 | //如果只删除classpath下的MyCat,则会报错,NoClassDefFoundError; 559 | //如果只删除classpath下的MySample,则由自定义加载器加载桌面上的MySample,由系统应用加载器加载MyCat。 560 | } 561 | } 562 | ``` 563 | 564 | 565 | 566 | ### 测试17_2 567 | 568 | ```java 569 | package com.poplar.classload; 570 | 571 | /** 572 | * Created By poplar on 2019/11/8 573 | */ 574 | public class ClassLoadTest17_2 { 575 | public static void main(String[] args) throws Exception { 576 | CustomClassLoader2 loader = new CustomClassLoader2("loader"); 577 | loader.setPath("C:\\Users\\poplar\\Desktop\\"); 578 | Class clazz = loader.loadClass("com.poplar.classload.Simple2"); 579 | System.out.println(clazz.hashCode()); 580 | //如果注释掉该行,就并不会实例化MySample对象,不会加载MyCat(可能预先加载) 581 | Object instance = clazz.newInstance();//实列化Simple和MyCat 582 | //修改MyCat2后,仍然删除classpath下的Simple2,留下MyCat2,程序报错 583 | //因为命名空间,父加载器找不到子加载器所加载的类,因此MyCat2找不到 584 | } 585 | } 586 | 587 | class MyCat2 { 588 | public MyCat2() { 589 | System.out.println("MyCat by load " + MyCat.class.getClassLoader()); 590 | System.out.println(Simple.class); 591 | } 592 | } 593 | 594 | class Simple2 { 595 | public Simple2() { 596 | System.out.println("Simple by Load " + Simple.class.getClassLoader()); 597 | new MyCat(); 598 | System.out.println(MyCat.class); 599 | } 600 | } 601 | ``` 602 | 603 | 1. **子加载器所加载的类能够访问父加载器所加载的类;** 604 | 2. **而父加载器所加载的类无法访问子加载器所加载的类**。 605 | 606 | ### 测试18: 607 | 608 | ```java 609 | package com.poplar.classload; 610 | 611 | /** 612 | * Created By poplar on 2019/11/8 613 | */ 614 | public class ClassLoadTest18 { 615 | public static void main(String[] args) { 616 | System.out.println(System.getProperty("sun.boot.class.path"));//根加载器路径 617 | System.out.println(System.getProperty("java.ext.dirs"));//扩展类加载器路径 618 | System.out.println(System.getProperty("java.class.path"));//应用类加载器路径 619 | } 620 | } 621 | 622 | ``` 623 | 624 | ### 测试19: 625 | 626 | ```java 627 | /** 628 | * Created By poplar on 2019/11/8 629 | * 各加载器的路径是可以修改的,修改后会导致运行失败,ClassNotFoundExeception 630 | */ 631 | public class ClassLoadTest19 { 632 | public static void main(String[] args) { 633 | //该类默认有扩展类加载器加载的,但是如果我们把该类默认的加载路劲修改后,就会报错 634 | AESKeyGenerator aesKeyGenerator = new AESKeyGenerator(); 635 | System.out.println(aesKeyGenerator.getClass().getClassLoader()); //ExtClassLoader@232204a1 636 | } 637 | } 638 | 639 | ``` 640 | 641 | ### 测试20 642 | 643 | ```java 644 | package com.poplar.classload; 645 | 646 | import java.lang.reflect.Method; 647 | 648 | /** 649 | * Created By poplar on 2019/11/8 650 | */ 651 | public class ClassLoadTest20 { 652 | public static void main(String[] args) throws Exception { 653 | CustomClassLoader2 loader1 = new CustomClassLoader2("load1"); 654 | CustomClassLoader2 loader2 = new CustomClassLoader2("load2"); 655 | Class clazz1 = loader1.loadClass("com.poplar.classload.Person"); 656 | Class clazz2 = loader2.loadClass("com.poplar.classload.Person"); 657 | 658 | //clazz1和clazz均由应用类加载器加载的,第二次不会重新加载,结果为true 659 | System.out.println(clazz1 == clazz2); 660 | 661 | Object object1 = clazz1.newInstance(); 662 | Object object2 = clazz2.newInstance(); 663 | 664 | Method method = clazz1.getMethod("setPerson", Object.class); 665 | method.invoke(object1, object2); 666 | } 667 | } 668 | 669 | class Person { 670 | 671 | private Person person; 672 | 673 | public void setPerson(Object object) { 674 | this.person = (Person) object; 675 | } 676 | } 677 | ``` 678 | 679 | ### 测试21: 680 | 681 | ```java 682 | package com.poplar.classload; 683 | 684 | import java.lang.reflect.Method; 685 | 686 | /** 687 | * Created By poplar on 2019/11/8 688 | * 1.每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类构成; 689 | * 2.在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类; 690 | * 3.在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类; 691 | * 4.同一命名空间内的类是互相可见的,非同一命名空间内的类是不可见的; 692 | * 5.子加载器可以见到父加载器加载的类,父加载器也不能见到子加载器加载的类。 693 | */ 694 | public class ClassLoadTest21 { 695 | 696 | public static void main(String[] args) throws Exception { 697 | CustomClassLoader2 loader1 = new CustomClassLoader2("load1"); 698 | loader1.setPath("C:\\Users\\poplar\\Desktop\\"); 699 | CustomClassLoader2 loader2 = new CustomClassLoader2("load2"); 700 | loader2.setPath("C:\\Users\\poplar\\Desktop\\"); 701 | Class clazz1 = loader1.loadClass("com.poplar.classload.MyPerson"); 702 | Class clazz2 = loader2.loadClass("com.poplar.classload.MyPerson"); 703 | //由于clazz1和clazz2分别有不同的类加载器所加载,所以他们处于不同的名称空间里 704 | System.out.println(clazz1 == clazz2);//false 705 | 706 | Object object1 = clazz1.newInstance(); 707 | Object object2 = clazz2.newInstance(); 708 | 709 | Method method = clazz1.getMethod("setMyPerson", Object.class); 710 | //此处报错,loader1和loader2所处不用的命名空间 711 | method.invoke(object1, object2); 712 | /* 713 | 714 | findClass,输出这句话说明我们自己的类加载器加载了指定的类 715 | findClass,输出这句话说明我们自己的类加载器加载了指定的类 716 | false 717 | Exception in thread "main" java.lang.reflect.InvocationTargetException 718 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 719 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 720 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 721 | at java.lang.reflect.Method.invoke(Method.java:498) 722 | at com.poplar.classload.ClassLoadTest21.main(ClassLoadTest21.java:25) 723 | Caused by: java.lang.ClassCastException: com.poplar.classload.MyPerson cannot be cast to com.poplar.classload.MyPerson*/ 724 | } 725 | } 726 | 727 | /** 728 | * Created By poplar on 2019/11/8 729 | */ 730 | public class MyPerson { 731 | 732 | private MyPerson person; 733 | 734 | public void setMyPerson(Object object) { 735 | this.person = (MyPerson) object; 736 | } 737 | } 738 | 739 | ``` 740 | 741 | - 类加载器双亲委托模型的好处: 742 | (1)可以确保Java和核心库的安全:所有的Java应用都会引用java.lang中的类,也就是说在运行期java.lang中的类会被加载到虚拟机中,如果这个加载过程如果是由自己的类加载器所加载,那么很可能就会在JVM中存在多个版本的java.lang中的类,而且这些类是相互不可见的(命名空间的作用)。借助于双亲委托机制,Java核心类库中的类的加载工作都是由启动根加载器去加载,从而确保了Java应用所使用的的都是同一个版本的Java核心类库,他们之间是相互兼容的; 743 | (2)确保Java核心类库中的类不会被自定义的类所替代; 744 | (3)不同的类加载器可以为相同名称的类(binary name)创建额外的命名空间。相同名称的类可以并存在Java虚拟机中,只需要用不同的类加载器去加载即可。相当于在Java虚拟机内部建立了一个又一个相互隔离的Java类空间。 745 | - 父亲委托机制的优点是能够提高软件系统的安全性。因此在此机制下,用户自定义的类加载器不可能加载应该由父类加载器加载的可靠类,从而防止不可靠甚至恶意的代码代替由父类加载器加载的可靠代码。例如,java.lang.Object类是由跟类加载器加载,其他任何用哪个户自定义的类加载器都不可能加载含有恶意代码的java.lang.Object类。 746 | 747 | ### 测试22: 748 | 749 | ```java 750 | package com.poplar.classload; 751 | 752 | /** 753 | * Created By poplar on 2019/11/8 754 | */ 755 | public class ClassLoadTest22 { 756 | 757 | static { 758 | System.out.println("ClassLoadTest22 invoked"); 759 | } 760 | 761 | //扩展类加载器只加载jar包,需要把class文件打成jar 762 | //此列子中将扩展类加载的位置改成了当前的classes目录 763 | public static void main(String[] args) { 764 | System.out.println(ClassLoadTest22.class.getClassLoader()); 765 | System.out.println(ClassLoadTest2.class.getClassLoader()); 766 | } 767 | } 768 | 769 | ``` 770 | 771 | ### 测试23: 772 | 773 | ```java 774 | package com.poplar.classload; 775 | 776 | import sun.misc.Launcher; 777 | 778 | /** 779 | * Created By poplar on 2019/11/8 780 | * 在运行期,一个Java类是由该类的完全限定名(binary name)和用于加载该类的定义类加载器所共同决定的。 781 | * 如果同样名字(完全相同限定名)是由两个不同的加载器所加载,那么这些类就是不同的,即便.class文件字节码相同,并且从相同的位置加载亦如此。 782 | * 在oracle的hotspot,系统属性sun.boot.class.path如果修改错了,则运行会出错: 783 | * Error occurred during initialization of VM 784 | * java/lang/NoClassDeFoundError: java/lang/Object 785 | */ 786 | public class ClassLoadTest23 { 787 | public static void main(String[] args) { 788 | System.out.println(System.getProperty("sun.boot.class.path"));//根加载器路径 789 | System.out.println(System.getProperty("java.ext.dirs"));//扩展类加载器路径 790 | System.out.println(System.getProperty("java.class.path"));//应用类加载器路径 791 | 792 | System.out.println(ClassLoader.class.getClassLoader()); 793 | //此处由于系统和扩展类加载器都是Launcher其内部静态类,但又都是非public的, 794 | //所以不能直接获取他们的类加载器,方法就是通过获取他们的外部类加载器是谁?从而确当他们的类加载器。 795 | System.out.println(Launcher.class.getClassLoader()); 796 | 797 | //下面的系统属性指定系统类加载器,默认是AppClassLoader 798 | System.out.println(System.getProperty("java.system.class.loader")); 799 | } 800 | } 801 | 802 | ``` 803 | 804 | - 类加载器本身也是类加载器,类加载器又是谁加载的呢??(先有鸡还是现有蛋) 805 | 类加载器是由启动类加载器去加载的,启动类加载器是C++写的,内嵌在JVM中。 806 | - 内嵌于JVM中的启动类加载器会加载java.lang.ClassLoader以及其他的Java平台类。当JVM启动时,一块特殊的机器码会运行,它会加载扩展类加载器以及系统类加载器,这块特殊的机器码叫做启动类加载器。 807 | - 启动类加载器并不是java类,其他的加载器都是java类。 808 | - 启动类加载器是特定于平台的机器指令,它负责开启整个加载过程。 809 | 810 | ### 测试24: 811 | 812 | ```java 813 | package com.poplar.classload; 814 | 815 | /** 816 | * Created By poplar on 2019/11/9 817 | */ 818 | public class ClassLoadTest24 { 819 | 820 | /** 821 | * 当前类加载器(Current ClassLoader) 822 | * 每个类都会尝试使用自己的类加载器去加载依赖的类。 823 | *

824 | * 线程上下文类加载器(Context ClassLoader) 825 | * 线程上下文加载器 @ jdk1.2 826 | * 线程类中的 getContextClassLoader() 与 setContextClassLoader(ClassLoader c) 827 | * 如果没有通过setContextClassLoader()方法设置,线程将继承父线程的上下文类加载器, 828 | * java应用运行时的初始线程的上下文类加载器是系统类加载器。该线程中运行的代码可以通过该类加载器加载类和资源。 829 | *

830 | * 线程上下文类加载器的作用: 831 | * SPI:Service Provide Interface 832 | * 父ClassLoader可以使用当前线程Thread.currentThread().getContextClassLoader()所制定的ClassLoader加载的类, 833 | * 这就改变了父加载器加载的类无法使用子加载器或是其他没有父子关系的ClassLoader加载的类的情况,即改变了双亲委托模型。 834 | *

835 | * 在双亲委托模型下,类加载是由下至上的,即下层的类加载器会委托父加载器进行加载。但是对于SPI来说, 836 | * 有些接口是Java核心库所提供的的(如JDBC),Java核心库是由启动类记载器去加载的,而这些接口的实现却来自不同的jar包(厂商提供), 837 | * Java的启动类加载器是不会加载其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求。通过给当前线程设置上下文类加载器, 838 | * 就可以由设置的上下文类加载器来实现对于接口实现类的加载。 839 | */ 840 | public static void main(String[] args) { 841 | System.out.println(Thread.currentThread().getContextClassLoader()); 842 | System.out.println(Thread.class.getClassLoader()); 843 | } 844 | } 845 | 846 | ``` 847 | 848 | ### 线程上下文类加载器 849 | 850 | - **当前类加载器(Current ClassLoader)** 851 | 每个类都会尝试使用自己的类加载器去加载依赖的类。 852 | 853 | 854 | - **线程上下文类加载器(Context ClassLoader)** 855 | 线程上下文加载器 @ jdk1.2 856 | 线程类中的` getContextClassLoader() `与 `setContextClassLoader(ClassLoader c)` 857 | 如果没有通过`setContextClassLoader()`方法设置,线程将继承父线程的上下文类加载器, 858 | java应用运行时的初始线程的上下文类加载器是系统类加载器。该线程中运行的代码可以通过该类加载器加载类和资源。 859 | 860 | - **线程上下文类加载器的作用:** 861 | **SPI**:`Service Provide Interface` 862 | 父`ClassLoader`可以使用当前线程`Thread.currentThread().getContextClassLoader()`所制定的`ClassLoader`加载的类, 863 | 这就改变了父加载器加载的类无法使用子加载器或是其他没有父子关系的`ClassLoader`加载的类的情况,即改变了双亲委托模型。 864 | 865 | - **在双亲委托模型下**,类加载是由下至上的,即下层的类加载器会委托父加载器进行加载。但是对于SPI来说, 866 | 有些接口是Java核心库所提供的的(如JDBC),Java核心库(如Connection接口)是由启动类加载器去加载的,而这些接口的实现却来自不同的jar包(**默认会被添加到classes下,这样就会导致父加载器无法访问子类加载器所加载的类**)(厂商提供), 867 | Java的启动类加载器是不会加载其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求。通过给当前线程设置上下文类加载器, 868 | 就可以由设置的上下文类加载器来实现对于接口实现类的加载。 869 | 870 | ### 测试25: 871 | 872 | ```java 873 | package com.poplar.classload; 874 | 875 | /** 876 | * Created By poplar on 2019/11/9 877 | */ 878 | public class ClassLoadTest25 implements Runnable { 879 | 880 | private Thread thread; 881 | 882 | public ClassLoadTest25() { 883 | thread = new Thread(this); 884 | thread.start(); 885 | } 886 | 887 | @Override 888 | public void run() { 889 | ClassLoader classLoader = thread.getContextClassLoader(); 890 | thread.setContextClassLoader(classLoader); 891 | System.out.println("Class: " + classLoader.getClass()); //Class: class sun.misc.Launcher$AppClassLoader 892 | System.out.println("Parent " + classLoader.getParent()); // Parent sun.misc.Launcher$ExtClassLoader@5b74b597 893 | } 894 | 895 | public static void main(String[] args) { 896 | new ClassLoadTest25(); 897 | } 898 | } 899 | //源码: 900 | /* public Launcher() { 901 | Launcher.ExtClassLoader var1; 902 | try { 903 | var1 = Launcher.ExtClassLoader.getExtClassLoader(); 904 | } catch (IOException var10) { 905 | throw new InternalError("Could not create extension class loader", var10); 906 | } 907 | 908 | try { 909 | //获取到系统类加载器 910 | this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); 911 | } catch (IOException var9) { 912 | throw new InternalError("Could not create application class loader", var9); 913 | } 914 | //把系统类加载器设置到当前线程的上下文类加载器中 915 | Thread.currentThread().setContextClassLoader(this.loader); 916 | }*/ 917 | ``` 918 | 919 | ### 测试26 920 | 921 | ```java 922 | package com.poplar.classload; 923 | 924 | import java.sql.Driver; 925 | import java.util.Iterator; 926 | import java.util.ServiceLoader; 927 | 928 | /** 929 | * Created By poplar on 2019/11/9 930 | *

931 | * 线程上下文类加载器的一般使用模式:(获取-使用-还原) 932 | * 伪代码: 933 | * ClassLoader classLoader=Thread.currentThread().getContextLoader(); 934 | * try{ 935 | * Thread.currentThread().setContextLoader(targetTccl); 936 | * myMethod(); 937 | * }finally{ 938 | * Thread.currentThread().setContextLoader(classLoader); 939 | * } 940 | * 在myMethod中调用Thread.currentThread().getContextLoader()做某些事情 941 | * ContextClassLoader的目的就是为了破坏类加载委托机制 942 | *

943 | * 在SPI接口的代码中,使用线程上下文类加载器就可以成功的加载到SPI的实现类。 944 | *

945 | * 当高层提供了统一的接口让底层去实现,同时又要在高层加载(或实例化)底层的类时, 946 | * 就必须通过上下文类加载器来帮助高层的ClassLoader找到并加载该类。 947 | */ 948 | public class ClassLoadTest26 { 949 | public static void main(String[] args) { 950 | 951 | //一旦加入下面此行,将使用ExtClassLoader去加载Driver.class, ExtClassLoader不会去加载classpath,因此无法找到MySql的相关驱动。 952 | //Thread.getCurrentThread().setContextClassLoader(MyTest26.class.getClassLoader().parent()); 953 | 954 | //ServiceLoader服务提供者,加载实现的服务 955 | ServiceLoader loader = ServiceLoader.load(Driver.class); 956 | Iterator iterator = loader.iterator(); 957 | while (iterator.hasNext()) { 958 | Driver driver = iterator.next(); 959 | System.out.println("driver:" + driver.getClass() + ",loader" + driver.getClass().getClassLoader()); 960 | } 961 | System.out.println("当前上下文加载器" + Thread.currentThread().getContextClassLoader()); 962 | System.out.println("ServiceLoader的加载器" + ServiceLoader.class.getClassLoader()); 963 | } 964 | } 965 | 966 | 967 | ``` 968 | 969 | ### 测试27 970 | 971 | ```java 972 | package com.poplar.classload; 973 | 974 | import java.sql.Connection; 975 | import java.sql.DriverManager; 976 | 977 | /** 978 | * Created By poplar on 2019/11/9 979 | */ 980 | public class ClassLoadTest27 { 981 | //源码:DriverManager.getConnection(){}方法中的: 982 | /* private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) { 983 | boolean result = false; 984 | if(driver != null) { 985 | Class aClass = null; 986 | try { 987 | //到这儿时其实已经加载过了,再次加载主要是名称空间的问题,确保是在同一名称空间下 988 | aClass = Class.forName(driver.getClass().getName(), true, classLoader); 989 | } catch (Exception ex) { 990 | result = false; 991 | } 992 | result = ( aClass == driver.getClass() ) ? true : false; 993 | } 994 | 995 | return result; 996 | }*/ 997 | public static void main(String[] args) throws Exception { 998 | Class clazz = Class.forName("com.mysql.jdbc.Driver"); 999 | Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/user", "root", "123456"); 1000 | } 1001 | } 1002 | 1003 | ``` 1004 | 1005 | ### jar hell问题以及解决办法 1006 | 1007 | ``` 1008 | /* 1009 | 当一个类或者一个资源文件存在多个jar中,就会存在jar hell问题。 1010 | 可通过以下代码解决问题:*/ 1011 | ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 1012 | String resource = "java/lang/String.class"; 1013 | Enumeration urls = classLoader.getResources(resource); 1014 | while (urls.hasMoreElements()) { 1015 | URL element = urls.nextElement(); 1016 | System.out.println(element); 1017 | } 1018 | ``` -------------------------------------------------------------------------------- /src/resources/images/1574823017674.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/1574823017674.gif -------------------------------------------------------------------------------- /src/resources/images/1574824343266.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/1574824343266.gif -------------------------------------------------------------------------------- /src/resources/images/G1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/G1.png -------------------------------------------------------------------------------- /src/resources/images/Snipaste_2019-11-07_14-51-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/Snipaste_2019-11-07_14-51-04.png -------------------------------------------------------------------------------- /src/resources/images/acc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/acc.png -------------------------------------------------------------------------------- /src/resources/images/byte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/byte.png -------------------------------------------------------------------------------- /src/resources/images/classload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/classload.png -------------------------------------------------------------------------------- /src/resources/images/classload2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/classload2.png -------------------------------------------------------------------------------- /src/resources/images/cms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/cms.png -------------------------------------------------------------------------------- /src/resources/images/compute_jmm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/compute_jmm.png -------------------------------------------------------------------------------- /src/resources/images/drrrr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/drrrr.png -------------------------------------------------------------------------------- /src/resources/images/dss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/dss.png -------------------------------------------------------------------------------- /src/resources/images/fieled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/fieled.png -------------------------------------------------------------------------------- /src/resources/images/fieled1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/fieled1.png -------------------------------------------------------------------------------- /src/resources/images/gcroot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/gcroot.png -------------------------------------------------------------------------------- /src/resources/images/gg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/gg.png -------------------------------------------------------------------------------- /src/resources/images/hff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/hff.png -------------------------------------------------------------------------------- /src/resources/images/java_jmm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/java_jmm.png -------------------------------------------------------------------------------- /src/resources/images/jvm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/jvm.png -------------------------------------------------------------------------------- /src/resources/images/kernel_thread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/kernel_thread.png -------------------------------------------------------------------------------- /src/resources/images/loadmethod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/loadmethod.png -------------------------------------------------------------------------------- /src/resources/images/memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/memory.png -------------------------------------------------------------------------------- /src/resources/images/mix_thread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/mix_thread.png -------------------------------------------------------------------------------- /src/resources/images/parnew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/parnew.png -------------------------------------------------------------------------------- /src/resources/images/pool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/pool.png -------------------------------------------------------------------------------- /src/resources/images/qqq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/qqq.png -------------------------------------------------------------------------------- /src/resources/images/re.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/re.png -------------------------------------------------------------------------------- /src/resources/images/region.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/region.png -------------------------------------------------------------------------------- /src/resources/images/rew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/rew.png -------------------------------------------------------------------------------- /src/resources/images/san.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/san.png -------------------------------------------------------------------------------- /src/resources/images/san2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/san2.png -------------------------------------------------------------------------------- /src/resources/images/san3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/san3.png -------------------------------------------------------------------------------- /src/resources/images/sanmark.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/sanmark.gif -------------------------------------------------------------------------------- /src/resources/images/sans1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/sans1.png -------------------------------------------------------------------------------- /src/resources/images/sans2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/sans2.png -------------------------------------------------------------------------------- /src/resources/images/sans3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/sans3.png -------------------------------------------------------------------------------- /src/resources/images/serial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/serial.png -------------------------------------------------------------------------------- /src/resources/images/stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/stack.png -------------------------------------------------------------------------------- /src/resources/images/thread_status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/thread_status.png -------------------------------------------------------------------------------- /src/resources/images/user_thread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/user_thread.png -------------------------------------------------------------------------------- /src/resources/images/vb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weolwo/jvm-learn/b4be4e814911c5756cad9c0abf51e56529680ab7/src/resources/images/vb.png -------------------------------------------------------------------------------- /src/resources/java内存模型.md: -------------------------------------------------------------------------------- 1 | ### JMM 2 | 3 | ### 计算机物理内存模型 4 | 5 | ![计算机物理内存模型](./images/compute_jmm.png) 6 | 7 | ### java JMM 8 | ![java JMM](./images/java_jmm.png) 9 | 10 | - Java内存模型规定了所有的变量都存储在主内存(Main Memory)中(此处的主内存与 介绍物理硬件时的主内存名字一样,两者也可以互相类比,但此处仅是虚拟机内存的一部 分)。每条线程还有自己的工作内存(Working Memory,可与前面讲的处理器高速缓存类 比),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。 11 | - 线程对变量的 所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。 不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主 内存来完成 12 | 13 | ### 内存间交互操作 14 | 15 | - 关于主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内 存、如何从工作内存同步回主内存之类的实现细节,Java内存模型中定义了以下8种操作来 完成,虚拟机实现时必须保证下面提及的每一种操作都是原子的 16 | - lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。 17 | - unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放 后的变量才可以被其他线程锁定。 18 | - read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内 存中,以便随后的load动作使用。 19 | - load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工 作内存的变量副本中。 20 | - use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引 擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。 21 | - assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内 存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。 22 | - store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存 中,以便随后的write操作使用。 23 | - write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入 主内存的变量中 24 | 25 | ### 对于volatile型变量的特殊规则 26 | 27 | - 关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制, 28 | 29 | - 是保证此变量对所有线程的可 见性,这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以 立即得知的 30 | 31 | - 是Java里面的运算并非 原子操作,导致volatile变量的运算在并发下一样是不安全的 32 | 33 | ```java 34 | /** 35 | ``` 36 | * Created BY poplar ON 2019/12/7 37 | * volatile在并发环境下并非原子操作 38 | */ 39 | public class VolatileTest { 40 | 41 | public static volatile int race = 0; 42 | 43 | public static void increase() { 44 | race++; 45 | } 46 | 47 | public static final int THREAD_COUNT = 20; 48 | 49 | public static void main(String[] args) { 50 | for (int i = 0; i < THREAD_COUNT; i++) { 51 | new Thread(() -> { 52 | for (int j = 0; j < 10000; j++) { 53 | increase(); 54 | } 55 | }).start(); 56 | } 57 | 58 | //等待所有累加线程都结束,如果还有线程在运行,主线程就让出cpu资源 59 | while (Thread.activeCount() > 2) {//由于idea原因此处不能为一 60 | Thread.yield(); 61 | } 62 | System.out.println(race); 63 | } 64 | 65 | /* 66 | public static void increase(); 67 | descriptor: ()V 68 | flags: ACC_PUBLIC, ACC_STATIC 69 | Code: 70 | stack=2, locals=0, args_size=0 71 | 0: getstatic #2 // Field race:I 72 | 3: iconst_1 73 | 4: iadd 74 | 5: putstatic #2 // Field race:I 75 | 8: return 76 | 当getstatic指令把race的值取到操作栈顶时,volatile关键字保证了race的值在此 时是正确的,但是在执行iconst_1、iadd这些指令的时候, 77 | 其他线程可能已经把race的值加大 了,而在操作栈顶的值就变成了过期的数据,所以putstatic指令执行后就可能把较小的race值 同步回主内存之中 78 | */ 79 | } 80 | 81 | ``` 82 | 83 | ``` 84 | 85 | 86 | 87 | - 禁止指令重排序优化 88 | 89 | ```java 90 | /** 91 | * Created BY poplar ON 2019/12/8 92 | * volatile静止指令重排序演示代码 93 | */ 94 | public class Singleton { 95 | 96 | private volatile static Singleton instance; 97 | 98 | private Singleton() { 99 | } 100 | 101 | public static Singleton getInstance() { 102 | if (instance == null) { 103 | synchronized (Singleton.class) { 104 | if (instance == null) { 105 | instance = new Singleton(); 106 | } 107 | } 108 | } 109 | return instance; 110 | } 111 | 112 | public static void main(String[] args) { 113 | Singleton.getInstance(); 114 | } 115 | } 116 | /* 117 | instance = new Singleton(); 118 | 这段代码可以分为如下的三个步骤: 119 | memory = allocate(); // 1:分配对象的内存空间 120 | ctorInstance(memory); // 2:初始化对象 121 | instance = memory; // 3:设置instance指向刚分配的内存地址 122 | 我们知道,编辑器和处理器会进行代码优化,而其中重要的一点是会将指令进行重排序。 123 | 上边的代码经过重排序后可能会变为: 124 | memory = allocate(); // 1:分配对象的内存空间 125 | instance = memory; // 3:设置instance指向刚分配的内存地址 126 | // 注意:此时对象尚未初始化 127 | ctorInstance(memory); // 2:初始化对象 128 | 129 | 代码对应的汇编的执行过程 130 | * 0x01a3de0f:mov $0x3375cdb0,%esi ;……beb0cd75 33 131 | ;{oop('Singleton')} 132 | 0x01a3de14:mov %eax,0x150(%esi) ;……89865001 0000 133 | 0x01a3de1a:shr $0x9,%esi ;……c1ee09 134 | 0x01a3de1d:movb $0x0,0x1104800(%esi) ;……c6860048 100100 135 | 0x01a3de24:lock addl$0x0,(%esp) ;……f0830424 00 136 | ;*put static instance 137 | ;- 138 | Singleton:getInstance@24 139 | 140 | 生成汇编码是lock addl $0x0, (%rsp), 在写操作(put static instance)之前使用了lock前缀,锁住了总线和对应的地址,这样其他的CPU写和读都要等待锁的释放。 141 | 当写完成后,释放锁,把缓存刷新到主内存。 142 | 加了 volatile之后,volatile在最后加了lock前缀,把前面的步骤锁住了,这样如果你前面的步骤没做完是无法执行最后一步刷新到内存的, 143 | 换句话说只要执行到最后一步lock,必定前面的操作都完成了。那么即使我们完成前面两步或者三步了,还没执行最后一步lock,或者前面一步执行了就切换线程2了, 144 | 线程B在判断的时候也会判断实例为空,进而继续进来由线程B完成后面的所有操作。当写完成后,释放锁,把缓存刷新到主内存。 145 | ———————————————— 146 | 版权声明:本文为CSDN博主「夏洛克卷」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 147 | 原文链接:https://blog.csdn.net/zx48822821/article/details/86589753 148 | 149 | * */ 150 | ``` 151 | 152 | 153 | 154 | ### Java与Thread 155 | 156 | - 线程的实现 157 | 158 | - 实现线程主要有3种方式:使用内核线程实现、使用用户线程实现和使用用户线程加轻 量级进程混合实现。 159 | 160 | - **内核线程**(Kernel-Level Thread,KLT) 161 | 162 | ![内核线程](./images/kernel_thread.png) 163 | 164 | - **用户线程**(User Thread,UT)使用用户线程的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援,所有 的线程操作都需要用户程序自己处理 165 | 166 | ![用户线程](./images/user_thread.png) 167 | 168 | - **使用用户线程加轻量级进程混合实现** 169 | 170 | ![用户线程](./images/mix_thread.png) 171 | 172 | - **Java线程的实现** 173 | 174 | - 在目前的JDK版 本中,操作系统支持怎样的线程模型,在很大程度上决定了Java虚拟机的线程是怎样映射 的,这点在不同的平台上没有办法达成一致,虚拟机规范中也并未限定Java线程需要使用哪 种线程模型来实现。 175 | 176 | - **Java线程调度** 177 | - 线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种,分别是协同 式线程调度(Cooperative Threads-Scheduling)和抢占式线程调度(Preemptive ThreadsScheduling)。 178 | - 协同 式线程调度,线程的执行时间由线程本身来控制,线程把自己的 工作执行完了之后,要主动通知系统切换到另外一个线程上。 179 | - 使用抢占式调度的多线程系统,那么每个线程将由系统来分配执行时间,线程的切 换不由线程本身来决定 180 | 181 | - Java使用的线程调度方式就是抢 占式调度 182 | 183 | - **线程状态转换** 184 | 185 | ![用户线程](./images/thread_status.png) 186 | 187 | ### 线程安全与锁优化 188 | 189 | - 为了更加深入地理解线程安全,在这里我们可以不把线程安全当做一个非真即假的二元 排他选项来看待,按照线程安全的“安全程度”由强至弱来排序,我们[1]可以将Java语言中各种 操作共享的数据分为以下5类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对 立 190 | 191 | - **不可变 (Immutable)**的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需 要再采取任何的线程安全保障措施 192 | 193 | - 在Java API中标注自己是线程安全的类,大多数 都不是**绝对的线程安全** 194 | 195 | ```java 196 | /** 197 | * Created BY poplar ON 2019/12/8 198 | * 对vector线程安全的测试,通过对源码debug测试发现会出现 ArrayIndexOutOfBoundsException 199 | * 尽管这里使用到的Vector的get()、remove()和size()方法都是同步的, 但是在多线程的环境中, 200 | * 如果不在方法调用端做额外的同步措施的话,使用这段代码仍然是 不安全的,因为如果另一个线程恰好在错误的时间里删除了一个元素, 201 | * 导致序号i已经不再 可用的话,再用i访问数组就会抛出一个ArrayIndexOutOfBoundsException 202 | */ 203 | public class VectorTest { 204 | 205 | private static Vector vector = new Vector<>(); 206 | 207 | public static void main(String[] args) { 208 | while (true) { 209 | for (int i = 0; i < 10; i++) { 210 | vector.add(i); 211 | } 212 | 213 | new Thread(() -> { 214 | for (int i = 0; i < vector.size(); i++) { 215 | vector.remove(i); 216 | } 217 | }).start(); 218 | 219 | new Thread(() -> { 220 | for (int i = 0; i < vector.size(); i++) { 221 | System.out.println(vector.get(i)); 222 | } 223 | }).start(); 224 | 225 | while (Thread.activeCount() > 90) ; 226 | } 227 | 228 | } 229 | } 230 | ``` 231 | 232 | **代码改进**: 233 | 234 | ```java 235 | /** 236 | * Created BY poplar ON 2019/12/8 237 | * 改进后debug源码未发现异常情况 238 | */ 239 | public class VectorTestImprove { 240 | private static Vector vector = new Vector<>(); 241 | 242 | public static void main(String[] args) { 243 | while (true) { 244 | for (int i = 0; i < 10; i++) { 245 | vector.add(i); 246 | } 247 | 248 | new Thread(() -> { 249 | synchronized (vector) { 250 | for (int i = 0; i < vector.size(); i++) { 251 | vector.remove(i); 252 | } 253 | } 254 | }).start(); 255 | 256 | new Thread(() -> { 257 | synchronized (vector) { 258 | for (int i = 0; i < vector.size(); i++) { 259 | System.out.println(vector.get(i)); 260 | } 261 | } 262 | }).start(); 263 | 264 | while (Thread.activeCount() > 90) ; 265 | } 266 | 267 | } 268 | } 269 | 270 | ``` 271 | 272 | 273 | 274 | - **相对线程安全** 275 | - 相对的线程安全就是我们通常意义上所讲的线程安全,在Java语言中,大部分的线程安全类都属于这种类型,例如Vector、HashTable、 Collections的synchronizedCollection()方法包装的集合等 276 | 277 | - **线程兼容** 278 | 279 | - 线程兼容是指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段 来保证对象在并发环境中可以安全地使用,我们平常说一个类不是线程安全的,绝大多数时 候指的是这一种情况。Java API中大部分的类都是属于线程兼容的,如与前面的Vector和 HashTable相对应的集合类ArrayList和HashMap等。 280 | 281 | - **线程对立** 282 | - 线程对立是指无论调用端是否采取了同步措施,都无法在多线程环境中并发使用的代 码。 283 | 284 | ### 线程安全的实现方法 285 | 286 | - **互斥同步**(Mutual Exclusion&Synchronization)是常见的一种并发正确性保障手段。同步 是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个(或者是一些, 使用信号量的时候)线程使用。 287 | - 从处理问题的方式上说,互斥同步属于一种悲观的 并发策略,总是认为只要不去做正确的同步措施(例如加锁),那就肯定会出现问题,无论 共享数据是否真的会出现竞争,它都要进行加锁 288 | 289 | - 在Java中,最基本的互斥同步手段就是synchronized关键字,我们还可以使用java.util.concurrent(下文称J.U.C)包中的重入锁 (ReentrantLock)来实现同步 290 | 291 | - **非阻塞同步** 292 | 293 | - 随着硬件指令集的发展,我们有了另外一个选择:基于冲突检测的 乐观并发策略,通俗地说,就是先进行操作,如果没有其他线程争用共享数据,那操作就成 功了;如果共享数据有争用,产生了冲突,那就再采取其他的补偿措施(最常见的补偿措施 就是不断地重试,直到成功为止),这种乐观的并发策略的许多实现都不需要把线程挂起, 因此这种同步操作称为非阻塞同步(Non-Blocking Synchronization)。 294 | 295 | ### 锁优化 296 | 297 | - **自旋锁与自适应自旋** 298 | 299 | - 互斥同步对性能最大的影响是阻塞的实现,挂起 线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性能带来了很大的 压力。同时,虚拟机的开发团队也注意到在许多应用上,共享数据的锁定状态只会持续很短 的一段时间,为了这段时间去挂起和恢复线程并不值得。如果物理机器有一个以上的处理 器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一 下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等 待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁, 300 | - 自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的, 因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时间 很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性 能上的浪费。因此,自旋等待的时间必须要有一定的限度,如果自旋超过了限定的次数仍然 没有成功获得锁,就应当使用传统的方式去挂起线程了 301 | 302 | - 自适应意味着自旋的时间不再固定了,而是由前 一次在同一个锁上的自旋时间及锁的拥有者的状态来决定 303 | 304 | ### 关于锁推荐相关参考链接: 305 | 306 | -------------------------------------------------------------------------------- /src/resources/java内容结构.md: -------------------------------------------------------------------------------- 1 | # Java内存结构 2 | 3 | ### java JVM内部结构 4 | 5 | ![JVM Components](./images/jvm.png) 6 | 7 | ![](./images/memory.png) 8 | 9 | ### java对象创建过程 10 | 11 | ```java 12 | /** 13 | * Created BY poplar ON 2019/11/25 14 | * 关于Java对象创建的过程: 15 | * new关键字创建对象的3个步骤: 16 | * 1.在堆内存中创建出对象的实例。 17 | * 2.为对象的实例成员变量赋初值。 18 | * 3.将对象的引用返回 19 | * 指针碰撞(前提是堆中的空间通过一个指针进行分割,一侧是已经被占用的空间,另一侧是未被占用的空间) 20 | * 空闲列表(前提是堆内存空间中已被使用与未被使用的空间是交织在一起的,这时,虚拟机就需要通过一个列表来记录哪些空间是可以使用的, 21 | * 哪些空间是已被使用的,接下来找出可以容纳下新创建对象的且未被使用的空间,在此空间存放该对象,同时还要修改列表上的记录) 22 | * 对象在内存中的布局: 23 | * 1.对象头. 24 | * 2.实例数据(即我们在一个类中所声明的各项信息) 25 | * 3.对齐填充(可选) ! 26 | * 引用访问对象的方式: 27 | * 1.使用句柄的方式。 28 | * 2.使用直接指针的方式。 29 | */ 30 | public class MemoryTest1 { 31 | public static void main(String[] args) { 32 | //-Xms5m -Xmx5m -XX:+HeapDumpOnOutOfMemoryError 设置jvm对空间最小和最大以及遇到错误时把堆存储文件打印出来 33 | //打开jvisualvm装在磁盘上的转存文件 34 | List list = new ArrayList<>(); 35 | while (true) { 36 | list.add(new MemoryTest1()); 37 | System.gc(); 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | ### 虚拟机栈溢出测试 44 | 45 | ```java 46 | /** 47 | * Created BY poplar ON 2019/11/25 48 | * 虚拟机栈溢出测试 49 | */ 50 | public class MemoryTest2 { 51 | 52 | private int length; 53 | 54 | public int getLength() { 55 | return length; 56 | } 57 | 58 | public void test() throws InterruptedException { 59 | length++; 60 | Thread.sleep(1); 61 | test(); 62 | } 63 | 64 | public static void main(String[] args) { 65 | //测试调整虚拟机栈内存大小为: -Xss160k,此处除了可以使用JVisuale监控程序运行状况外还可以使用jconsole 66 | MemoryTest2 memoryTest2 = new MemoryTest2(); 67 | try { 68 | memoryTest2.test(); 69 | } catch (Throwable e) { 70 | System.out.println(memoryTest2.getLength());//打印最终的最大栈深度为:2587 71 | e.printStackTrace(); 72 | } 73 | } 74 | } 75 | ``` 76 | 77 | ### 元空间溢出测试 78 | 79 | ```java 80 | /** 81 | * Created BY poplar ON 2019/11/26 82 | * 元空间内存溢出测试 83 | * 设置元空间大小:-XX:MaxMetaspaceSize=100m 84 | * 关于元空间参考:https://www.infoq.cn/article/java-permgen-Removed 85 | */ 86 | public class MemoryTest3 { 87 | public static void main(String[] args) { 88 | //使用动态代理动态生成类 89 | while (true) { 90 | Enhancer enhancer = new Enhancer(); 91 | enhancer.setSuperclass(MemoryTest3.class); 92 | enhancer.setUseCache(false); 93 | enhancer.setCallback((MethodInterceptor) (obj, method, ags, proxy) -> proxy.invokeSuper(obj, ags)); 94 | System.out.println("Hello World"); 95 | enhancer.create();// java.lang.OutOfMemoryError: Metaspace 96 | } 97 | } 98 | ``` 99 | 100 | ### JVM命令使用 101 | 102 | ```java 103 | /** 104 | * Created BY poplar ON 2019/11/26 105 | * jmam命令的使用 -clstats进程id to print class loader statistics 106 | * jmap -clstats 3740 107 | * 108 | * jstat -gc 3740 109 | * S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 110 | * 512.0 512.0 0.0 0.0 24064.0 9626.0 86016.0 1004.1 4864.0 3758.2 512.0 409.1 144 0.064 0 0.000 0.064 111 | * MC元空间总大小,MU元空间已使用的大小 112 | */ 113 | public class MemoryTest4 { 114 | public static void main(String[] args) { 115 | while (true) 116 | System.out.println("hello world"); 117 | } 118 | //查看java进程id jps -l 119 | // 使用jcmd查看当前进程的可用参数:jcmd 10368 help 120 | //查看jvm的启动参数 jcmd 10368 VM.flags 121 | // 10368:-XX:CICompilerCount=3 -XX:InitialHeapSize=132120576 -XX:MaxHeapSize=2111832064 -XX:MaxNewSize=703594496 122 | // -XX:MinHeapDeltaBytes=524288 -XX:NewSize=44040192 -XX:OldSize=88080384 -XX:+UseCompressedClassPointers 123 | // -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 124 | 125 | } 126 | ``` 127 | 128 | 129 | 130 | ### JVM常用命令 131 | 132 | ```java 133 | jcmd (从JDK 1. 7开始增加的命令) 134 | 1. jcmd pid VM.flags: 查看JVM的启动参数 135 | 2. jcmd pid help: 列出当前运行的Java进程可以执行的操作 136 | 3. jcmd pid helpJFR.dump:查看具体命令的选项 137 | 4. jcmd pid PerfCounter.print:看JVm性能相关的参数 138 | 5. jcmd pid VM.uptime:查有JVM的启动时长 139 | 6. jcmd pid GC.class_ histogram: 查看系统中类的统计信息 140 | 7. jcmd pid Thread.print: 查看线程堆栈信息 141 | 8. jcmd pid GC.heap dump filename 导出Heap dump文件, 导出的文件可以通过jvisualvm查看 142 | 9. jcmd pid VM.system_ properties:查看JVM的属性信息 143 | 144 | ``` 145 | 146 | ### JVM内存举例说明 147 | 148 | ```java 149 | public void method() { 150 | Object object = new Object(); 151 | 152 | /*生成了2部分的内存区域,1)object这个引用变量,因为 153 | 是方法内的变量,放到JVM Stack里面,2)真正Object 154 | class的实例对象,放到Heap里面 155 | 上述 的new语句一共消耗12个bytes, JVM规定引用占4 156 | 个bytes (在JVM Stack), 而空对象是8个bytes(在Heap) 157 | 方法结束后,对应Stack中的变量马上回收,但是Heap 158 | 中的对象要等到GC来回收、*/ 159 | } 160 | ``` 161 | 162 | 163 | 164 | ### JVM垃圾识别(根搜索算法( GC RootsTracing )) 165 | 166 | - 在实际的生产语言中(Java、 C#等),都是使用根搜索算法判定对象是否存活。 167 | 168 | - 算法基本思路就是通过一系列的称为“GCRoots"的点作为起始进行向下搜索,当一个对象到GCRoots没有任何引用链( Reference Chain)相连,则证明此对象 169 | 是不可用的 170 | 171 | - 在Java语言中,GC Roots包括 172 | ●在VM栈(帧中的本地变量)中的引用 173 | ●方法区中的静态引用 174 | ●JNI (即一般说的Native方法) 中的引用 175 | 176 | ### 方法区 177 | 178 | - Java虛拟机规范表示可以不要求虚拟机在这区实现GC,这区GC的“性价比”一般比较低 179 | 在堆中,尤其是在新生代,常规应用进行I次GC一般可以回收70%~95%的空间,而方法区的GC效率远小于此 180 | - 当前的商业JVM都有实现方法区的GC,主要回收两部分内容:废弃常量与无用类 181 | 182 | - 主要回收两部分内容:废弃常量与无用类 183 | - 类回收需要满足如下3个条件: 184 | - 该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例 185 | - 加载该类的ClassL oader已经被GC 186 | - 该类对应的java.lang.Class对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法 187 | 188 | - 在大量使用反射、动态代理、CGLib等字节码框架、动态生成JSP以及OSGi这类频繁自定义Classloader的场景都需要JVM具备类卸载的支持以保证方法区不会溢出 189 | 190 | ### 垃圾判断与GC算法 191 | 192 | - 垃圾判断的算法 193 | - 引用计数算法(Reference Counting) 194 | - 根搜索算法( GC RootsTracing ) 195 | - 在实际的生产语言中(Java、 C#等)都是使用根搜索算法判定对象是否存活 196 | - 算法基本思路就是通过一一系列的称为GCRoots"的点作为起始进行向下搜索,当一个对象到GC Roots没有任何引用链(Reference Chain)相连,则证明此对象是不可用的 197 | 198 | - 在Java语言中,可作为GC Roots的对象包括下面几种: 199 | - 虚拟机栈(栈帧中的本地变量表)中引用的对象。 200 | - 方法区中类静态属性引用的对象。 201 | - 方法区中常量引用的对象。 202 | - 本地方法栈中JNI(即一般说的Native方法)引用的对象 203 | 204 | ![根搜索算法(Root Tracing)](./images/gcroot.png) 205 | 206 | - 标记-清除算法(Mark Sweep) 207 | - 标记-整理算法(Mark-Compact) 208 | - 复制算法(Copying) 209 | - 分代算法(Generational) 210 | 211 | ### 标记一清除算法(Mark-Sweep) 212 | 213 | - 算法分为“标记”和“清除”两个阶段, 214 | 首先标记出所有需要回收的对象,然后回 215 | 收所有需要回收的对象 216 | 217 | - 缺点 218 | 效率问题,标记和清理两个过程效率都不高 219 | 空间问题, 220 | 标记清理之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续使用中无法找到足够的连续内存而提前触发另一次的垃圾搜集动作 221 | 222 | - 效率不高,需要扫描所有对象。堆越大,GC越慢 223 | 存在内存碎片问题。GC次数越多,碎片越为严重 224 | 225 | 226 | 227 | ![标记一清除算法(Mark-Sweep)](./images/1574823017674.gif) 228 | 229 | ### 复制(Copying) 搜集算法 230 | 231 | - 将可用内存划分为两块,每次只使用其中的一块,当一半区内存用完了,仅将还存活 232 | 的对象复制到另外一块上面,然后就把原来整块内存空间一次性清理掉, 233 | - 这样使得每次内存回收都是对整个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存就可以了,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,代价高昂 234 | 235 | - 现在的商业虚拟机中都是用了这一种收集算法来回收新生代 236 | - 将内存分为一块较大的eden空间和2块较少的survivor空间,每次使用eden和其中一块 237 | survivor, 当回收时将eden和survivor还存活的对象一次性拷 贝到另外一块survivor空间上,然后清理掉eden和用过的survivor 238 | - Oracle Hotspot虚拟机默认eden和survivor的大小比例是8:1,也就是每次只有10%的内存是“浪费”的 239 | 240 | - 复制收集算法在对象存活率高的时候,效率有所下降 241 | - 如果不想浪费50%的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法 242 | 243 | ![复制(Copying) 搜集算法](./images/1574824343266.gif) 244 | 245 | - 只需要扫描存活的对象,效率更高 246 | - 不会产生碎片 247 | - 需要浪费额外的内存作为复制区 248 | - 复制算法非常适合生命周期比较短的对象,因为每次GC总能回收大部分的对象,复制的开销比较小 249 | - 根据IBM的专i研究,98%的Java对象只会存活1个GC周期,对这些对象很适合用复制算法。而且 250 | 不用1: 1的划分工作区和复制区的空间 251 | 252 | ### 标记一整理( Mark-Compact )算法 253 | 254 | - 标记过程仍然样,但后续步骤不是进行直接清理,而是令所有存活的对象一端移动,然后直接清理掉这端边界以外的内存。 255 | 256 | - 没有内存碎片 257 | - 比Mark-Sweep耗费更多的时间进行compact 258 | 259 | ### 分代收集。( GenerationalCollecting)算法 260 | 261 | - 当前商业虚拟机的垃圾收集都是采用“分代收集”( Generational Collecting)算法,根据对象不同的存活周期将内存划分为几块。 262 | - 一般是把Java堆分作新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法,譬如新生代每次GC都有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本,就可以完成收集。 263 | 264 | ### Hotspot JVM 6中共划分为三个代: 265 | 266 | - 年轻代(Young Generation) 267 | - 老年代(Old Generation)和 268 | - 永久代( Permanent Generation) 269 | 270 | ![Hotspot JVM 6中共划分为三个代](./images/drrrr.png) 271 | 272 | - 年轻代(Young Generation) 273 | 新生成的对象都放在新生代。年轻代用复制算法进行GC (理论上年轻代对象的生命周期非常短,所以适合复制算法) 274 | - 年轻代分三个区。一个Eden区,两个Survivor区(可以通过参数设置Survivor个数)。对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到一个Survivor区,当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当第二个Survivor区也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到老年代。2个Survivor是完全对称,轮流替换。 275 | - Eden和2个Survivor的缺省比例是8:1:1,也就是10%的空间会被 276 | 浪费。可以根据GClog的信息调整大小的比例 277 | 278 | - 老年代(Old Generation) 279 | - 存放了经过一次或多次GC还存活的对象 280 | - 一般采用Mark-Sweep或者Mark-Compact算法进行GC 281 | - 有多种垃圾收集器可以选择。每种垃圾收集器可以看作一个GC算法的具体实现。可以根据具体应用的需求选用合适的垃圾收集器(追求吞吐量?追求最短的响应时间?) 282 | 283 | - ~~永久代~~ 284 | - 并不属于堆(Heap).但是GC也会涉及到这个区域 285 | - 存放了每个Class的结构信息, 包括常量池、字段描述、方法描述。与垃圾收集要收集的Java对象关系不大 286 | 287 | ### 内存分配与回收 288 | 289 | - 堆上分配 290 | 大多数情况在eden上分配,偶尔会直接在old上分配细节取决于GC的实现 291 | - 栈上分配 292 | 原子类型的局部变量 293 | 294 | - GC要做的是将那些dead的对象所占用的内存回收掉 295 | - Hotspot认为没有引用的对象是dead的 296 | - Hotspot将引用分为四种: Strong、 Soft、Weak、Phantom 297 | Strong 即默认通过Object o=new Object()这种方式赋值的引用 298 | Soft、Weak、 Phantom这 三种则都是继承Reference 299 | 300 | - 在Full GC时会对Reference类型的引用进行特殊处理 301 | - Soft:内存不够时一定会被GC、长期不用也会被GC 302 | - Weak: - 定会被GC, 当被mark为dead, 会在ReferenceQueue中通知 303 | - Phantom: 本来就没引用,当从jvm heap中释放时会通知 304 | 305 | 垃圾回收器 306 | 307 | ![垃圾回收器](./images/qqq.png) 308 | 309 | ### GC回收的时机 310 | 311 | - 在分代模型的基础上,GC从时机上分为两种: Scavenge GC和Full GC 312 | - Scavenge GC (Minor GC) 313 | 触发时机:新对象生成时,Eden空间满了理论上Eden区大多数对象会在ScavengeGC回收,复制算法的执 314 | 行效率会很高,ScavengeGC时间比较短。 315 | - Full GC 316 | 对整个JVM进行整理,包括Young、Old 和Perm主要的触发时机: 1) Old满了2) Perm满了3) system.gc()效率很低,尽量减少Full GC。 317 | 318 | ### 垃圾回收器(Garbage Collector) 319 | 320 | - 分代模型: GC的宏观愿景; 321 | - 垃圾回收器: GC的具体实现 322 | - Hotspot JVM提供多种垃圾回收器,我们需要根据具体应用的需要采用不同的回收器 323 | - 没有万能的垃圾回收器,每种垃圾回收器都有自己的适用场景 324 | 325 | ### 垃圾收集器的‘并行”和并发 326 | 327 | - 并行(Parallel):指多个收集器的线程同时工作,但是用户线程处于等待状态 328 | - 并发(Concurrent):指收集器在工作的同时,可以允许用户线程工作。并发不代表解决了GC停顿的问题,在关键的步骤还是要停顿。比如在收集器标记垃圾的时候。但在清除垃圾的时候,用户线程可以和GC线程并发执行。 329 | 330 | ### Serial收集器 331 | 332 | - 最早的收集器,单线程进行GC, New和Old Generation都可以使用,在新生代,采用复制算法; 333 | - 在老年代,采用Mark-Compact算法因为是单线程GC,没有多线程切换的额外开销,简单实用 334 | Hotspot Client模式默认的收集器 335 | 336 | ![Serial收集器](./images/serial.png) 337 | 338 | ### ParNew收集器 339 | 340 | - ParNew收集器就是Serial的多线程版本,除了使用多个收集线程外,其余行为包括算法、STW、对象分配规则、回收策略等都与Seria收集器一模一样。 341 | - 对应的这种收集器是虚拟机运行在Server模式的默认新生代收集器,在单CPU的环境中,ParNew收集器并不会比Serial收集器有更好的效果 342 | 343 | - Serial收集器在新生代的多线程版本 344 | - 使用复制算法(因为针对新生代)只有在多CPU的环境下,效率才会比Serial收集器高 345 | - 可以通过-XX:ParallelGC Threads来控制GC线程数的多少。需要结合具体CPU的个数Server模式下新生代的缺省收集器 346 | 347 | ![ParNew收集器](./images/parnew.png) 348 | 349 | ### Parallel Scavenge收集器 350 | 351 | - Parallel Scavenge收集器也是一个多线程收集器,也是使用复制算法,但它的对象分配规则与回收策略都与ParNew收集器有所不同,它是以吞吐量最大化(即GC时间占总运行时间最小)为目标的收集器实现,它允许较长时间的STW换取总吞吐量最大化 352 | 353 | ### CMS ( Concurrent Mark Sweep )收集器 354 | 355 | - CMS是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高(总体GC时间最小),但它能尽可能降低GC时服务的停顿时间,CMS收集器使用的是标记一清除算法 356 | - 特点: 357 | - 追求最短停顿时间,非常适合Web应用 358 | - 只针对老年区,一般结合ParNew使用 359 | - Concurrent, GC线程和用户线程并发工作(尽量并发 ) 360 | - Mark-Sweep 361 | - 只有在多CPU环境下才有意义 362 | - 使用-XX:+UseConcMarkSweepGC打开 363 | 364 | - CMS收集器的缺点 365 | - CMS以牺牲CPU资源的代价来减少用户线程的停顿。当CPU个数少于4的时候,有可能对吞吐量影响非常大 366 | - CMS在并发清理的过程中,用户线程还在跑。这时候需要预留一部分空间给用户线程 367 | - CMS用Mark-Sweep,会带来碎片问题。碎片过多的时候会容易频繁触发FullGC 368 | 369 | ![CMS收集器](./images/cms.png) 370 | 371 | ### G1收集器 372 | ![G1](./images/G1.png) 373 | 374 | - heap被划分为一个个相等的不连续的内存区域(regions) ,每个region都有一个分代的角色: eden、 survivor、 old 375 | 376 | - 对每个角色的数量并没有强制的限定,也就是说对每种分代内存的大小,可以动态变化 377 | - G1最大的特点就是高效的执行回收,优先去执行那些大量对象可回收的区域(region) 378 | 379 | - G1使用了gc停顿可预测的模型,来满足用户设定的gc停顿时间,根据用户设定的目标时间,G1会自动地选择哪些region要清除,次清除多少个region 380 | - G1从多个region中复制存活的对象,然后集中放入一个region中,同时整理、清除内存(copying收集算法) 381 | 382 | - 对比使用mark-sweep的CMS, G1使用的copying算法不会造成内存碎片; 383 | - 对比Parallel Scavenge(基于copying )、Parallel Old收集器(基于mark-compact-sweep),Parallel会对整个区域做整理导致gc停顿会比较长,而G1只是特定地整理几个region。 384 | 385 | - G1并非一个实时的收集器,与parallelScavenge-样,对gc停顿时间的设置并不绝对生效,只是G1有较高的几率保证不超过设定的gc停顿时间。与之前的gc收集器对比,G1会根据用户设定的gc停顿时间,智能评估哪几个region需要被回收可以满足用户的设定 386 | 387 | ### 分区(Region): 388 | 389 | - G1采取了不同的策略来解决并行、串行和CMS收集器的碎片、暂停时间不可控等问题一G1将 整个堆分成相同大小的分区(Region) 390 | 391 | - 每个分区都可能是年轻代也可能是老年代,但是在同,时刻只能属于某个代。年轻代、幸存区、老年代这些概念还存在,成为逻辑上的概念,这样方便复用之前分代框架的逻辑。 392 | - 在物理,上不需要连续,则带来了额外的好处有的分区内垃圾对象特别多,有的分区内垃圾对象很少,G1会优先回收垃圾对象特别多的分区,这样可以花费较少的时间来回收这些分区的垃圾,这也就是G1名字的由来,即首先收集垃圾最多的分区。 393 | 394 | - 依然是在新生代满了的时候,对整个新生代进行回收整个新生代中的对象,要么被回收、要么晋升,至于新生代也采取分区机制的原因,则是因为这样跟老年代的策略统一,方便调整代的大小 395 | - G1还是一种带压缩的收集器,在回收老年代的分区时,是将存活的对象从一个分区拷贝到另一个可用分区,这个拷贝的过程就实现了局部的压缩。 396 | 397 | ### 收集集合(CSet) 398 | 399 | - 一组可被回收的分区的集合。在CSet中存活的数据会在GC过程中被移动到另一个可用分区,CSet中的分区可以来自eden空间、survivor空间、 或者老年代 400 | 401 | ### 已记忆集合(RSet) : 402 | 403 | - RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构( 谁引用了我的对象)RSet的价值在于使得垃圾收集器不需要扫描整个堆找到谁引用了当前分区中的对象,只需要扫描RSet即可。 404 | 405 | - Region1和Region3中的对象都引用了Region2中的对象,因此在Region2的RSet中记录了这两个引用。 406 | 407 | ![](./images/region.png) 408 | 409 | - G1 GC是在points-out的card table之上再加了一层结构来构成points-into RSet:每个region会记录下到底哪些别的 410 | region有指向自己的指针,而这些指针分别在哪些card的范围内。 411 | - 这个RSet其实是一个hash table,key是别的region的起始地址,value是一个集合,里面的元素是card table的index. 412 | 举例来说,如果region A的RSet里有一项的key是region B,value里有index为1234的card,它的意思就是region B的 413 | 一个card里 有引用指向region A。所以对region A来说,该RSet记录的是points-into的关系;而card table仍然记录了points-out的关系。 414 | 415 | - Snapshot-AtThe-Beginning(SATB):SATB是G1 GC在并发标记阶段使用的增量式的标记算法, 416 | - 并发标记是并发多线程的,但并发线程在同一时刻只扫描一个分区 417 | 418 | ### 参考链接: 419 | 420 | ### G1相对于CMS的优势 421 | 422 | - G1在压缩空间方面有优势 423 | - G1通过将内存空间分成区域(Region) 的方式避免内存碎片问题Eden、Survivor、 Old区不再固定,在内存使用效率上来说更灵活 424 | - G1可以通过设置预期停顿时间( Pause Time) 来控制垃圾收集时间,避免应用雪崩现象 425 | - G1在回收内存后会马上同时做合并空闲内存的工作,而CMS默认是在STW ( stop the world) 的时候做 426 | - G1会在Young GC中使用,而CMS只能在Old区使用 427 | 428 | ### G1的适合场景 429 | 430 | - 服务端多核CPU、JVM内存占用较大的应用 431 | - 应用在运行过程中会产生大量内存碎片、需要经常压缩空间 432 | - 想要更可控、可预期的GC停顿周期:防止高并发下应用的雪崩现象 433 | 434 | ### G1 GC模式 435 | 436 | - G1提供了两种GC模式,Young GC和Mixed GC, 两种都是完全Stop The World的 437 | - Young GC:选定所有年轻代里的Region。通过控制年轻代的Region个数,即年轻代内存大小,来控制Young GC的时间开销。 438 | - Mixed GC:选定所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region 439 | 440 | - Mixed GC不是Full GC,它只能回收部分老年代的Region,如果Mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行MixedGC,就会使用serialold GC (Full GC)来收集整个GC heap。 所以本质上,G1是不提供Full GC的 441 | 442 | ### global concurrent marking 443 | 444 | - **初始标记( initial mark, STW)** :它标记了从GCRoot开始直接可达的对象。 445 | - **并发标记( Concurrent Marking)** :这个阶段从GC Root开始对heap中的对象进行标记,标记线 446 | 程与应用程序线程并发执行,并且收集各个Region的存活对象信息。 447 | - **重新标记( Remark, STW)** :标记那些在并发标记阶段发生变化的对象,将被回收。 448 | - **清理(Cleanup)** :清除空Region (没有存活对象的),加入到free list。 449 | 450 | - 第一阶段initial mark是共用了Young GC的暂停,这是因为他们可以复用rootscan操作,所以可以说global concurrent marking是伴随Young GC而发生的 451 | - 第四阶段Cleanup只是回收了没有存活对象的Region,所以它并不需要STW。 452 | 453 | ### G1在运行过程中的主要模式 454 | 455 | - YGC(不同于CMS) 456 | - G1 YGC在Eden充满时触发,在回收之后所有之前属于Eden的区块全部变成空白,即不属于任何一个分区( Eden、Survivor、Old ) 457 | - YGC执行步骤: 458 | - 阶段1:根扫描 459 | 静态和本地对象被描 460 | - 阶段2:更新RS 461 | 处理dirty card队列更新RS 462 | - 阶段3:处理RS 463 | 检测从年轻代指向老年代的对象 464 | - 阶段4:对象拷贝 465 | 拷贝存活的对象到survivor/old区域 466 | - 阶段5:处理引用队列 467 | 软引用,弱引用,虚引用处理 468 | - 并发阶段(global concurrent marking) 469 | - 混合模式 470 | - Full GC (一 般是G1出现问题时发生,本质上不属于G1,G1进行的回退策略(回退为:Serial Old GC)) 471 | 472 | ### 什么时候发生MixedGC? 473 | 474 | - 由一些参数控制,另外也控制着哪些老年代Region会被选入CSet (收集集合) 475 | - **G1HeapWastePercent**:在globalconcurrent marking结束之后,我们可以知道oldgenregions中有多少空间要被回收,在每次YGC之后和再次发生MixedGC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才 会发生Mixed GC 476 | - **G1MixedGCLiveThresholdPercent**: oldgeneration region中的存活对象的占比,只有在此参数之下,才会被选入CSet 477 | - **G1MixedGCCountTarget**:一 次globalconcurrent marking之后,最多执行Mixed GC的次数 478 | - **G1OldCSetRegionThresholdPercent**:次Mixed GC中能被选入CSet的最多old generation region数量 479 | 480 | ### 三色标记算法 481 | 482 | 提到并发标记,我们不得不了解并发标记的三色标记算法。它是描述追踪式回收器的一种有效的方法,利用它可以推演回收器的正确性 483 | 484 | - 我们将对象分成三种类型: 485 | - **黑色**:根对象,或者该对象与它的子对象都被扫描过(对象被标记了,且它的所有field也被标记完了) 486 | - **灰色**:对象本身被扫描,但还没扫描完该对象中的子对象( 它的field还没有被标记或标记完) 487 | - **白色**:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象(对象没有被标记到) 488 | 489 | #### 提到并发标记,我们不得不了解并发标记的三色标记算法。它是描述追踪式回收器的一种有效的方法,利用它可以推演回收器的正确性 490 | 491 | 遍历了所有可达的对象后,所有可达的对象都变成了黑色。不可达的对象即为白色,需要被清理,如图: 492 | 493 | ![三色标记算法](./images/sanmark.gif) 494 | 495 | - 但是如果在标记过程中,应用程序也在运行,那么对象的指针就有可能改变。这样的话,我们就会遇到一个问题:对象丢失问题 496 | 497 | ![](./images/sans3.png) 498 | 499 | 这时候应用程序执行了以下操作: 500 | A.c=C 501 | B.c=null 502 | 这样,对象的状态图变成如下情形: 503 | 504 | ![](./images/sans2.png) 505 | 506 | 这时候垃圾收集器再标记扫描的时候就会变成下图这样 507 | 508 | ![](./images/sans1.png) 509 | 510 | - **很显然,此时C是白色,被认为是垃圾需要清理掉,显然这是不合理的** 511 | 512 | ### SATB 513 | 514 | - 在G1中,使用的是SATB ( Snapshot-At-The- Beginning)的方式,删除的时候记录所有的对象 515 | - 它有3个步骤 516 | - 在开始标记的时候生成一个快照图,标记存活对象 517 | - 在并发标记的时候所有被改变的对象入队(在writebarrier里把所有旧的引用所指向的对象都变成非白的) 518 | - 可能存在浮动垃圾,将在下次被收集 519 | 520 | ### G1混合式回收 521 | 522 | - G1到现在可以知道哪些老的分区可回收垃圾最多。当全局并发标记完成后,在某个时刻,就开始了Mixed GC。这些垃圾回收被称作“混合式”是因为他们不仅仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的分区混合式GC也是采用的复制清理策略,当GC完成后,会重新释放空间 523 | 524 | ### SATB详解 525 | 526 | - SATB是维持并发GC的一种手段。G1并发的基础就是SATB。SATB可以理解成在GC开始之前对堆内存里的对象做次快照,此时活的对象就认为是活的,从而形成了一个对象图。 527 | - 在GC收集的时候,新生代的对象也认为是活的对象,除此之外其他不可达的对象都认为是垃圾对象 528 | 529 | ### 如何找到在GC过程中分配的对象呢? 530 | 531 | - 每个region记录着两个top-at-mark-start ( TAMS 指针,分别为prevTAMS和nextTAMS。在TAMS以上的对象就是新分配的,因而被视为隐式marked。 532 | - 通过这种方式我们就找到了在GC过程中新分配的对象,并把这些对象认为是活的对象。 533 | 534 | - 解决了对象在GC过程中分配的问题,那么在GC过程中引用发生变化的问题怎么解决呢? 535 | - G1给出的解决办法是通过WriteBarrier.Write Barrier就是对引用字段进行赋值做了额外处理。通过Write Barrier就可以了解到哪些引用对象发生了什么样的变化 536 | 537 | ### mark的过程就是遍历heap标记live object的过程, 538 | 539 | - 采用的是三色标记算法,这三种颜色为white(表示还未访问到)、gray(访问到但是它用到的引用还没有完全扫描、black( 访问到而且其用到的引用已经完全扫描完) 540 | - 整个三色标记算法就是从GCroots出发遍历heap,针对可达对象先标记white为gray,然后再标记gray为black;遍历完成之后所有可达对象都是black的,所有white都是可以回收的 541 | 542 | - SATB仅仅对于在marking开始阶段进行"snapshot"(marked all reachable at markstart),但是concurrent的时候并发修改可能造成对象漏标记 543 | - 对black新引用了一个white对象,然后又从gray对象中删除了对该white对象的引用,这样会造成了该white对象漏标记 544 | - 对black新引用了一个white对象,然后从gray对象删了一个引用该white对象的white对象,这样也会造成了该white对象漏标记, 545 | - 对black新引用了一个刚new出来的white对象,没有其他gray对象引用该white对象,这样也会造成了该white对象漏标记 546 | 547 | - 对于三色算法在concurrent的时候可能产生的漏标记问题,SATB在marking阶段中,对于从gray对象移除的目标引用对象标记为gray,对于black引用的新产生的对象标记为black;由于是在开始的时候进行snapshot,因而可能存在Floating Garbage 548 | 549 | ### 漏标与误标 550 | 551 | - 误标没什么关系,顶多造成浮动垃圾,在下次gc还是可以回收的,但是漏标的后果是致命的,把本应该存活的对象给回收了,从而影响的程序的正确性 552 | 553 | - 漏标的情况只会发生在白色对象中,且满足以下任意一个条件 554 | 555 | - 并发标记时,应用线程给一个黑色对象的引用类型字段赋值 了该白色对象 556 | 557 | - 并发标记时,应用线程删除所有灰色对象到该白色对象的引用 558 | 559 | - 对于第一种情况,利用post-write barrier,记录所有新增的引用关系,然后根据这些引用关系为根重新扫描一-遍 560 | - 对于第二种情况,利用pre-write barrier,将所有即将被删除的引用关系的旧引用记录下来,最后以这些旧引用为根重新扫描一遍 561 | 562 | ### 停顿预测模型 563 | 564 | - G1收集器突出表现出来的一点是通过一个停顿预测模型根据用户配置的停顿时间来选择CSet的大小,从而达到用户期待的应用程序暂停时间。 565 | - 通过-XX:MaxGCPauseMillis参数来设置。这一点有点类似于ParallelScavenge收集器。 关于停顿时间的设置并不是越短越好。 566 | 567 | - 设置的时间越短意味着每次收集的CSet越小,导致垃圾逐步积累变多,最终不得不退化成SerialGC;停顿时间设置的过长,那么会导致每次都会产生长时间的停顿,影响了程序对外的响应时间 568 | 569 | ### G1的收集模式 570 | 571 | - G1的运行过程是这样的:会在Young GC和Mixed GC之间不断地切换运行,同时定期地做全局并发标记,在实在赶不上对象创建速度的情况下 使用Full GC(Serial GC)。 572 | - 初始标记是在Young GC.上执行的,在进行全局并发标记的时候不会做MixedGC,在做MixedGC的时候也不会启动初始标记阶段。 573 | - 当MixedGC赶不上对象产生的速度的时候就退化成FullGC,这一点是需要重点调优的地方 574 | 575 | ### G1最佳实践 576 | 577 | - 不要设置新生代和老年代的大小,G1收集器在运行的时候会调整新生代和老年代 578 | 的大小。通过改变代的大小来调整对象晋升的速度以及晋升年龄,从而达到我们为收集器设置的暂停时间目标。 579 | - 设置了新生代大小相当于放弃了G1为我们做的自动调优。我们需要做的只是设置整个堆内存的大小,剩下的交给G1自已去分配各个代的大小即可。 580 | 581 | - 不断调优暂停时间指标 582 | - 通过-XX:MaxGCPauseMillis=x可以设置启动应用程序暂停的时间,G1在运行的时候会根据这个参数选择CSet来满足响应时间的设置。一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样),但如果设置成50ms就不太合理。暂停时间设置的太短,就会导致出 现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。 583 | 584 | - 关注Evacuation Failure 585 | - Evacuation(表示copy) Failure类似于CMS里面的晋升失败,堆空间的垃圾太多导致无法完成Region之间的拷贝,于是不得不退化成Full GC来做一次全局范围内的垃圾收集 586 | 587 | ### G1日志解析: 588 | 589 | ```java 590 | 591 | /** 592 | * Created BY poplar ON 2019/11/30 593 | * G1日志分析 594 | * 虚拟机相关参数: 595 | * -verbose:gc 596 | * -Xms10m 597 | * -Xmx10m 598 | * -XX:+UseG1GC 表示垃圾收集器使用G1 599 | * -XX:+PrintGCDetails 600 | * -XX:+PrintGCDateStamps 601 | * -XX:MaxGCPauseMillis=200m 设置垃圾收集最大停顿时间 602 | */ 603 | public class G1LogAnalysis { 604 | 605 | public static void main(String[] args) { 606 | int size = 1024 * 1024; 607 | byte[] bytes1 = new byte[size]; 608 | byte[] bytes2 = new byte[size]; 609 | byte[] bytes3 = new byte[size]; 610 | byte[] bytes4 = new byte[size]; 611 | System.out.println("hello world"); 612 | } 613 | } 614 | /** 615 | * GC日志: 616 | * 2019-11-30T16:13:41.663+0800: [GC pause (G1 Humongous Allocation【说明分配的对象超过了region大小的50%】) (young) (initial-mark), 0.0014516 secs] 617 | * [Parallel Time: 1.1 ms, GC Workers: 4【GC工作线程数】] 618 | * [GC Worker Start (ms): Min: 167.0, Avg: 167.1, Max: 167.1, Diff: 0.1]【几个垃圾收集工作的相关信息统计】 619 | * [Ext Root Scanning (ms): Min: 0.4, Avg: 0.4, Max: 0.4, Diff: 0.1, Sum: 1.6] 620 | * [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] 621 | * [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0] 622 | * [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] 623 | * [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] 624 | * [Object Copy (ms): Min: 0.6, Avg: 0.6, Max: 0.6, Diff: 0.0, Sum: 2.4] 625 | * [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] 626 | * [Termination Attempts: Min: 1, Avg: 1.3, Max: 2, Diff: 1, Sum: 5] 627 | * 【上面的几个步骤为YOUNG GC的固定执行步骤】 628 | * 阶段1:根扫描 629 | * 静态和本地对象被描 630 | * 阶段2:更新RS 631 | * 处理dirty card队列更新RS 632 | * 阶段3:处理RS 633 | * 检测从年轻代指向老年代的对象 634 | * 阶段4:对象拷贝 635 | * 拷贝存活的对象到survivor/old区域 636 | * 阶段5:处理引用队列 637 | * 软引用,弱引用,虚引用处理 638 | * [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2] 639 | * [GC Worker Total (ms): Min: 1.0, Avg: 1.1, Max: 1.1, Diff: 0.1, Sum: 4.2] 640 | * [GC Worker End (ms): Min: 168.1, Avg: 168.1, Max: 168.1, Diff: 0.0] 641 | * [Code Root Fixup: 0.0 ms] 642 | * [Code Root Purge: 0.0 ms] 643 | * [Clear CT: 0.1 ms]【清楚cardTable所花费时间】 644 | * [Other: 0.3 ms] 645 | * [Choose CSet: 0.0 ms] 646 | * [Ref Proc: 0.1 ms] 647 | * [Ref Enq: 0.0 ms] 648 | * [Redirty Cards: 0.1 ms] 649 | * [Humongous Register: 0.0 ms] 650 | * [Humongous Reclaim: 0.0 ms] 651 | * [Free CSet: 0.0 ms] 652 | * [Eden: 2048.0K(4096.0K)->0.0B【新生代清理后】(2048.0K) Survivors: 0.0B->1024.0K Heap: 3800.2K(10.0M)->2752.1K(10.0M)] 653 | * [Times: user=0.00 sys=0.00, real=0.01 secs] 654 | * 2019-11-30T16:13:41.671+0800: [GC concurrent-root-region-scan-start] 655 | * 2019-11-30T16:13:41.671+0800: [GC concurrent-root-region-scan-end, 0.0008592 secs] 656 | * 2019-11-30T16:13:41.671+0800: [GC concurrent-mark-start] 657 | * 2019-11-30T16:13:41.672+0800: [GC concurrent-mark-end, 0.0000795 secs] 658 | * 2019-11-30T16:13:41.672+0800: [GC remark 2019-11-30T16:13:41.672+0800: [Finalize Marking, 0.0001170 secs] 2019-11-30T16:13:41.672+0800: [GC ref-proc, 0.0002159 secs] 2019-11-30T16:13:41.672+0800: [Unloading, 0.0005800 secs], 0.0011024 secs] 659 | * [Times: user=0.00 sys=0.00, real=0.00 secs] 660 | * 2019-11-30T16:13:41.673+0800: [GC cleanup 4800K->4800K(10M), 0.0003239 secs] 661 | * [Times: user=0.00 sys=0.00, real=0.00 secs] 662 | * hello world 663 | * Heap 664 | * garbage-first heap total 10240K, used 4800K [0x00000000ff600000, 0x00000000ff700050, 0x0000000100000000) 665 | * region size 1024K【说明region默认大小】, 2 young (2048K), 1 survivors (1024K) 666 | * Metaspace used 3224K, capacity 4496K, committed 4864K, reserved 1056768K 667 | * class space used 350K, capacity 388K, committed 512K, reserved 1048576K 668 | */ 669 | ``` 670 | 671 | 672 | 673 | -------------------------------------------------------------------------------- /src/resources/java字节码.md: -------------------------------------------------------------------------------- 1 | # Java字节码 2 | 3 | ### 简介 4 | 5 | - Java虚拟机不和包括java在内的任何语言绑定,它只与“Class”特定的二进制文件格式关联,Class文件中包含Java虚拟机指令集和符号表以及若干其他辅助信息。本文将以字节码的角度来研究Java虚拟机。 6 | 7 | ### 字节码 8 | 9 | - Java跨平台的原因是JVM不跨平台 10 | - 首先编写一个简单的java代码,一次为例进行讲解 11 | 12 | ### 测试1: 13 | 14 | ```java 15 | /** 16 | * Created By poplar on 2019/11/9 17 | * 源码: 18 | */ 19 | public class ByteCodeTest1 { 20 | private int a = 1; 21 | 22 | public int getA() { 23 | return a; 24 | } 25 | 26 | public void setA(int a) { 27 | this.a = a; 28 | } 29 | } 30 | ``` 31 | 32 | - 执行**javap**命令后的字节码文件 33 | 34 | ``` 35 | Compiled from "ByteCodeTest1.java" 36 | public class com.poplar.bytecode.ByteCodeTest1 { 37 | public com.poplar.bytecode.ByteCodeTest1(); 38 | public int getA(); 39 | public void setA(int); 40 | } 41 | ``` 42 | 43 | - 执行**javap -c**命令后的字节码文件 44 | 45 | ```java 46 | Compiled from "ByteCodeTest1.java" 47 | public class com.poplar.bytecode.ByteCodeTest1 { 48 | public com.poplar.bytecode.ByteCodeTest1(); 49 | Code: 50 | 0: aload_0 51 | 1: invokespecial #1 // Method java/lang/Object."":()V 52 | 4: aload_0 53 | 5: iconst_1 54 | 6: putfield #2 // Field a:I 55 | 9: return 56 | 57 | public int getA(); 58 | Code: 59 | 0: aload_0 60 | 1: getfield #2 // Field a:I 61 | 4: ireturn 62 | 63 | public void setA(int); 64 | Code: 65 | 0: aload_0 66 | 1: iload_1 67 | 2: putfield #2 // Field a:I 68 | 5: return 69 | } 70 | ``` 71 | 72 | - 执行**javap -verbose**命令后的字节码文件 73 | 74 | ```java 75 | Classfile /E:/idea-workspace/jvm-study/build/classes/java/main/com/poplar/bytecode/ByteCodeTest1.class 76 | Last modified 2019-11-9; size 503 bytes 77 | MD5 checksum 785bb46a966a166c3101fb5c64415667 78 | Compiled from "ByteCodeTest1.java" 79 | public class com.poplar.bytecode.ByteCodeTest1 80 | minor version: 0 81 | major version: 52 82 | flags: ACC_PUBLIC, ACC_SUPER 83 | Constant pool: 84 | #1 = Methodref #4.#20 // java/lang/Object."":()V 85 | #2 = Fieldref #3.#21 // com/poplar/bytecode/ByteCodeTest1.a:I 86 | #3 = Class #22 // com/poplar/bytecode/ByteCodeTest1 87 | #4 = Class #23 // java/lang/Object 88 | #5 = Utf8 a 89 | #6 = Utf8 I 90 | #7 = Utf8 91 | #8 = Utf8 ()V 92 | #9 = Utf8 Code 93 | #10 = Utf8 LineNumberTable 94 | #11 = Utf8 LocalVariableTable 95 | #12 = Utf8 this 96 | #13 = Utf8 Lcom/poplar/bytecode/ByteCodeTest1; 97 | #14 = Utf8 getA 98 | #15 = Utf8 ()I 99 | #16 = Utf8 setA 100 | #17 = Utf8 (I)V 101 | #18 = Utf8 SourceFile 102 | #19 = Utf8 ByteCodeTest1.java 103 | #20 = NameAndType #7:#8 // "":()V 104 | #21 = NameAndType #5:#6 // a:I 105 | #22 = Utf8 com/poplar/bytecode/ByteCodeTest1 106 | #23 = Utf8 java/lang/Object 107 | { 108 | public com.poplar.bytecode.ByteCodeTest1(); 109 | descriptor: ()V 110 | flags: ACC_PUBLIC 111 | Code: 112 | stack=2, locals=1, args_size=1 113 | 0: aload_0 114 | 1: invokespecial #1 // Method java/lang/Object."":()V 115 | 4: aload_0 116 | 5: iconst_1 117 | 6: putfield #2 // Field a:I 118 | 9: return 119 | LineNumberTable: 120 | line 6: 0 121 | line 7: 4 122 | LocalVariableTable: 123 | Start Length Slot Name Signature 124 | 0 10 0 this Lcom/poplar/bytecode/ByteCodeTest1; 125 | 126 | public int getA(); 127 | descriptor: ()I 128 | flags: ACC_PUBLIC 129 | Code: 130 | stack=1, locals=1, args_size=1 131 | 0: aload_0 132 | 1: getfield #2 // Field a:I 133 | 4: ireturn 134 | LineNumberTable: 135 | line 10: 0 136 | LocalVariableTable: 137 | Start Length Slot Name Signature 138 | 0 5 0 this Lcom/poplar/bytecode/ByteCodeTest1; 139 | 140 | public void setA(int); 141 | descriptor: (I)V 142 | flags: ACC_PUBLIC 143 | Code: 144 | stack=2, locals=2, args_size=2 145 | 0: aload_0 146 | 1: iload_1 147 | 2: putfield #2 // Field a:I 148 | 5: return 149 | LineNumberTable: 150 | line 14: 0 151 | line 15: 5 152 | LocalVariableTable: 153 | Start Length Slot Name Signature 154 | 0 6 0 this Lcom/poplar/bytecode/ByteCodeTest1; 155 | 0 6 1 a I 156 | } 157 | SourceFile: "ByteCodeTest1.java" 158 | ``` 159 | 160 | - 使用winHex打开后的文件: 161 | 162 | ![ByteCodeTest1](./images/byte.png) 163 | 164 | 1. 使用javap -verbose ByteCodeTest1命令分析一个字节码文件时,将会分析该字节码文件的魔数,版本号,常量池,类信息,类的构造方法,类中的方法信息,类变量与成员变量的信息。 165 | 166 | 2. 魔数:所有的.class文件的前四个字节都是魔数,魔数值为固定值:0xCAFEBABE(咖啡宝贝) 167 | 168 | 3. 版本号:魔数后面4个字节是版本信息,前两个字节表示minor version(次版本号),后两个字节表示major version(主版本号),十六进制34=十进制52。所以该文件的版本号为1.8.0。低版本的编译器编译的字节码可以在高版本的JVM下运行,反过来则不行。 169 | 170 | 4. 常量池(constant pool):版本号之后的就是常量池入口,一个java类定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是class文件的资源仓库,包括java类定义的方法和变量信息,常量池中主要存储两类常量:字面量和符号引用。字面量如文本字符串、java中生命的final常量值等,符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符等。 171 | 172 | 5. 常量池的整体结构:Java类对应的常量池主要由常量池数量和常量池数组两部分共同构成,常量池数量紧跟在主版本号后面,占据两个字节,而常量池数组在常量池数量之后。常量池数组与一般数组不同的是,常量池数组中元素的类型、结构都是不同的,长度当然也就不同,但是每一种元素的第一个数据都是一个u1类型标志位,占据一个字节,JVM在解析常量池时,就会根据这个u1类型的来获取对应的元素的具体类型。 值得注意的是,常量池数组中元素的个数=常量池数-1,(其中0暂时不使用)。目的是满足某些常量池索引值的数据在特定的情况下需要表达不引用任何常量池的含义。根本原因在于索引为0也是一个常量,它是JVM的保留常量,它不位于常量表中。这个常量就对应null,所以常量池的索引从1而非0开始。 173 | 174 | 175 | ![](./images/pool.png) 176 | 177 | 6. 在JVM规范中,每个变量/字段都有描述信息,主要的作用是描述字段的数据类型,方法的参数列表(包括数量、类型和顺序)与返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符来表示,而对象类型使用字符L+对象的全限定名称来表示。为了压缩字节码文件的体积,对于基本数据类型,JVM都只使用一个大写字母来表示。如下所示:B - byte,C - char,D - double,F - float,I - int,J -l ong,S -short,Z - boolean,V - void,L-对象类型,如Ljava/lang/String; 178 | 对于数组类型来说,每一个维度使用一个前置的[ 来表示,如int[]表示为[I ,String [][]被记录为[[Ljava/lang/String; 179 | 7. 用描述符描述方法的时候,用先参数列表后返回值的方式来描述。参数列表按照参数的严格顺序放在一组()之内,如方法String getNameByID(int id ,String name) 180 | (I,Ljava/lang/String;)Ljava/lang/String; 181 | Java字节码整体结构 182 | 183 | ![](./images/gg.png) 184 | 185 | ### Class字节码中有两种数据类型: 186 | (1)字节数据直接量:这是基本的数据类型。共细分为u1、u2、u4、u8四种,分别代表连续的1个字节、2个字节、4个字节、8个字节组成的整体数据。 187 | (2)表/数组:表是由多个基本数据或其他表,按照既定顺序组成的大的数据集合。表是有结构的,它的结构体:组成表的成分所在的位置和顺序都是已经严格定义好的。 188 | 189 | Access Falgs: 190 | 访问标志信息包括了该class文件是类还是接口,是否被定义成public,是否是abstract,如果是类,是否被定义成final。 191 | 192 | ![](./images/re.png) 193 | 194 | ![](./images/hff.png) 195 | 196 | - 0x0021是0x0020和0x0001的并集,表示ACC_PUBLIC和ACC_SUPER 197 | 0x0002:private 198 | 199 | - 字段表(Fields): 200 | 字段表用于描述类和接口中声明的变量。这里的字段包含了类级别变量和实例变量,但是不包括方法内部声明的局部变量。 201 | 202 | 203 | 204 | ![](./images/fieled.png) 205 | 206 | - 方法表 207 | 方法的属性结构: 208 | 方法中的每个属性都是一个attribute_info结构: 209 | (1)JVM预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,供运行时使用; 210 | (2)不同的attribute通过attribute_name_index来区分。 211 | 212 | - attribute_info格式: 213 | attribute_info{ 214 | u2 attribute_name_index; 215 | u4 attribute_length; 216 | u1 info[attribute_length] 217 | } 218 | 219 | - attribute_name_index值为code,则为Code结构 220 | Code的作用是保存该方法的结构,所对应的的字节码 221 | ![](./images/dss.png) 222 | 223 | attribute_length:表示attribute所包含的字节数,不包含attribute_name_index和attribute_length 224 | max_stacks:表示这个方法运行的任何时刻所能达到的操作数栈的最大深度 225 | max_locals:表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量 226 | code_length:表示该方法所包含的字节码的字节数以及具体的指令码。具体的字节码是指该方法被调用时,虚拟机所执行的字节码 227 | exception_table:存放处理异常的信息,每个exception_table表,是由start_pc、end_pc、hangder_pc、catch_type组成 228 | start_pc、end_pc:表示在code数组中从start_pc到end_pc(包含start_pc,不包含end_pc)的指令抛出的异常会由这个表项来处理 229 | hangder_pc:表示处理异常的代码的开始处。 230 | catch_type:表示会被处理的异常类型,它指向常量池中的一个异常类。当catch_type=0时,表示处理所有的异常。 231 | 232 | - 附加其他属性: 233 | 234 | - LineNumbeTable_attribute: 235 | ![](./images/rew.png) 236 | 237 | 这个属性表示code数组中,字节码与java代码行数之间的关系,可以在调试的时候定位代码执行的行数。 238 | 239 | - LocalVariableTable :结构类似于 LineNumbeTable_attribute 240 | 对于Java中的任何一个非静态方法,至少会有一个局部变量,就是this。 241 | 242 | - 字节码查看工具:jclasslib 243 | http://github.com/ingokegel/jclasslib 244 | 245 | - 测试2 ------- 反编译分析MyTest2.class 246 | static变量会导致出现static代码块 247 | 248 | 249 | ### 测试3: 250 | 251 | ```java 252 | package com.poplar.bytecode; 253 | 254 | /** 255 | * Created By poplar on 2019/11/9 256 | * 从字节码分析得出的结论: 257 | * 成员变量的初始化是在构造方法中完成的,有多少个构造方法,初始化指令就会调用几次 258 | * 静态成员变量同样是在clinit方法完成的,不管有多少个静态变量都是在该方法完成初始化 259 | */ 260 | public class ByteCodeTest2 { 261 | 262 | String str = "Welcome"; 263 | 264 | private int x = 5; 265 | 266 | public static Integer in = 10; 267 | 268 | public ByteCodeTest2(String str) { 269 | this.str = str; 270 | } 271 | 272 | public ByteCodeTest2(String str, int x) { 273 | this.str = str; 274 | this.x = x; 275 | } 276 | 277 | public ByteCodeTest2() { 278 | 279 | } 280 | 281 | public static void main(String[] args) { 282 | ByteCodeTest2 byteCodeTest2 = new ByteCodeTest2(); 283 | byteCodeTest2.setX(8); 284 | in = 20; 285 | } 286 | 287 | private synchronized void setX(int x) { 288 | this.x = x; 289 | } 290 | 291 | public void test(String str) { 292 | synchronized (this) {//给当前对象上锁 293 | System.out.println("Hello World"); 294 | } 295 | } 296 | 297 | //给类字节码码上锁 298 | public static synchronized void test() { 299 | } 300 | 301 | static { 302 | System.out.println(); 303 | } 304 | } 305 | 306 | ``` 307 | 308 | ### 测试4 309 | 310 | ```java 311 | package com.poplar.bytecode; 312 | 313 | import java.io.FileInputStream; 314 | import java.io.FileNotFoundException; 315 | import java.io.IOException; 316 | import java.io.InputStream; 317 | import java.net.ServerSocket; 318 | 319 | /** 320 | * Created By poplar on 2019/11/10 321 | * 对于Java类中的每一个实例方法(非static方法) ,其在编译后所生成的字节码当中,方法参数的数量总是会比源代码中方法数的数量多一个(this) , 322 | * 它位于方法的第一个参数位置处;这样,我们就可以在Java的实例方法中使用this来去访问当前对象的属性以及其他方法。 323 | * 这个操作是在编译期间完成的,即由javac编译器在编译的时候将对this的访问转化为对一个普通实例方法参数的访问; 324 | * 接下来在运行期间由JVM在调用实例方法时,自动向实例方法传入this参数.所以,在实例方法的局部变量表中,至少会有一个指向当前对象的局部变量 325 | */ 326 | 327 | /** 328 | * Java字节码对于异常的处理方式: 329 | * 1.统一采用异常表的方式来对异常进行处理; 330 | * 2.在jdk1.4.2之前的版本中,并不是使用异常表的方式对异常进行处理的,而是采用特定的指令方式; 331 | * 3.当异常处理存在finally语句块时,现代化的JVM采取的处理方式是将finally语句内的字节码拼接到每个catch语句块后面。 332 | * 也就是说,程序中存在多少个catch,就存在多少个finally块的内容。 333 | */ 334 | public class ByteCodeTest3 { 335 | 336 | public void test() throws IOException, FileNotFoundException { 337 | 338 | try { 339 | InputStream is = new FileInputStream("test.txt"); 340 | 341 | ServerSocket serverSocket = new ServerSocket(9999); 342 | serverSocket.accept(); 343 | throw new RuntimeException(); 344 | 345 | } catch (FileNotFoundException ex) { 346 | 347 | } catch (IOException ex) { 348 | 349 | } catch (Exception ex) { 350 | 351 | } finally { 352 | System.out.println("finally"); 353 | } 354 | } 355 | } 356 | 357 | ``` 358 | 359 | ### 注意: 360 | 361 | - 对于Java类中的每一个实例方法(非static方法) ,其在编译后所生成的字节码当中,方法参数的数量总是会比源代码中方法数的数量多一个(this) , 362 | 它位于方法的第一个参数位置处;这样,我们就可以在Java的实例方法中使用this来去访问当前对象的属性以及其他方法。 363 | 这个操作是在编译期间完成的,即由javac编译器在编译的时候将对this的访问转化为对一个普通实例方法参数的访问; 364 | 接下来在运行期间由JVM在调用实例方法时,自动向实例方法传入this参数.所以,在实例方法的局部变量表中,至少会有一个指向当前对象的局部变量 365 | - Java字节码对于异常的处理方式: 366 | 1.统一采用异常表的方式来对异常进行处理; 367 | 2.在jdk1.4.2之前的版本中,并不是使用异常表的方式对异常进行处理的,而是采用特定的指令方式; 368 | 3.当异常处理存在finally语句块时,现代化的JVM采取的处理方式是将finally语句内的字节码拼接到每个catch语句块后面。 369 | 也就是说,程序中存在多少个catch,就存在多少个finally块的内容。 370 | 371 | - 栈帧(stack frame): 372 | 用于帮助虚拟机执行方法调用和方法执行的数据结构 373 | 栈帧本身是一种数据结构,封装了方法的局部变量表,动态链接信息,方法的返回地址以及操作数栈等信息。 374 | 符号引用:符号引用以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,符号引用和虚拟机的布局无关。(在编译的时候一个每个java类都会被编译成一个class文件,但在编译的时候虚拟机并不知道所引用类的地址,多以就用符号引用来代替,而在这个解析阶段就是为了把这个符号引用转化成为真正的地址的阶段。) 375 | 直接引用:(1)直接指向目标的指针(指向对象,类变量和类方法的指针)(2)相对偏移量。(指向实例的变量,方法的指针)(3)一个间接定位到对象的句柄。 376 | 有些符号引用在加载阶段或者或是第一次使用时,转换为直接引用,这种转换叫做静态解析;另外一些符号引用则是在运行期转换为直接引用,这种转换叫做动态链接。 377 | - 助记符: 378 | 1.invokeinterface:调用接口的方法,在运行期决定调用该接口的哪个对象的特定方法。 379 | 2.invokestatic:调用静态方法 380 | 3.invokespecial:调用私有方法, 构造方法(),父类的方法 381 | 4.invokevirtual:调用虚方法,运行期动态查找的过程 382 | 5.invokedynamic:动态调用方法 383 | 384 | - 静态解析的四种场:静态方法、父类方法、构造方法、私有方法。 385 | - 以上四种方法称为非虚方法,在类加载阶段将符号引用转换为直接引用。 386 | 387 | ### 测试5: 388 | 389 | ```java 390 | package com.poplar.bytecode; 391 | 392 | /** 393 | * Created By poplar on 2019/11/10 394 | * 静态解析的四种场:静态方法、父类方法、构造方法、私有方法。 395 | * 以上四种方法称为非虚方法,在类加载阶段将符号引用转换为直接引用。 396 | */ 397 | 398 | /** 399 | * 方法的静态分派。 400 | * Grandpa g1 = new Father(); 401 | * 以上代码, g1的静态类型是Grandpa,而g1的实际类型(真正指向的类型)是Father. 402 | * 我们可以得出这样一个结论:变量的静态类型是不会发生变化的,而变量的实际类型则是可以发生变化的(多态的一种体现) 403 | * 实际变量是在运行期方可确定 404 | */ 405 | public class ByteCodeTest4 { 406 | 407 | public void test(Grandpa grandpa) { 408 | System.out.println("Grandpa"); 409 | } 410 | 411 | public void test(Father father) { 412 | System.out.println("father"); 413 | } 414 | 415 | public void test(Son son) { 416 | System.out.println("Son"); 417 | } 418 | 419 | public static void main(String[] args) { 420 | ByteCodeTest4 byteCodeTest4 = new ByteCodeTest4(); 421 | //方法重载,是一种静态的行为,编译期就可以完全确定 422 | Grandpa g1 = new Father(); 423 | Grandpa g2 = new Son(); 424 | byteCodeTest4.test(g1);//Grandpa 425 | byteCodeTest4.test(g2);//Grandpa 426 | } 427 | } 428 | 429 | class Grandpa { 430 | 431 | } 432 | 433 | class Father extends Grandpa { 434 | 435 | } 436 | 437 | class Son extends Father { 438 | 439 | } 440 | ``` 441 | 442 | ### 测试6: 443 | 444 | ```java 445 | package com.poplar.bytecode; 446 | 447 | /** 448 | * Created By poplar on 2019/11/10 449 | * 方法的动态分派 450 | * 方法的动态分派涉及到一个重要概念:方法接收者。 451 | * invokevirtua1字节码指令的多态查找流程 452 | * 比较方法重载(overload)与方法重写(overwrite) ,我们可以得到这样一个结论: 453 | * 方法重载是静态的,是编译期行为; 454 | * 方法重写是动态的,是运行期行为。 455 | */ 456 | public class ByteCodeTest5 { 457 | public static void main(String[] args) { 458 | Fruit apple = new Apple(); 459 | apple.test();//将符号引用转换为直接引用 460 | 461 | Fruit orange = new Orange(); 462 | orange.test(); 463 | } 464 | } 465 | 466 | class Fruit { 467 | 468 | public void test() { 469 | System.out.println("Fruit"); 470 | } 471 | } 472 | 473 | class Apple extends Fruit { 474 | 475 | @Override 476 | public void test() { 477 | System.out.println("Apple"); 478 | } 479 | } 480 | 481 | class Orange extends Fruit { 482 | @Override 483 | public void test() { 484 | System.out.println("Orange"); 485 | } 486 | } 487 | ``` 488 | ### 测试7: 489 | ```java 490 | package com.poplar.bytecode; 491 | 492 | /** 493 | * Created BY poplar ON 2019/12/4 494 | * 基于栈的解释器的执行过程概念模型 495 | */ 496 | public class BasicStackExecutionProcess { 497 | 498 | public int calc() { 499 | int a = 100; 500 | int b = 200; 501 | int c = 300; 502 | return (a + b) * c; 503 | 504 | /* 505 | public int calc(); 506 | descriptor: ()I 507 | flags: ACC_PUBLIC 508 | Code: 509 | stack=2, locals=4, args_size=1 510 | 0: bipush 100 执行地址偏移量为0 将100推送至栈顶 511 | 2: istore_1 执行地址偏移量为2 将栈顶的100出栈并存放到第一个局部变量Slot中 512 | 3: sipush 200 513 | 6: istore_2 514 | 7: sipush 300 515 | 10: istore_3 516 | 11: iload_1 执行地址偏移量为11 将局部变量中第一个Slot中的整型值复制到栈顶 517 | 12: iload_2 518 | 13: iadd 将栈顶的两个元素出栈并作整形加法,然后把结果重新入栈 519 | 14: iload_3 520 | 15: imul 将栈顶的两个元素出栈并作整形乘法,然后把结果重新入栈 521 | 16: ireturn 结束方法并将栈顶的值返回给方法调用者 522 | LineNumberTable: 523 | line 10: 0 524 | line 11: 3 525 | line 12: 7 526 | line 13: 11 527 | LocalVariableTable: 528 | Start Length Slot Name Signature 529 | 0 17 0 this Lcom/poplar/bytecode/BasicStackExecutionProcess; 530 | 3 14 1 a I 531 | 7 10 2 b I 532 | 11 6 3 c I 533 | */ 534 | } 535 | 536 | public static void main(String[] args) { 537 | BasicStackExecutionProcess process = new BasicStackExecutionProcess(); 538 | int res = process.calc(); 539 | System.out.println(res); 540 | } 541 | } 542 | 543 | ``` 544 | ### 动态分派: 545 | 546 | ```java 547 | package com.poplar.bytecode; 548 | 549 | /** 550 | * Created BY poplar ON 2019/12/4 551 | * 动态分派的演示与证明: 552 | * 在动态分派中虚拟机是如何知道要调用那个方法的? 553 | */ 554 | public class DynamicDispatch { 555 | 556 | static abstract class Human { 557 | public abstract void hello(); 558 | } 559 | 560 | static class Man extends Human { 561 | @Override 562 | public void hello() { 563 | System.out.println("Hello Man"); 564 | } 565 | } 566 | 567 | static class Woman extends Human { 568 | @Override 569 | public void hello() { 570 | System.out.println("Hello Woman"); 571 | } 572 | } 573 | 574 | public static void main(String[] args) { 575 | Human man = new Man(); 576 | Human woMan = new Woman(); 577 | man.hello(); 578 | woMan.hello(); 579 | 580 | man = new Woman(); 581 | man.hello(); 582 | 583 | /*public static void main(java.lang.String[]); 584 | descriptor: ([Ljava/lang/String;)V 585 | flags: ACC_PUBLIC, ACC_STATIC 586 | Code: 587 | stack=2, locals=3, args_size=1 588 | 0: new #2 // class main/java/com/poplar/bytecode/DynamicDispatch$Man 589 | 3: dup 590 | 4: invokespecial #3 // Method main/java/com/poplar/bytecode/DynamicDispatch$Man."":()V 591 | 7: astore_1 592 | 8: new #4 // class main/java/com/poplar/bytecode/DynamicDispatch$Woman 593 | 11: dup 594 | 12: invokespecial #5 // Method main/java/com/poplar/bytecode/DynamicDispatch$Woman."":()V 595 | 15: astore_2 596 | 16: aload_1 从局部变量加载一个引用 aload1是加载索引为1的引用(man),局部变量有三个(0:args; 1 :man ; 2 :woMan) 597 | 17: invokevirtual #6 // Method main/java/com/poplar/bytecode/DynamicDispatch$Human.hello:()V 598 | 20: aload_2 加载引用woMan 599 | 21: invokevirtual #6 // Method main/java/com/poplar/bytecode/DynamicDispatch$Human.hello:()V 600 | 24: new #4 // class main/java/com/poplar/bytecode/DynamicDispatch$Woman 601 | 27: dup 602 | 28: invokespecial #5 // Method main/java/com/poplar/bytecode/DynamicDispatch$Woman."":()V 603 | 31: astore_1 604 | 32: aload_1 605 | 33: invokevirtual #6 // Method main/java/com/poplar/bytecode/DynamicDispatch$Human.hello:()V 606 | 36: return 607 | LineNumberTable: 608 | line 28: 0 609 | line 29: 8 610 | line 30: 16 611 | line 31: 20 612 | line 33: 24 613 | line 34: 32 614 | line 36: 36 615 | LocalVariableTable: 616 | Start Length Slot Name Signature 617 | 0 37 0 args [Ljava/lang/String; 618 | 8 29 1 man Lmain/java/com/poplar/bytecode/DynamicDispatch$Human; 619 | 16 21 2 woMan Lmain/java/com/poplar/bytecode/DynamicDispatch$Human; 620 | } 621 | invokevirtual 运行期执行的时候首先: 622 | 找到操作数栈顶的第一个元素它所指向对象的实际类型,在这个类型里边,然后查找和常量里边Human的方法描述符和方法名称都一致的 623 | 方法,如果在这个类型下,常量池里边找到了就会返回实际对象方法的直接引用。 624 | 625 | 如果找不到,就会按照继承体系由下往上(Man–>Human–>Object)查找,查找匹配的方式就是 626 | 上面描述的方式,一直找到位为止。如果一直找不到就会抛出异常。 627 | 628 | 比较方法重载(overload)和方法重写(overwrite),我们可以得出这样的结论: 629 | 方法重载是静态的,是编译器行为;方法重写是动态的,是运行期行为。 630 | ———————————————— 631 | 版权声明:本文为CSDN博主「魔鬼_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 632 | 原文链接:https://blog.csdn.net/wzq6578702/article/details/82712042 633 | */ 634 | } 635 | } 636 | 637 | ``` 638 | 639 | ### 静态分派: 640 | 641 | ```java 642 | package com.poplar.bytecode; 643 | 644 | /** 645 | * Created BY poplar ON 2019/12/4 646 | * 静态分派的演示与证明: 647 | */ 648 | public class StaticDispatch { 649 | 650 | static abstract class Human { 651 | 652 | } 653 | 654 | static class Man extends Human { 655 | 656 | } 657 | 658 | static class Woman extends Human { 659 | 660 | } 661 | 662 | public void hello(Human param) { 663 | System.out.println("Hello Human"); 664 | } 665 | 666 | public void hello(Man param) { 667 | System.out.println("Hello Man"); 668 | } 669 | 670 | public void hello(Woman param) { 671 | System.out.println("Hello Woman"); 672 | } 673 | 674 | public static void main(String[] args) { 675 | StaticDispatch dispatch = new StaticDispatch(); 676 | /*Human man = new Man(); 677 | Human woMan = new Woman(); 678 | dispatch.hello(man); 679 | dispatch.hello(woMan);*/ 680 | 681 | Human human = new Woman(); 682 | human = new Man(); 683 | dispatch.hello((Woman) human); 684 | dispatch.hello((Man) human); 685 | //java.lang.ClassCastException: main.java.com.poplar.bytecode.WoMan cannot be cast to main.java.com.poplar.bytecode.Man 686 | } 687 | } 688 | 689 | ``` 690 | - 现代JVM在执行Java代码的时候,通常都会将解释执行与编译执行二者结合起来进行. 691 | - 所谓解释执行,就是通过解释器来读取字节码,遇到相应的指令就去执行该指令 692 | - 所谓编译执行,就是通过即时编译器(Just in Time, JIT)将字节码转换为本地机器码来执行;现代JoM会根据代码热点来生成目应的本地机器码 693 | 在布尔德E马文项目 694 | 695 | - JVM执行指令时所采取的方式是基于栈的指令集。 696 | 697 | 基于栈的指令集主要有入栈和出栈两种; 698 | 699 | 基于栈的指令集的缺点在主完成相同的操作,指令集通常要比基于寄存器的指令集要多,指令集是在内存中完成操作的,而基于寄存器的指令集是直接由CPU来执行的,是在高速缓冲区中进行的,速度要快很多.虽然虚拟机可以采用一些优化手段,但总体来说,基于栈的指令集的执行速度要慢一些; 700 | 701 | 基手栈的指令集的优势在于它可以在不同平台之间移植,而基于寄存器的指令集是与硬件架构累密关联的,无法做到可移植。 702 | 703 | ### 运行时栈结构 704 | 705 | ![运行时栈结构](./images/stack.png) --------------------------------------------------------------------------------