jvm

JVM运行时数据区的内存模型由五部分组成:

【1】方法区

【2】堆

【3】JAVA栈

【4】PC寄存器

【5】本地方法栈

 

常量池是属于类型信息的一部分,类型信息也就是每一个被转载的类型,这个类型反映到JVM内存模型中是对应存在于JVM内存模型的方法区中,也就是这个类型信息中的常量池概念是存在于在方法区中,而方法区是在JVM内存模型中的堆中由JVM来分配的。

 

 

javac 把源码整成字节码

java 时,首先把你.class文件装进内存里面,这是装载。

new 一个对象的时候,是个实例化的过程,要先初始化该初始化的数据,static块的,static的,非static的成员变量,调构造方法 等

对象实例化的同时就会分配存储空间,然后对成员变量进行初始化赋值,再执行构造函数。这个过程就是对象初始化。调用构造函数只是初始化的一部分

1          装载class

1.1         载入baseclass , 产生了此处说明的序列步骤(即: [装载class])的递归.

1.2         载入derivedclass

1.2.1    为类作用域变量分配存储空间,并赋默认值

1.2.2    调用<cinit>,这包括: a).声明类作用域变量并同时赋值的语句 b).包含在 static {…} 中的赋值语句以及其他任何合法语句.c).要说明的是: 以上赋值语句的右值也可以是函数调用,那是合法的.

2          为实例作用域变量分配存储空间,并赋默认值.

3          调用构造函数   注意: java规定:构造函数的第一个条语句必须是对super构造函数的调用,如果代码中不是这样,会由编译器自动为其偷偷加上.

3.1         调用super构造函数, 产生了此处说明的序列步骤(即: [调用构造函数])的递归.

3.2          调用<init>,这包括:a).声明实例作用域变量并同时赋值的语句 b).包含在 {…} 中的赋值语句以及其他任何合法语句.c).要说明的是: 以上赋值语句的右值也可以是函数调用,那是合法的.

 

java确保程序健壮性/内存完整性

1,没有通过使用强制转换指针类型或通过进行指针运输访问内存。Java在运行时强制执行严格的类型规则,根本无法可能导致内存冲突的方式直接管理内存。

2,自动垃圾回收。

3,数组边界检查。Java不允许数组操作超出边界(抛出异常),从而导致内存冲突

4,对对象引用的检查。NullPoint

编写平台独立的Java程序,2条原则:

1,不要依赖finalization/GC来达到程序的正确性

2,不要依赖线程的优先级来达到程序的正确性

 

根据JMM(java memory model)的设计,系统存在一个主内存(MainMemory)(方法区,堆区),Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory)(栈帧),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。

Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。

 

4种引用,依次是:强引用、软引用、弱引用、虚引用。

强引用即我们普通使用的申明方式,对于该引用,只有显示的赋值为null,gc才会视为回收对象。

软引用(SoftReference)是只有内存不足时,gc才会将它视为回收对象。

弱引用(WeakReference)是无论当前内存是否满足,gc都会去回收它。

虚引用(PahntomReference)实际是可以理解成“子虚乌有”的引用,目的仅仅是(必须)结合引用关联队列(ReferenceQueue),实现对对象引用关系的跟踪

 

 

 

 

 

符号引用:虚拟机未给变量分配内存空间,只是一个符号而已。

直接引用:虚拟机已经给变量分配了内存空间,有真实的指针指向

符号引用,顾名思义,就是一个符号,符号引用被使用的时候,才会解析这个符号。如果熟悉linux或unix系统的,可以把这个符号引用看作一个文件的软链接,当使用这个软连接的时候,才会真正解析它,展开它找到实际的文件

对于符号引用,在类加载层面上讨论比较多,源码级别只是一个形式上的讨论。

当一个类被加载时,该类所用到的别的类的符号引用都会保存在常量池,实际代码执行的时候,首次遇到某个别的类时,JVM会对常量池的该类的符号引用展开,转为直接引用,这样下次再遇到同样的类型时,JVM就不再解析,而直接使用这个已经被解析过的直接引用。

除了上述的类加载过程的符号引用说法,对于源码级别来说,就是依照引用的解析过程来区别代码中某些数据属于符号引用还是直接引用,

如,System.out.println(“test” + “abc”);//这里发生的效果相当于直接引用,

String s = “abc”;System.out.println(“test” + s);//这里的发生的效果相当于符号引用,即把s展开解析,也就相当于s是”abc”的一个符号链接,也就是说在编译的时候,class文件并没有直接展看s,而把这个s看作一个符号,在实际的代码执行时,才会展开这个s

