JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM 是一种用于计算机设备的规范,它是一个虚构出来的计算机,是通过在实际计算机上仿真模拟各种计算机功能来实现的。
引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在虚拟机上运行的字节码,就可以在多种平台上不加修改的运行。
本地方法接口
:JNI(Java Native Interface)
在介绍双亲委派机制前先介绍一下 类加载器
作用
:加载Class文件
Java是运行是通过Java的虚拟机(JVM)的,但是它是如何运行在JVM中了呢?我们第一天学Java 就知道,编写的Java源代码需要被编译器编译成.class的字节码文件,然后再通过解释器,电脑才能执行。然后由我们得ClassLoader负责将这些class文件给加载到JVM中去执行。
JVM中提供了三层的ClassLoader:
1. 虚拟机自带的加载器
2. 启动类(根) 加载器【BOOTStrapClassLoader】: 主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
3. 扩展类加载器 【EXTClassLoader】: 主要负责加载jre/lib/ext目录下的一些扩展的jar。
4. 应用程序加载器【AppClassLoader】: 主要负责加载应用程序的主函数类
注
:加载过程 从 4到1进行加载
看一下JDK 8 中的双亲委派是怎么实现的
搜索ClassLoader 类
看到 loadClass 方法
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先检查类是否被加载过
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果父类加载器存在 就是使用父类加载器,否则使用Boot根加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 如果仍未找到,则调用 findClass 以查找该类。
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
// 如果已经加载过,就直接解析
if (resolve) {
resolveClass(c);
}
return c;
}
}
解释:双亲委派机制(安全):APP—>EXT—>BOOT【最终执行】
类加载器收到类加载的请求
将这个请求向上委托为父类加载器去完成,一直向上委托,直到启动类加载器
启动类加载器检查是否能够加载当前的这个类,能加载就结束,使用当前的加载器,否则抛出异常,通知子加载器进行加载
重复步骤 3
但是BOOT根加载器输出是null,这是java调用不到~ C,C++ Java = C+±- :去掉繁琐的东西(指针、内存管理等)
举例:我自己建立一个 java.lang.String 类,写上 static 代码块
package java.lang;
public class String {
static{
System.out.println("我是自定义的String类的静态代码块");
}
}
在另外的程序中加载 String 类,看看加载的 String 类是 JDK 自带的 String 类,还是我们自己编写的 String 类
public class StringTest {
public static void main(String[] args) {
java.lang.String str = new java.lang.String();
System.out.println("hello,atguigu.com");
StringTest test = new StringTest();
System.out.println(test.getClass().getClassLoader());
}
}
程序并没有输出我们静态代码块中的内容,可见仍然加载的是 JDK 自带的 String 类
为什么呢?
由于我们定义的String类本应用系统类加载器,但它并不会自己先加载,而是把这个请求委托给父类的加载器去执行,到了扩展类加载器发现String类不归自己管,再委托给父类加载器(引导类加载器),这时发现是java.lang包,这事就归引导类加载器管,所以加载的是 JDK 自带的 String 类
/**
* 双亲委派机制
*/
public class Car {
public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();
//不同的实例
System.out.println("car1 hashCode="+car1.hashCode());
System.out.println("car2 hashCode="+car2.hashCode());
System.out.println("car3 hashCode="+car3.hashCode());
//同一个类模版
Class<? extends Car> aClass1 = car1.getClass();
Class<? extends Car> aClass2 = car2.getClass();
Class<? extends Car> aClass3 = car3.getClass();
System.out.println("aClass1 hashCode="+aClass1.hashCode());
System.out.println("aClass2 hashCode="+aClass2.hashCode());
System.out.println("aClass3 hashCode="+aClass3.hashCode());
//由于类模版都是一个,以下就选择一个进行测试
ClassLoader classLoader = aClass1.getClassLoader();
System.out.println(classLoader); //AppClassLoader
System.out.println(classLoader.getParent()); //ExtClassLoader 所在位置:\jre\lib\ext
System.out.println(classLoader.getParent().getParent()); //null 1.不存在 2.java程序获取不到 所在位置:rt.jar
}
}
为什么需要双亲委派机制?(也就是双亲委派的优点):
如何打破双亲委派?
沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
所有的Java程序运行都可以指定沙箱,可以定制安全策略。
虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。
类装载器采用的机制是双亲委派模式
。
1、从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
2、由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。