目录
- 一、JVM初探
- 1.1 JVM的位置
- 1.2 JVM体系结构
- 二、双亲委派机制
- 2.1 类加载器
- 2.2 面试问题
- 三、沙箱安全机制
- 3.1 什么是沙箱
- 3.2 组成沙箱的基本条件
一、JVM初探
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM 是一种用于计算机设备的规范,它是一个虚构出来的计算机,是通过在实际计算机上仿真模拟各种计算机功能来实现的。
引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在虚拟机上运行的字节码,就可以在多种平台上不加修改的运行。
1.1 JVM的位置
1.2 JVM体系结构
本地方法接口
:JNI(Java Native Interface)
二、双亲委派机制
在介绍双亲委派机制前先介绍一下 类加载器
2.1 类加载器
作用
:加载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 loadedClass<?> 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 statssun.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);//AppClassLoaderSystem.out.println(classLoader.getParent());//ExtClassLoader所在位置:\jre\lib\extSystem.out.println(classLoader.getParent().getParent());//null 1.不存在2.java程序获取不到所在位置:rt.jar}}
运行测试结果:
2.2 面试问题
为什么需要双亲委派机制?(也就是双亲委派的优点):
- 双亲委派机制使得类加载出现层级,父类加载器加载过的类,子类加载器不会重复加载,可以防止类重复加载;
- 使得类的加载出现优先级,防止了核心API被篡改,提升了安全,所以越基础的类就会越上层进行加载,反而一般自己的写的类,就会在应用程序加载器(Application)直接加载。
如何打破双亲委派?
- 自定义类加载器,重写loadClass方法
- 使用线程上下文类加载器
三、沙箱安全机制
3.1 什么是沙箱
沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
所有的Java程序运行都可以指定沙箱,可以定制安全策略。
3.2 组成沙箱的基本条件
- 字节码校验器 (bytecode verifier): 确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。
- 类装载器(class loader): 其中类装载器在3个方面对Java沙箱起作用
1、它防止恶意代码去干涉善意的代码;
2、它守护了被信任的类库边界;
3、它将代码归入保护域,确定了代码可以进行哪些操作。
虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。
类装载器采用的机制是双亲委派模式
。
1、从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
2、由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
- 存取控制器(access controller): 存取控制器可以控制核心API对操作系统的存取权限,而这个控制策略的设定,可以由用户自己设定。
- 安全管理器(security manager): 是核心API 和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
- 安全软件包(security package): java.security 下的类和扩展包下的类,运行用户未自己的应用增加新的安全特性,包括:
1、安全提供者
2、消息摘要
3、数字签名
4、加密
5、鉴别