其他分享
首页 > 其他分享> > 类的加载、本地计数器、虚拟机栈、本地方法栈篇

类的加载、本地计数器、虚拟机栈、本地方法栈篇

作者:互联网

Java虚拟机:就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对应平台上的机器指令执行。每一条Java指令,java虚拟机规范中都有详细定义。

特点:一次编译,到处运行;自动内存管理;自动垃圾回收功能。

JVM的整体结构

 

 

Java代码执行流程

 

 

 

 

JVM的生命周期

1.虚拟机的启动:通过引导类加载器(bootStrap class loader)创建一个初始类来完成的,这个类是由虚拟机的具体实现指定的。

2.虚拟机的执行:》一个运行的java虚拟机有着一个清晰的任务:执行JAVA程序。

        》程序开始执行时他才运行,程序结束时他就结束

        》执行一个所谓的Java程序的时候,真真正正在执行的是一个叫做Java虚拟机的          进程。

3.虚拟机的退出:》程序正常执行结束。》程序在执行过程中遇到了异常或错误而异常终止。》由于操作系统出现错误而导致JAVA虚拟机进程终止。》某线程调用Runtime类或System类的exit方法,或Runtime类的halt方法,并且java安全管理器也允许这次exit或halt操作。

 

 

JVM-HotSpot

HotSpot指的就是它的热点代码探测技术。

  >通过计数器找到最具编译价值代码,触发即使编译或栈上替换

  》通过编译器与解释器协同工作,在最优化的程序响应时间与最佳执行性能中取得平衡。

 

 类的加载过程:

  加载:

    》通过一个类的全限定名获取定义此类的二进制字节流

    》将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

    》在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的    访问入口

   链接:

    》验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。

    》准备:为类变量分配内存并且设置该类变量的默认初始值,即零值。

        这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化;这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到java堆中。

    》解析:将常量池内的符号引用转换为直接引用的过程。事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行。

 

 

类变量和静态代码块的执行顺序:按代码顺序先后执行。

静态代码块》构造代码块》构造函数》普通代码块。

 

   初始化:》初始化阶段就是执行类构造器方法<clinit>()的过程。》此方法无需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。》若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕。虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁。

  小tips:1.类变量和静态代码块在JVM加载的时候就已经初始化了。2.父类的静态代码块》子类的静态代码块。3.类加载只会加载一次,无论几个线程。

 

 

类加载器的分类

JVM支持两种类型的类加载器,分别为引导类加载器(BootStrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。从概念上讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是JAVA虚拟机规范没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。

 

 

ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        ClassLoader bootstrapClassLoader = systemClassLoader.getParent();
        System.out.println(bootstrapClassLoader);//sun.misc.Launcher$ExtClassLoader@7f31245a

        ClassLoader bootstrapClassLoaderParent = bootstrapClassLoader.getParent();
        System.out.println(bootstrapClassLoaderParent);//null
        
        ClassLoader classLoader = Test.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);//null

 

小tips:从这里就可以看出来,他们的关系不是继承关系,类似于一种上下级关系。并且java的一些核心类是由引导类加载器进行加载的,而我们自定义的类是由扩展类加载器加载。

 

 

启动类加载器(引导类加载器,Bootstrap ClassLoader)

  》这个类加载使用C/C++语言实现的,嵌套在JVM内部

  》他用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身所需要的类。

  》并不继承自java.lang.ClassLoader,没有父加载器

  》处于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类。

 

扩展类加载器(Extension ClassLoader)

  》java语言编写,有sun.misc.Lacuncher$ExtClassLoader实现。

  》派生于ClassLoader类

  》父类加载器为启动类加载器

  》从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。

 

应用程序类加载器(系统类加载器,AppClassLoader)

  》java语言编写,由sun.misc.Launcher$AppClassLoader实现

  》派生于ClassLoader类

  》父类加载器为扩展类加载器

  》他负责加载环境变量classpath或系统属性java.class.path指定路径下的类库

  》该类加载是程序中默认的类加载器,一般来说,java应用的类都是由它来完成加载

 

 

双亲委派机制

  概念:Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,java虚拟机才用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。

 

  工作原理:

    》如果一个类加载器收到了类加载请求,他并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。

    》如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;

    》如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载。

 

 

优势:1.避免类的重复加载;2.保护程序安全,防止核心API被随意篡改。

 

沙箱安全机制:

  自定义String类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java\lang\String.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的String类。这样可以保证java核心源代码的保护。

 

 

 

 

 

 

PC寄存器

 

 

作用:PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。

为什么使用PC寄存器记录当前线程的执行地址呢?

因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行。JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令。

 

虚拟机的栈

定义:早期也叫JAVA栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧,对应着一次次的Java方法调用。

生命周期:生命周期和线程一致。

作用:主要管Java程序的运行,它保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回

优点:栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。JVM直接对java栈的操作只有两个,》每个方法执行,伴随着进栈》执行结束后的出栈工作。对于栈来说不存在垃圾回收问题。

栈是运行时的单位,而堆是存储的单位。即:栈是解决程序的运行问题,即程序如何执行,或者说如何处理数据,堆解决的是数据存储的问题,即数据怎么放、放在哪里。

 

 栈中可能出现的问题:

  Java虚拟机规范允许Java栈的大小是动态的或者是固定不变的。

  如果采用固定大小的java虚拟机栈,线程请求分配的栈容量超过java虚拟机栈允许的最大容量,java虚拟机将会抛出一个StackOverflowError异常。

  如果Java虚拟机栈可以动态扩展,在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个OutOfMemoryError异常。

 

栈帧的内部结构

  局部变量表

  操作数栈(或表达式栈)

  动态链接(或指向运行时常量池的方法引用)

  方法返回地址(或方法正常退出或者异常退出的定义)

  一些附加信息

 

 局部变量表:

  局部变量表也被称为之局部变量数组或本地变量表

  定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些类型包括各类基本数据类型、对象引用,以及returnAddress类型。

  由于局部变量表示建立在线程的栈上,示线程的私有数据,因此不存在数据安全问题。

  局部变量表所需的容量大小实在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。

  如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排列。

 

操作数栈

  主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。

  操作数栈就是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的。

  操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈和出栈操作来完成一次数据访问。

 

什么是本地方法?

  一个Native Method就是一个Java调用非java代码的接口。一个Native Method是这样一个java方法;该方法的实现由非java语言实现,比如C。

 

本地方法栈?

  Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。

  本地方法栈,也是线程私有的。

  允许被实现称固定或者可动态扩展的内存大小。(在内存溢出方面是相同的)

标签:Java,虚拟机,ClassLoader,本地,java,方法,栈篇,加载
来源: https://www.cnblogs.com/wxynb/p/16296295.html