JVM字节码加载过程-双亲委派机制:
类文件(磁盘,网络,jar包,war包等等中的JVM字节码文件)被类加载器(类加载器子系统)加载到JVM运行时数据区的方法区(HotSpot虚拟机专有,永久代和元空间(本地内存)是方法区实现的两种方式),并生成对应的Class对象(堆区)。也就是说类信息被加载到方法区,又在堆内存中生成其对应的Class对象做为访问类信息的接口。
不同的类加载器加载路径是不一样的
- 用户自定义的类是由应用程序类加载器AppClassLoader加载的,加载路径是classpath。
- 扩展类加载器的加载路径是$JAVA_HOME/jre/lib/ext,只能加载该路径下的类。
- 引导类加载器加载java的核心类,其加载路径为$JAVA_HOME/jre/lib/rt.jar, resources.jar或者sun.boot.class.path路径下的内容,
出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
我们知道java.lang.String类作为一个核心类(路径$JAVA_HOME/jre/lib/rt.jar),是由启动类加载器负责加载的,如果我们在工程中自定义一个java.lang.String类,并且使用它,那么我们用的是核心类中的String还是我们自定义的String呢?
然后测试一下这个类会否被加载
执行结果如下:
没有打印自定义String类中的静态代码块的打印信息,这说明没有加载我们自定义的类,这是为什么呢? 加载器AppClassLoader没有加载吗?这就跟JVM类加载器子系统的双亲委派机制有关系了。
双亲委派加载流程:
首先要交给AppClassLoader去加载,但是它会把这个任务委派给其parent加载器(AppClassLoader类中parent引用所指向的类加载器)去加载,一步一步向上委托,最后委托到BootStrapClassLoader。
BootStrapClassLoader会去核心类路径$JAVA_HOME/jre/lib/中加载java.lang.String,发现路径中有这个类,那么就将其加载到内存中,并且到此为止,别的类加载不会再尝试加载这个类。
这样我们自己定义的java.lang.String类就不会被加载了,这种双亲委派加载机制有什么好处呢?
- 防止重复加载,父加载器加载之后,子加载器不会再重新加载
- 保证安全,防止我们使用的类(比如String核心类)被替换(比如被网络上下载的相同包名类名类替换),造成风险。
双亲委派机制三种破坏方式:
1.JDK1.2以前没有双亲委派机制
2. SPI(service provider interface): JDBC、JDNI等已经加入java核心库,但是需要用户自己去实现部分接口或者抽象类的情况下,用户自己的实现类也需要被引导类加载器加载,但是这是不可能的( 因为JDBC的实现类不在引导类加载器的加载路径中。),只能委派给线程上下文类加载器去加载,这就打破了双亲加载机制。注意:线程上下文类加载器默认调用系统类加载器去加载
3.为了满足动态代码,比如代码热替换,模块热加载,比如OSGI规范的模块热加载,就打破了双亲委派机制,多个模块内的自定义类加载器之间是平行的。
沙箱安全机制:
沙箱安全机制的理念都是类似的,例如360安全卫士的沙箱安全机制,在沙箱安全环境中,测试一个不知是否安全的程序,防止未知访问系统资源并做出破坏。只是具体的实现不同而已。
JDK1.0时期的实现:
JDK1.1时期的实现:
JDK 1.2时期的实现:
再次改进了安全机制,增加了代码签名,不论本地代码或者远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,实现差异化的代码执行权限控制,如下图所示:
JDK1.6时期的实现:
参考尚硅谷,宋红康老师的教程。