JVM对于直接引用和符号引用的处理是有区别的,可以看到符号引用时,JVM将使用StringBuilder来完成字符串的添加,而直接引用时则直接使用String来完成;

直接引用永远比符号引用效率更快,但实际应用开发中不可能全用直接引用,要提高效能可以考虑按虚拟机的思维来编写你的程序。

public  class   Test   {

  public   void   printf()  {

     System.out.println(java.lang.Math.PI);

     java.util.Date   date   =  new   java.util.Date();

     java.text.DateFormat df = newjava.text.simpleDateFormat(“yyyy-MM-dd “);

     String   sDate   =  df.format(date);

     System.out.println(sDate);              

  }

}

符号引用是针对类和类中的方法和字段而言的,在加载类的时候被转换为直接引用,(应该不包括实例中的变量—not sure)。

符号引用应该包含所有的信息以便能唯一地定位到该类/接口/方法/字段

举例来说,这段代码中符号引用包括:

java.lang.System

java.lang.System.out

java.io.PrentStream

java.io.PrentStream.println(dboule)

java.lang.Math

java.lang.Math.PI

java.util.Date

java.util.Date.Date()

java.text.DateFormat

java.text.DateFormat.format(java.util.Date)

java.text.simpleDateFormat

java.text.simpleDateFormat.simpleDateFormat(java.lang.String)

java.lang.String

 

 

 

 

起到第一道安全保障作用的”双亲委派类加载模型”

双亲委派方式的类加载,指的是优先从顶层启动类加载器开始,自顶向下的方式加载类的模型(参见第一条类装载器体系结构)。

这种模型的好处是,底层的类装载器装载的类无法与顶层类装载器装载的类相互调用。

哪怕是同包下的类,只要他们不属于同一类装载器,都是相互隔绝的。这对一些有安全隐患的类起到了安全隔离的作用。使它不能冒充系统类来破坏程序正常运作。

此外,不同的类装载器,也有自己的类装载范围。比如启动类装载器,它只会装在jdk/lib目录下的包/类,因此,系统级的类是相对安全的。

 

class文件校验器,通过四趟扫描,保证了class文件正确

第一趟是,检查class文件的结构是否正确。比较典型的就是,检查class文件是否以魔数OxCAFEBABE打头。通过这趟检查,可以过滤掉大部分可能损坏的,或者压根就不是class的文件,来冒充装载。

第二趟是,检查它是否符合java语言特性里的编译规则。比如发现一个类的超类不是Object,就抛出异常。

第三趟是,检查字节码是否能被JVM安全的执行,而不会导致JVM崩溃。这里提到了一个停机的问题。内容是这样的,“即不可能写出一个程序,用它来判定作为其输入而读入的某个程序,是否会停机”。意思是,不可能写一个程序,让它告诉你,另外一个程序会不会中断或崩溃。

第四趟是,符号引用验证。一个类文件,它会包含它引用的其他类的全名和描述符,并跟他们建立符号引用(一种虚拟的,非物理连接的方式)。当程序第一次执行到需要符号引用的位置时,jvm会检查这个符号链接的正确性,然后建立真正的物理引用(直接引用)。

 

jvm类型安全特性

这些都是基础的java语言特性,他们降低了java程序出现内存混乱,崩溃的几率。

结构化内存访问(不使用指针,一定程度上让黑客无法篡改内存数据)

自动垃圾收集

数组边界检查

空引用检查

数据类型安全

Java api的安全管理器 securityManager

这是安全沙箱中,离我们程序员最接近的一环。

securityMananger,是一个api级别的,可自定义的安全策略管理器,它深入到javaapi中,在各处都可以见到它的身影。比如SecurityClassLoader。

默认情况下,java应用程序是不设置securityManager 实例的(意味着不会起到安全检查),这个实例需要我们在程序启动时通过 System.setSecurityManager 来设置。

一般情况下,检查权限是,通过SecurityManager.checkPermission(Permission perm) 来完成的。外部程序通过,创建Permission实例,传递给前面的check。

Permission是一个抽象类,需要继承它实现不同的权限验证,比如FilePermission,代表对某个文件的读写权限。

new FilePermission(”test.txt”, “read”)

你可以将这个实例,扔给 SecurityManager,检查是否可读text.txt这个文件。

 

 

 

JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader)。

 

JVM在运行时会产生三个ClassLoader:BootstrapClassLoader、 Extension ClassLoader和AppClassLoader.

Bootstrap是用C++编写的,我们在Java中看不到它,是null,是JVM自带的类装载器,用来装载核心类库,如java.lang.*等。

AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为Bootstrap ClassLoader。

 

Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。 System Class Loader是一个特殊的用户自定义类装载器,由JVM的实现者提供,在编程者不特别指定装载器的情况下默认装载用户类。系统类装载器可以通过ClassLoader.getSystemClassLoader() 方法得到。

 

