JVM详解

JVM详细介绍:
  1)JVM执行原理
  JVM可以称为软件模拟的计算机,它可以在任何处理器安全地兼容并且执行.class字节码。其实JVM兼容的二进制字节码和操作系统的本地机器码有一定的区别,只是针对JVM上层的调用程序而言,执行过程效果一样,所以我们一般理解就是说直接用JVM来执行二进制码,实际上二者本质有一定的差异,但是这一点可以理解JVM具有跨平台性。一般情况下,编程人员都是直接编写.java的源文件,然后用Java编译器(javac命令)对源文件进行编译,生成.class文件,生成的.class文件就是我们平时所说的包含了“机器码”的文件,实际上JVM在编译和运行过程做了两件事,先是直接将源文件编译成二进制字节码.class文件,然后进行第二次处理:解释器负责将这些二进制字节码根据本地操作系统宿主环境生成相应的本地机器码解释执行。所以可以理解的一点是为什么Java语言具有跨平台性,因为JVM提供了Java运行的一个中间层,使得操作系统和上层应用相互之间是依靠JVM中间层进行通信的,也就是说Java编写的程序是运行在JVM上的;再者尽管Java确实可以做到“一次编译,多处运行”,但是在不同结构的操作系统平台生成的.class文件真正在执行的时候是存在一定差异的,只是JVM本身会根据安装的不同版本进行不同的操作系统平台下本地机器码的生成及运行,所以虽然我们在Sun公司官方网站可以下载到很多不同操作系统版本的JDK,但是执行效果一样。而且还有一点,这一步操作针对开发人员是透明的,所以真正在开发过程可以放心的是不需要去担心这种问题,只要下载的版本是和符合我们运行的操作系统的,只管进行普通编程的编译、解释运行操作就可以了。
  Java语言既是编译型语言,也是解释型语言,在.class文件生成之前,JVM通过javac命令对源代码进行编译操作,然后用JVM根据包含了二进制码的.class生成机器码并且解释执行。所以Java程序的跨平台特性主要是指字节码文件可以在任何具有Java虚拟机的计算机或者电子设备上运行,Java虚拟机中的Java解释器负责将字节码文件解释成为特定的机器码进行运行。Java虚拟机的建立需要针对不同的软硬件平台来实现,既要考虑处理器的型号,也要考虑操作系统的种类。由此在SPARC结构、X86结构、MIPS和PPC等嵌入式处理芯片上,在UNIX、Linux、Windows和部分实时操作系统上都可实现Java虚拟机,这也是为了在运行过程生成本地机器码而考虑的,使得JVM可以兼容不同的软硬件平台。

  2)JVM的安全检查机制【参考链接:http://galaxystar.javaeye.com/blog/225615
  JVM在执行字节码的时候需要经过下边的步骤:
  • 由类加载器(Class Loader)负责把类文件加载到Java虚拟机中(.class),在这个过程需要校验该类文件是否符合类文件规范
  • 字节码校验器(Bytecode Verifier)检查该类文件的代码中是否存在着某些非法操作
  • 如果字节码校验器校验通过,就由Java解释器负责把该类文件解释成机器码进行执行
  JVM在上边操作过程使用了“沙箱”模型,即把Java程序的代码和数据都限制起来放在一定的内存空间执行,不允许程序访问该内存空间以外的内存。这种访问过程不仅仅是本地的,也可以是远程的,最明显的体验是使用RMI的时候。
  ——[$]Java的“沙箱”详解——
  [1]步骤一:“双亲委派类加载模型”:
  双亲委派方式,指的是优先从顶层启动类加载器,自定向下的方式加载类模型,这种方式在“沙箱”安全模型里面做了第一道安全保障;而且这样的方式使得底层的类加载器加载的类和顶层的类加载器的类不能相互调用,哪怕两种类加载器加载的是同一个包里面的类,只要加载的时候不属于同一个类加载器,就是相互隔绝的,这样的操作称为JVM的“安全隔离”
  [2]步骤二:字节码校验:
  字节码校验过程需要经过四个步骤:
  • 检查class文件的内部结构是否正确,主要是检查该文件是否以某个内存地址打头一般为0xCAFEBABE,但是可能32bit和64bit存在一定的差异,通过这样的检查可以使得被损坏的class文件或者伪装的class文件不能够被加载,可以标记为不安全的。第一趟扫描的主要目的是保证这个字节序列正确的定义了一个类型,它必须遵从java class文件的固定格式,这样它才能被编译成在方法区中的(基于实现的)内部数据结构。
  • 检查是否符合JVM语言特性里面的编译规则,因为Java里面所有的Class都是从Object类继承过来的,一旦发现这种不安全的类存在,就直接抛异常。这次检查,class文件检验器不需要查看字节码,也不需要查看和装载任何其他类型。在这趟扫描中,检验器查看每个组成部分,确认它们是否是其所属类型的实例,它们结构是否正确。比如,方法描述符(它的返回类型,以及参数的类型和个数)在class文件中被存储为一个字符串,这个字符串必须符合特定的上下文无关文法。另外,还会检查这个类本身是否符合特定的条件,它们是由java编程语言规定的。比如,除Object外,所有类都必须要有一个超类,final的类不能被子类化,final方法也没有被覆盖,检查常量池中的条目是合法的,而且常量池的所有索引必须指向正确类型的常量池条目。
  • 检查字节码是否导致JVM崩溃掉,这里存在一个JVM中断的问题,编程过程无法捕捉Error,同样的没有办法判断程序是否因为执行的class字节码中断或者崩溃。字节码流代表了java的方法,它是由被称为操作码的单字节指令组成的序列,每一个操作码后都跟着一个或多个操作数。执行字节码时,依次执行操作码,这就在java虚拟机内构成了执行的线程,每一个线程被授予自己的java栈,这个栈是由不同的栈帧构成的,每一个方法调用将获得一个自己的栈帧——栈帧其实就是一个内存片段,其中存储着局部变量和计算的中间结果,用于存储中间结果的部分被称为操作数栈。字节码检验器要进行大量的检查,以确保采用任何路径在字节码流中都得到一个确定的操作码,确保操作数栈总是包含正确的数值以及正确的类型。它必须保证局部变量在赋予合适的值以前不能被访问,而且类的字段中必须总是被赋予正确类型的值,类的方法被调用时总是传递正确数值和类型的参数。字节码检验器还必须保证每个操作码都是合法的,即都有合法的操作数,以及对每一个操作码,合适类型的数值位于局部变量中或是在操作数栈中。这些仅仅是字节码检验器所做的大量检验工作中的一小部分,在整个检验过程通过后,它就能保证这个字节码流可以被java虚拟机安全的执行。
  • 检查符号引用验证,一个类文件,它会包含它引用的其他类的全名和描述符,并跟他们建立符号引用(一种虚拟的,非物理连接的方式)。当程序第一次执行到需要符号引用的位置时,JVM会检查这个符号链接的正确性,然后建立真正的物理引用(直接引用)。java虚拟机将追踪那些引用——从被验证的class文件到被引用的class文件,以确保这个引用是正确的。这次扫描可能要装载新的类。考虑到虚拟机实现上的差别,第四趟扫描可能紧随第三趟扫描发生,也有可能在第三趟扫描之后很久,当字节码被执行时才执行。动态连接是一个将符号引用解析为直接引用的过程。当java虚拟机执行字节码时,如果它遇到一个操作码,这个操作码第一次使用一个指向另一个类的符号引用,那么虚拟机就必须解析这个符号引用。在解析时,虚拟机执行两个基本任务:
    ——查找被引用的类(如果必要的话就装载它)
    ——将符号引用替换为直接引用,例如指向一个类、字段或方法的指针或偏移量
    虚拟机必须记住这个直接引用,这样当它以后再次遇到同样的引用时,就可以直接使用,而不需要重新解析该符号引用了。
  [3]步骤三:内置于JVM的安全特性:
  在执行期,除了符号引用验证,JVM还会对一些内建的安全特性进行检查
  • 类型安全的引用转化
  • 结构化的内存访问(非指针算法)
  • GC
  • 数组边界检查
  • 空引用检查(NullPointer)
  强制内存的结构话访问,避免了恶意用户在了解内存分布的情况下通过指针对JVM的内部结构进行破外。当然要了解JVM的内存分布也不是易事。JVM对运行时数据空间的分配是一个黑盒过程,完全由JVM自己决定如何分配,在class中没有任何相关的信息。

  字节码检查的局限就是对于那些不经过字节码检查的方法(如本地方法:native method)无法验证其安全性,所以这里采用的是对动态链接库的访问控制,对于那些足有足够可信度的代码才被允许访问本地方法。具体的实现是,由安全管理器来决定代码是否有调用动态链接库的权限,因为调用本地方法必须调用动态链接库。这样一来,不可信的代码将无法通过调用本地方法来绕过字节码检查。

  [4]步骤四:安全管理器SecurityManager:
  这一步是编程中最接近程序的一个环节,SecurityManager存在于JavaAPI里面,是可以通过编程来完成安全策略管理的,默认情况下,Java应用是不设置SecurityManager实例的,但是这个实例却需要我们在启动的时候通过System.setSecurityManager来进行设置。一般情况下,调用了SecurityManager.checkPermission(Permission perm)来完成的,外部程序可以创建一个权限实例Permission来进行Check操作。主要应用为:
  • 默认安全管理器:java.lang.SecurityManager
  • 代码签名和认证
  • 策略:java.security.Policy
  • 权限:java.security.Permission
  • 策略文件
  • 保护域:CodeSource,PersimissionCollection,ProtectionDomain
  • 访问控制器:java.security.AccessController
  【这一步内容比较多,这里不详细讲解,带过】
  [5]步骤五:Java签名/证书机制:
  Java签名机制使得使用者可以安全调用外部提供的jar文件,签名必须是基于jar包的,也就是如果要使用安全模型必须要将提供的class文件打包成jar,然后使用JDK工具(jarsigner)对jar进行签名。新安全平台中对足够信任度的代码放宽了限制,要获得充分信任须通过代码签名来实现,若我们对某一签名团体足够信任,比如SUN,那么具有该团体签名的代码将被给予充分信任。一个基本的思路大致为,代码发布者创建私钥/公钥对,然后利用私钥对发布代码进行签名,代码使用方在获得代码发布者提供的公钥后对代码进行验证,确认代码确为该提供者提供以及在发布后未经非法修改。这其中存在一些潜在的危险,既是公钥是否是该代码发布者提供的,恶意用户可能替换掉合法的公钥,这会导致用户将给与恶意用户发布的代码以充分信任,目前的常见做法是通过一些权威的证书机构来发布证书而不是公钥,代码发布者被证书发布机构认证合格后,可以将自己的公钥交付证书发布机构,证书发布机构再通过私钥加密该公钥,从而生成证书序列。这样替换公钥的可能性就变得微乎其微。不过世上无绝对安全之事,通过证书机构发布的证书也不是绝对安全的途径。

  证书是在签名基础上,对签名值,再进一步做一次加密。而这次加密使用的私钥和公钥都是证书机构提供的。这种方式,是为了防止,有些恶意用户,在公钥发到你手上前,就对其做了手脚,然后再发一个动过手脚的jar给你,用动过手脚的公钥解动过手脚的jar包,是可以解开的。而使用证书后,它会对已经加密的签名值,再做一层加密,这样,到你手里,你只需要通过证书机构的公钥进行解密,然后再用jar包发布者的公钥解密就行了。(只能在一定程度上,提供一些安全性)
  3)JVM的内部结构:
  JVM是Java平台的核心,为了让编译产生的字节码能够更好的解释和执行,JVM主要分为6个部分【这里只是带过,想要了解JVM整体执行原理的读者可以去参考《Inside JVM》】

 

  • JVM解释器:即这个虚拟机处理字段码的CPU。
  • JVM指令系统:该系统与计算机很相似,一条指令由操作码和操作数两部分组成。操作码为8位二进制数,主要是为了说明一条指令的功能,操作数可以根据需要而定,JVM有多达256种不同的操作指令。
  • 寄存器:JVM有自己的虚拟寄存器,这样就可以快速地与JVM的解释器进行数据交换。为了功能的需要,JVM设置了4个常用的32位寄存器:pc(程序计数器)、optop(操作数栈顶指针)、frame(当前执行环境指针)和vars(指向当前执行环境中第一个局部变量的指针)
  • JVM栈:指令执行时数据和信息存储的场所和控制中心,它提供给JVM解释器运算所需要的信息。
  • 存储区:JVM存储区用于存储编译过后的字节码等信息。
  • 碎片回收区:JVM碎片回收是指将使用过的Java类的具体实例从内存进行回收,这就使得开发人员免去了自己编程控制内存的麻烦和危险。随着JVM的不断升级,其碎片回收的技术和算法也更加合理。JVM 1.4.1版后产生了一种叫分代收集技术,简单来说就是利用对象在程序中生存的时间划分成代,以此为标准进行碎片回收。

你可能感兴趣的:(java,jvm,虚拟机)