![bf9f4f93056e33b486d128b0b36d6786.png](https://i-blog.csdnimg.cn/blog_migrate/6a374c454d0ca66759ad80e8fc6aa104.png)
![eb94b7a3b96d79952a801691c75a1cfa.png](https://i-blog.csdnimg.cn/blog_migrate/7c972221c0bb336ffef9112f9609cd9f.png)
![1f95408e90ae693f00772b137e9286c7.png](https://i-blog.csdnimg.cn/blog_migrate/9440a66104b402e12f6aaa5379520087.png)
为什么要自定义类加载器呢?
- 隔离加载类:某些类路径一样,类名也相同,需要做类的仲裁,就需要自定义加载器,来隔离加载类。
- 修改类的加载方式
- 扩展加载源
- 防止源码泄露
用户自定义类加载器实现步骤
- 可以通过继承抽象类java.lang.ClassLoader类的方式,实现自己的类的加载器,满足一些特殊的需求
- 在JDK1.2之前,在自定义类加载器时,总会去继承ClassLoader类并重写loadClass()方法,从而实现自定义类的类加载器。但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法。而是建议把自定义类加载逻辑写在fingClass()方法中
- 在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findClass()方法及获取字节码流的方式,使自定义类加载器编写更加简洁
![535a7e17195f748c978cf0c3e3ba3d27.png](https://i-blog.csdnimg.cn/blog_migrate/1aa56201a2d08aecb10951929faccaa1.png)
字节码执行引擎是java虚拟机中最核心的部分之一。
输入的是字节码文件,处理过程是对字节码文件解析的等效过程,输出的是执行结果。
![fb08ff6e76da4e36a3932c37f7549334.png](https://i-blog.csdnimg.cn/blog_migrate/f9c3e95fb6896b4d414dc9dbf4dfd3ed.png)
![49e01e21c6c73ebf3f500a9c582e049e.png](https://i-blog.csdnimg.cn/blog_migrate/6ad40edf4bdddc3a1b668054b141d58a.png)
![21e844d89b302a513940ee9b2fa7d3ec.png](https://i-blog.csdnimg.cn/blog_migrate/a7a610fbd5a8623348447f227a18f508.png)
Compiled from "Math.java"public class com.example.demo.test1.Math { public static int initData; public static com.example.demo.bean.User user; public com.example.demo.test1.Math(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public int compute(); Code: 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: iload_1 5: iload_2 6: iadd 7: bipush 10 9: imul 10: istore_3 11: iload_3 12: ireturn public static void main(java.lang.String[]); Code: 0: new #2 // class com/example/demo/test1/Math 3: dup 4: invokespecial #3 // Method "":()V 7: astore_1 8: aload_1 9: invokevirtual #4 // Method compute:()I 12: pop 13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 16: ldc #6 // String test 18: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 21: return static {}; Code: 0: sipush 666 3: putstatic #8 // Field initData:I 6: new #9 // class com/example/demo/bean/User 9: dup 10: invokespecial #10 // Method com/example/demo/bean/User."":()V 13: putstatic #11 // Field user:Lcom/example/demo/bean/User; 16: return}
此时的jvm指令码就清楚多了,
体结构是可以看懂的,类、静态变量、构造方法、compute()方法、main()方法。我们以
compute()方法进行详细解释:
0. 将int类型常量1压入操作数栈
0: iconst_1
这一步就是1压入操作数栈
![e228b9613d6fe970c8c754d4f9083e69.png](https://i-blog.csdnimg.cn/blog_migrate/77c2e2942e976723126ca93fc699abf0.png)
1: istore_1
先给局部变量1,也就是代码中的第一个局部变量a在
局部变量表中分配内存,然后将int类型的值,也就是目前唯一的一个1存入局部变量a
![0210bac765064a46e4437a33a91483d9.png](https://i-blog.csdnimg.cn/blog_migrate/a59960a3bca01f162c6b43c2d54535e9.png)
2: iconst_2
3. 将int类型值存入局部变量2
3: istore_2
![603102a78ad08d4c49a62d782e7e1fa8.png](https://i-blog.csdnimg.cn/blog_migrate/46388c52f357bfbdbc353887ea631d4e.png)
4: iload_1
5. 从局部变量2中装载int类型值
5: iload_2
这两个代码是将局部变量1和2,也就是a和b的值装载到操作数栈中
![22c7358f6255d99bfa88b90ce9908c24.png](https://i-blog.csdnimg.cn/blog_migrate/69bfc6e36b9e187cf2a9a6176d01bab1.png)
6: iadd
iadd指令一执行,会将操作数栈中的1和2依次从栈底弹出并相加,然后把运算结果3再压入操作数栈底。
![4f70d74d749f7b37f2e715b6dfaf0afd.png](https://i-blog.csdnimg.cn/blog_migrate/86c66171638657c1b0382f94c26670fe.png)
7: bipush 10
![50744e8afe405db826a79f076cffb013.png](https://i-blog.csdnimg.cn/blog_migrate/cd6f1848c37a4045c3bbfeb25f2dc45f.png)
9: imul
这里就类似上面的加法了,将3和10弹出栈,把结果30压入栈
![4364f184dd59f9b1c7fbfdd6f362f99c.png](https://i-blog.csdnimg.cn/blog_migrate/359954ab4b65a2bf52d29d9476c61cc2.png)
10: istore_3
将30存入局部变量3,也就是c
![df269a75d3babd1e0514dbd5a84bf013.png](https://i-blog.csdnimg.cn/blog_migrate/233e699e48622e79432831fd9ee2567b.png)
11: iload_3
![da348812497be20f939f78527e3f7c71.png](https://i-blog.csdnimg.cn/blog_migrate/751ffaec09263dbe6fb34ee8c3d6f557.png)
12: ireturn
将操作数栈中的30返回
说白了赋值号=后面的就是操作数,在这些操作数进行赋值、运算的时候需要内存存放,那就是存放在操作数栈中,作为临时存放操作数的一小块内存区域。
方法出口
说白了就是方法执行完了之后要到哪里,那么我们知道compute()方法执行完之后应该回到main()方法第三行,那么当main()方法调用compute()的时候,
compute()栈帧中的方法出口
就存储了
当前要回到的位置
,那么当compute()方法执行完之后,会根据方法出口中存储的相关信息回到main()方法的相应位置。
main()方同样有自己的栈帧,在这里有些不同的地方我们讲一下。
我们上面已经知道
局部变量会存放在栈帧中的局部变量表中,那么main()方法中的math会存入其中,但是这里的math是一个对象,我们知道new出来的
对象是存放在堆中的
![4fdc70f1f30c66fc9ec20d561c1db5d0.png](https://i-blog.csdnimg.cn/blog_migrate/b5eb4740bfac2d8e22dec5618092dcec.png)
当一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用最终转换为调用方法的直接引用。
2).程序计数器 (Program Counter Register) 程序计数器也叫PC寄存器,是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。 在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果 正在执行的是本地(Native)方法 ,这个 计数器值则应为空 (Undefined)。此内存区域是唯一一个在《Java虚拟机规范》中没有规定任何 OutOfMemoryError 情况的区域。 PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码,由执行引擎读取下一条指令。![3aabb9d088d2dfcf603ec259bd2e7253.png](https://i-blog.csdnimg.cn/blog_migrate/1a48e833f42d29bca804962d71649329.png)
package PCRegister;
public class PCRegisterTest {
public PCRegisterTest() {
}
public static void main(String[] args) {
int i = 10;
int j = 20;
int k = i + j;
System.out.println(k);
}
}
图解代码:
![9b4b5117773dd883c24dc495391c14b7.png](https://i-blog.csdnimg.cn/blog_migrate/652f0f08675db53d62c76f24853e4b52.png)
public static int initData = 666;
这个initData就是静态变量,毋庸置疑是存放在方法区的
public static User user = new User();
那么这个user就有点不一样了,
user变量放在方法区,
new的User是存放在堆中的到这里我们就能意识到栈,堆,方法区之间都是有联系的。
![46891df90aeeaef909d803bdc94b6948.png](https://i-blog.csdnimg.cn/blog_migrate/ebc9e0e33debebba7ace2aa15f5caee5.png)
![6c5fdefc66018b54ac4fbbe62ea98bdb.png](https://i-blog.csdnimg.cn/blog_migrate/d8b84e603257ac144fb1dad359815479.png)
![897901ce2716e5b333c4b3623d267acd.png](https://i-blog.csdnimg.cn/blog_migrate/acc8b814d2170623b610f51969196154.png)
new Thread().start();
它在底层调用了一个start0()的方法
private native void start0();
这个方法没有实现,但又不是接口,是使用
native修饰
的,是属于本地方法,底层通过C语言实现的。
本地方法始终也是方法,每个线程在运行的时候,如果有运行到本地方法,那么必然也要产生局部变量等,那么就需要存储在本地方法栈了。如果没有本地方法,也就没有本地方法栈了。
5).堆(Heap)
![7da05fc17c55e4e19e5a98423b2ad77a.png](https://i-blog.csdnimg.cn/blog_migrate/028ebbd53b9e8dac26d4f11cf54464b8.png)
查看所有参数的默认初始值:
-XX:+PrintFlagsInitial
查看所有参数的最终值(可能会存在修改,不再是初始值)
-XX:+PrintFlagsFinal
初始堆空间内存(默认为物理内存的1/64),比如说我设置600m
-Xms:600m
最大堆空间内存(默认为物理机的1/4),比如说我设置600m
-Xmx:600m
设置新生代的大小(初始值及最大值)。比如说我设置80m
-Xmn:80m
设置新生代与老年代在堆结构的占比,比如我设置的:表示新生代占1,老年代占2,新生代占整个堆的1/3
-XX:NewRatio=2
设置新生代中Eden和S0/S1空间的占比,表示Eden:S0:S1 = 8:1:1
-XX:SurvivorRatio=8
设置新生代垃圾的最大年龄
-XX:MaxTenuringThreshold
输出详细的GC处理日志
-XX:+PrintGCDetils
打印GC简要信息
-XX:PrintGC
-verbose:gc
是否设置空间分配担保(JDK6 Update24之后,不再影响空间分配担保策略,其规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行MinorGC,否则进行FullGC。)
-XX:HandlePromotionFailure
public class Ex{ public static void main(String[] args){// String st="javac"; String s1="jav"; s1=s1+"ac";//相当于是new了一个字符串对象"javac",放入到堆内存中。 //s1="jav"+"ac"是等价于s1="javac"的,并没有在堆内存中new一个对象。 System.out.println(s1.intern()==s1); String s2="ja"; s2=s2+"va"; //会把java当作常量放到常量池里 System.out.println(s2.intern()==s2); String s3=new String("abc"); System.out.println(s3.intern()==s3); }}