1:BootStrapClassLoader  :加载java运行过程中的核心类库JRE\lib\rt.jar, sunrsasign.jar, charsets.jar,jce.jar, jsse.jar, plugin.jar 以及存放在JRE\classes里的类

2:ExtClassLoader        :加载JRE\lib\ext 或者目录下的库文件目录下的类(包括任何子目录,有别于其他两个ClassLoader)

3:AppClassLoader        :加载CLASSPATH变量指定路径下的类

 

A、其中AppClassLoader的parent(并非父类)为ExtClassLoader, ExtClassLoader的parent为BootStrapClassLoader,根据ClassLoader的委托模型,当要加载一个 class时候会先让其parent来加载,如果parent找不到的话,再由自己来加载。

B、加载的第一个类的ClassLoader如果为BootStrapClassLoader的话,那么其他的所有类也必须又 BootStrapClassLoader来加载,如果加载第一个类的ClassLoader为AppClassLoader的话,则类的加载按规则A来执行。

 

BootStrapClassLoader的默认Load路径 :System.getProperty(“sun.boot.class.path”) ExtClassLoader的默认Load路径 : System.getProperty(“java.ext.dirs”)

AppClassLoader的默认Load路径: System.getProperty(“java.class.path”)

 

其中ExtClassLoader,AppClassLoader的load可以通过命令:

java -Djava.ext.dirs xxx xxx,java -Djava.class.pathxxx xxx来改变

而BootStrapClassLoader的路径是用C++写死在JVM里面的,

即使通过java -Dsun.boot.class.pathxxx xxx来改变路径也不起任何作用。

 

 

 

 

 

 

 

 

public   class   LoaderSample1 {

     public   static  void  main(String[] args) {

        Class c;

        ClassLoader cl;

        cl  = ClassLoader.getSystemClassLoader();

       System.out.println(cl);

         while  (cl !=   null ) {

            cl  = cl.getParent();

           System.out.println(cl);

        }

         try  {

            c  = Class.forName( ” java.lang.Object ” );

            cl  = c.getClassLoader();

           System.out.println( ” java.lang.Object’s loader is  ”  +  cl);

            c  = Class.forName( ” LoaderSample1 ” );

            cl  = c.getClassLoader();

           System.out.println( ” LoaderSample1’s loader is  ”  +  cl);

        }  catch (Exception e) {

           e.printStackTrace();

        }

    }

}

在我的机器上(Sun Java 1.4.2)的运行结果

sun.misc.Launcher$AppClassLoader@1a0c10f

sun.misc.Launcher$ExtClassLoader@e2eec8

null

java.lang.Object’s loader is null

LoaderSample1’s loader issun.misc.Launcher$AppClassLoader@1a0c10f

第一行表示,系统类装载器实例化自类sun.misc.Launcher$AppClassLoader

第二行表示,系统类装载器的parent实例化自类sun.misc.Launcher$ExtClassLoader

第三行表示,系统类装载器parent的parent为bootstrap

第四行表示,核心类java.lang.Object是由bootstrap装载的

第五行表示,用户类LoaderSample1是由系统类装载器装载的

 

 

 

 

 

 

 

 

 

 

 

 

parent delegation模型

从1.2版本开始,Java引入了双亲委托模型,从而更好的保证Java平台的安全。在此模型下,当一个装载器被请求装载某个类时,它首先委托自己的parent去装载,若parent能装载,则返回这个类所对应的Class对象,若parent不能装载,则由parent的请求者去装载。

 

loader2的parent为loader1,loader1的parent为system class loader。假设loader2被要求装载类MyClass,在parent delegation模型下,loader2首先请求loader1代为装载,loader1再请求系统类装载器去装载MyClass。若系统装载器能成功装载,则将MyClass所对应的Class对象的reference返回给loader1,loader1再将reference返回给loader2,从而成功将类MyClass装载进虚拟机。若系统类装载器不能装载MyClass,loader1会尝试装载MyClass,若loader1也不能成功装载,loader2会尝试装载。若所有的parent及loader2本身都不能装载,则装载失败。

 

若有一个能成功装载,实际装载的类装载器被称为定义类装载器,所有能成功返回Class对象的装载器(包括定义类装载器)被称为初始类装载器。如图1所示,假设 loader1实际装载了MyClass,则loader1为MyClass的定义类装载器,loader2和loader1为MyClass的初始类装载器。

 

需要指出的是,Class Loader是对象,它的父子关系和类的父子关系没有任何关系。

 

