java虚拟机学习之安全机制

来源:互联网 发布:windows消息机制 编辑:程序博客网 时间:2024/05/09 15:55

沙箱基本组件:

     类装载器结构

    class文件检验器

    java虚拟机及语言的安全特性

    安全管理器及Java api

1.1.1 类装载器结构

   

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:

  • 引导类加载器(Bootstrap ClassLoader):它用来加载 Java 的核心库,是用原生代码(C++)来实现的,并不继承自 java.lang.ClassLoader。
  • 扩展类加载器(ExtClassLoader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  • 系统类加载器(App ClassLoader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

类加载器也是Java类,因为其它Java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这个就是BootStrap。BootStrap它是嵌套在Java虚拟机内核中的,jvm启动,这个类就会启动,它是由c++语言编写的。Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级类加载器对象或者默认采用系统类加载器(App ClassLoader)为其父级类加载。

    

Java的类加载器分为以下几种:

  1. Bootstrap ClassLoader:Bootstrap ClassLoader用C++实现,一切的开始,是所有类加载器的最终父加载器。负责将一些关键的Java类,如java.lang.Object和其他一些运行时代码先加载进内存中。
  2. ExtClassLoader:ExtClassLoader用java实现,是Launcher.java的内部类,编译后的名字为:Launcher$ExtClassLoader.class 。此类由Bootstrap ClassLoader加载,但由于Bootstrap ClassLoader已经脱离了java体系(c++),所以Launcher$ExtClassLoader.class的Parent(父加载器)被设置为null;它用于装载Java运行环境扩展包(jre/lib/ext)中的类,而且一旦建立其加载的路径将不再改变。
  3. AppClassLoader:AppClassLoader用java实现,也是是Launcher.java的内部类,编译后的名字为:Launcher$AppClassLoader.class 。AppClassLoader是当Bootstrap ClassLoader加载完ExtClassLoader后,再被Bootstrap ClassLoader加载。所以ExtClassLoader和AppClassLoader都是被Bootstrap ClassLoader加载,但AppClassLoader的Parent被设置为ExtClassLoader。可见Parent和由哪个类加载器来加载不一定是对应的。这个类装载器是我们经常使用的,可以调用ClassLoader.getSystemClassLoader() 来获得(实例1中使用了这个方法),如果程序中没有使用类装载器相关操作设定或者自定义新的类装载器,那么我们编写的所有java类都会由它来装载。而它的查找区域就是我们常常说到的Classpath,一旦建立其加载路径也不再改变。
  4.  ClassLoader:ClassLoader一般我们自定义的ClassLoader从ClassLoader类继承而来。比如:URLClassloader是ClassLoader的一个子类,而URLClassloader也是ExtClassLoader和AppClassLoader的父类(注意不是父加载器)。
  1. BootStrap------>JRE/lib/rt.jar   
  2. ExtClassLoader---------->JRE/lib/ext/*.jar   
  3. AppClassLoader---------->CLASSPATH指定的所有jar或目录。
转载:http://blog.csdn.net/xw13106209/article/details/7030821

运行时包(runtime package)

由同一类装载器定义装载的属于相同包的类组成了运行时包,决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看的定义类装载器是否相同。只有属于同一运行时包的类才能互相访问包可见的类和成员。这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。假设用户自己定义了一个类java.lang.Yes,并用用户自定义的类装载器装载,由于java.lang.Yes和核心类库java.lang.*由不同的装载器装载,它们属于不同的运行时包,所以java.lang.Yes不能访问核心类库java.lang中类的包可见的成员。


1.1.2 class文件检验器



    class文件检验器保证装载的class文件内容有正确的内部结构,检验class文件的问题抛出异常。因为class文件是一个字节序列,所以虚拟机无法分辨class文件的来源

    class文件检验器进行四趟独立的扫描

第一趟:class文件的结构检查

    class文件检验器检验class文件是否符合Java class文件的基本结构。例如每个class文件必须以四个同样的字节开始:魔数0xCAFEBABE,检验器还必须确认在class文件中声明的主版本号和次版本号,这个版本号必须在这个java虚拟机实现可以支持的范围之内。

    必须检验确认这个class文件没有被删节,尾部也没有附带其他的字节。 class文件每个组成部分会声明他的长度和类型,检验器可以是用组成部分的类型和长度来确定整个class文件的正确总长度进而判断class文件没有被删节

    第一趟的主要目的:确保这个字节序列正确定义一个新类型,必须遵从java的class文件的固定格式,这样才能被编译在方法去中的内部数据结构。

    第二,三,四趟扫描不是在符合class文件格式的二进制数据上进行的,而是在方法区中,由实现缺德的数据结构上进行的。

 第二趟:类型数据的语义检查

    检验器查看每个组成部分,确认它们是否是其所属类型的实例,他们的结构是否正确。确认每个方法描述符都是符合特定语法的、格式正确的字符串。

   检验是否符合特定的条件,java编程语言规定的。例如强制规定除object类以外的类,都必须有一个超类。检查final类没有子类化,final方法没有被覆盖。检查常量池中的条目是否合法,常量池IDE所有索引必须指向正确类型的常量池条目。在运行时检查了一些java语言在编译时应该遵守的强制规则。

   扫描发生在方法区中,主要对于,语义,词法和语法的分析,也就是检查这个类是否能够顺利的编译!

第三趟:字节码校验
    在这一趟的校验中涉及两个比较不好理解的概念,第一个是字节码流,第二个是栈帧.

执行字节码时,一次执行操作码,java虚拟机内构成了执行线程,而每个线程会有自己的java栈就是我们说的栈帧。每一个方法都有一个栈帧。
如果学过汇编的人理解这两个概念会容易一点
字节码流=操作码+操作数,在这里可以看做汇编里的伪指令+操作数,因为这里的操作码实际上就是给jvm识别的“汇编伪指令”,而操作数的概念和汇编里的除了数据类型,并没有多大的差异
重点来看一下栈帧,栈帧其实也很好理解,栈帧里有局部变量栈操作数栈,这两块内存就是放数据的时机不同,操作数栈就是用来存放字节码指令执行的中间结果,结果或操作数,而局部变量区,就是用来存局部变量形参等,这个很好理解
这个字节码的校验过程校验的就是字节码流的合法过程,也就是校验操作数+操作码的合法性。

java的class文件编码我们之所以称之为字节码,是因为每调条操作指令都只占一个字节,除了两个例外情况,所有的操作码和他们的操作数按字节对齐,这使得字节流在传输的时候跟小,更有优势,这两个例外是这样一些操作码,在操作码和他们的操作数之间会天上一至三个字节,以便操作数都按字节对齐。

下面是一个图,描述了栈帧的结构

第四趟

符号引用的校验
由于大部分jvm的实现都是延迟加载或者说动态链接的,延迟加载的意思就是,jvm装载某个类A时,如果A类里有引用其他的类B,虚拟机并不会把这个被引用B类也同时装载入内存,而是等到执行到的时候才去装载。
而这个被引用的B类在引用它的类A中的表现形式主要被登记在了符号表中,而第四趟的这个过程就是当需要用到被引用类B的时候,将被引用类B在引用类A的符号引用名改为内存里的直接引用
所以第四趟发生的时间是不可预料的,而且发生在方法区中。总个这个过程称之为动态连接
可以简单的划分为两步
1.查找被引用的类(有必要的话就加载它)
2.将符号引用替换为直接引用,例如一个指向类、字段或方法的指针,下次再需要用到被引用类的时候直接运用直接引用,不需要再去装载。

这个过程其实在ClassLoader类中的loadClass中就可以发现它的痕迹。

loadClass有两个参数,第一个参数是类的全限定名,第二个参数就是我们要说的重点,这个参数为true的时候表示,loadClass方法会执行resolveClass的方法,这个方法就是将类中的符号引用替换为直接引用。最终调用的方法是一个本地方法 resolveClass0。

这里还有一点需要注意,Class.forName这个静态的方法我们也常用来加载class文件的字节码,那它和classLoader有什么区别?

区别就在于是否执行resolveClass这个方法,Class.forName总是承诺将符号连接进行连接和初始化,而loadClass没有这样的承诺。

总结:

第一趟扫描,在类被装载时进行,校验class文件的内部结构,保证能够被正常安全的编译
第二趟和第三趟在连接的过程中进行,这两趟基本上是语法校验,词法校验
第四趟是解析符号引用和直接引用时进行的,这次校验确认被引用的类,字段以及方法确实存在

转载:http://blog.csdn.net/yfqnihao/article/details/8258228


1.1.3 java虚拟机中内置的安全特性


     java虚拟机装载一个类,第一趟到第三趟的class文件检验,这些字节码就可以被运行了。除了第四躺的对符号引用的检验,Java虚拟机在执行字节码时还进行其他内置的安全机制的操作

    1)类型安全的引用转换

    2)结构化的内存访问

    3)自动垃圾收集

    4)数组边界检验

    5)空指针检验

    保证一个java程序只能使用类型安全的、结构化的方法去访问内存,即对内存的访问增加限制

    运行时数据空间:Java栈(每个线程一个)、一个存储字节码的方法区、垃圾收集堆

    每个虚拟机的都是开发者自己决定使用什么样的数据结构来表示运行时数据空间,放在内存什么位置,这样就能避免从class文件内容的某些数据代替某个类。

    禁止对内存进行非结构化访问

    调用本地方法时可以绕过沙箱。调用本地方法前必须确认他是可信任的


1.1.4 安全管理器和javaApi

    类装载器体系结构、class文件检验器以及java内置安全特性:保持java虚拟机的实例和它正在运行的应用程序的内部完整性,使得他们不被下载的恶意或有漏洞的代码侵犯

    安全管理器:保护虚拟机的外部资源不被虚拟机内运行的恶意或有漏洞的代码侵犯。例如checkRead()方法决定线程是否可以读取一个特定的文件。

    java程序启动时。通过将一个java.lang.securityMananger或是其子类的实例传给setSecurityManager(),以此来安装安全管理器。java1.2之后可以自定义安全管理器,通过对指向另一个不同的安全管理器对象的引用调用System.setSecurityManager实现的。



   



原创粉丝点击