那么parent delegation模型为什么更安全了?因为在此模型下用户自定义的类装载器不可能装载应该由父亲装载器装载的可靠类,从而防止不可靠甚至恶意的代码代替由父亲装载器装载的可靠代码。实际上,类装载器的编写者可以自由选择不用把请求委托给parent,但正如上所说,会带来安全的问题。

 

命名空间及其作用

每个类装载器有自己的命名空间,命名空间由所有以此装载器为创始类装载器的类组成。不同命名空间的两个类是不可见的,但只要得到类所对应的Class对象的reference,还是可以访问另一命名空间的类。

虚拟机会为每一个类装载器维护一张列表,列表中是已经被请求过的类型的名字。这些列表包含了每一个类装载器被标记为初始类装载器的类型,它们代表了每一个类装载器的命名空间。虚拟机总是会在调用loadClass()之前检查这个内部列表,如果这个类装载器已经被标记为是这个具有该全限定名的类型的初始类装载器,就会返回表示这个类型的Class实例,这样,虚拟机永远不会自动在同一个用户自定义类装载器上调用同一个名字的类型两次。

命名空间的类型共享

前面提到过只有同一个命名空间内的类才可以直接进行交互,但是我们经常在由用户自定义类装载器定义的类型中直接使用JAVA API类,这不是矛盾了吗?这是类型共享 原因-如果某个类装载器把类型装载的任务委派给另外一个类装载器,而后者定义了这个类型,那么被委派的类装载器装载的这个类型,在所有被标记为该类型的初始类装载器的命名空间中共享。

运行时包(runtime package)

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

每个类装载器都有自己的命名空间,其中维护着由它装载的类型。所以一个JAVA程序可以多次装载具有同一个全限定名的多个类型。这样一个类型的全限定名就不足以确定在一个JAVA虚拟机中的唯一性。因此,当多个类装载器都装载了同名的类型时,为了唯一表示该类型,还要在类型名称前加上装载该类型的类装载器来表示-[classloaderclass]。

PC 是 16 位程序计数器(Program Counter ),它不属于特殊功能寄存器范畴,程序员不以像访问特殊功能寄存器那样来访问 PC。 PC 是专门用于在 CPU 取指令期间寻址程序存储器。 PC 总是保存着下一条要执行的指令的 16 位地址。

任何一台计算机的指令系统一般都包含有几十条到上百条指令,下面按一般计算机的功能把指令划分以下几种类型.

(1)算术运算指令

计算机指令系统一般都设有二进制数加\减\比较和求补等最基本的指令,此外还设置了乘\除法运算指令\浮点运算指令以有十进制动算指令等.

(2)逻辑运算指令

一般计算机都具有与\或\非(求反)\异或(按位加)和测试等逻辑运算指令.

(3)数据传送指令.

这是一种常用的指令,用以实现寄存器与寄存器,寄存器与存储单元以及存储器单元与存储器单元之间的数据传送,对于存储器来说,数据传送包括对数据的读(相当于取数指令)和写(相当于存数指令)操作.

(4)移位操作指令

移位操作指令分为算术移位\逻辑移位和循环移位三种,可以实现对操作数左移或右移一位或若干位.

(5)堆栈及堆栈操作指令.

堆栈是由若干个连续存储单元组成的先进后出(FILO)存储区,第一个送入堆栈中的数据存放在栈底,最后送入堆栈中的数据存放在栈顶.栈底是固定不变的,而栈顶却是随着数据的入栈和出栈在不断变化.

(6)字符串处理指令.

字符串处理指令就是一种非数值处理指令,一般包括字符串传送,字符串转换(把一种编码的字符串转换成另一种编码的字符串),字符串比较,字符串查找(查找字符串中某一子串),字符串匹配,字符串的抽取(提取某一子串)和替换(把某一字符串用另一字符串替换)等.

(7)输入输出(I/O)指令.

计算机本身公是数据处理和管理机构,不能产生原始数把,也不能长期保存数据.所处理的一切原始数据均来自输入设备,所得的处理结果必须通过外总设备输出.

(8)其它指令.

特权指令—-具有特殊权限的指令,在多服务用户\多任务的计算机系统中,特权指令是不可少的.

陷阱与陷阱指令—陷阱实际上是一种意外事故中断,中断的目的不是为请求CPU的正常处理,面是为了通知CPU所出现的故障,并根据故障情况,转入相就的故障处理程序.

转移指令—用来控制程序的执行方向,实现程序的分支.

子程序调用指令—在骗写程序过程中,常常需要编写一些经常使用的\能够独立完成的某一特定功能的程序段,在需要时能随时调用,而不必重复编写,以便节省存储空间和简化程序设计.

上一篇:
下一篇:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

昵称 *