JVM运行时数据区-程序计数器

运行时数据区分为:
        程序计数器(PC寄存器)、虚拟机栈、本地方法栈、方法区、堆区

线程共享区:方法区,堆区。
线程独立区:程序计数器(PC寄存器)、虚拟机栈、本地方法栈。

JVM线程:

1.一个JVM线程对应一个Runtime(运行时数据区)

2.JVM允许一个应用有多个线程并执行。

3.当一个Java线程准备好执行后,此时操作系统的本地线程也同时创建,Java线程执行终止后,本地线程也会回收。

4.当一个Java线程准备好执行后(初始化-> 1) 程序计数器 2)虚拟机栈 3)本地方法栈),此时操作系统的本地线程也同时创建并初始化,本地线程初始化成功后,就会调用Java线程中的run()方法

如果Java线程启动,发现未处理异常,Java线程终止,操作系统线程决定要不要回收取决于该线程是守护线程或普通线程。

程序计数器:(PC寄存器  Program Counter Register)

        寄存器存储指令相关现场信息,CPU只有把数据装载到寄存器中才能够运行。JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟。

PC寄存器作用:

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

PC寄存器介绍:
1.PC寄存器是一块很小的内存空间,也是运行速度最快的存储区域。没有GC。

2.在JVM规范中,每个线程都有独立的程序计数器,线程私有的,生命周期与线程生命周期保持一致。

3.程序计数器指令会在存储当前线程正在执行的Java方法的Jvm地址,如果是正在执行Native(本地)方法,则是未指定值(undefined)。

字节码解释器工作是通过改变程序计数器值来选取下一条需要执行的字节码指令。

唯一一个在Java虚拟机规范中没有规定任何Out Of Memory(内存溢出)情况的区域。

JVM指令集、类加载子系统介绍

Jvm整体架构图:

Jvm指令集架构:

指令集架构分为两种:
    1.基于栈的指令集架构(Java编译器是基于栈的指令集架构)。
    2.基于寄存器指令架构。

栈的指令架构:
    优势:跨平台、零地址指令、指令集更小、移植性高。(编译器更容易实现)
    劣势:指令多,性能下降(实现同样的功能需要更多指令)
寄存器指令架构:
    优势:性能优秀、执行效率高,指令少
    劣势:移植性差

Jvm生命周期:

跟随线程的结束或终止生命周期就结束(System.exit()方法或Runtime类的Halt方法)。

Jvm类加载子系统图:

 

类加载子系统作用:

类加载子系统负责从文件系统或网络中加载class文件,class文件在文件开头有特定文件标识(Ca Fe Ba Be)。ClassLoad只负责class文件的加载,是否可以运行由Execution Engine决定。

Jvm加载器:

1.启动类加载器(引导类加载器 Bootstrap ClassLoader):
    1) 使用C/C++语言实现,嵌套在Jvm内部。
    2) 用来加载Java核心类库。
    3) 加载扩展类和系统类,指定为它们的父类加载器2.扩展类加载器(Extension ClassLoader):
    1) Java语言编写。
    2) 派生于ClassLoader类。
    3) 从java.ext.dirs系统属性所指定目录中加载或JDK的安装目录jre/lib/ext子目录下加载。
    4) 如果用户创建的Jar放在jre/lib/ext子目录下,自动由扩展类加载器加载3.系统类加载器(App ClassLoader):
    1) Java语言编写。
    2) 派生于ClassLoader类。
    3) 父类为扩展类加载器。
    4) 负责加载环境变量classpath或系统属性java.class.path指定下的类库。
    5) Java应用类,自定义类都是由系统加载器完成加载自定义加载器的好处:
    1) 隔离加载类 2) 修改类的加载方式 3) 扩展加载源 4) 防止源码泄漏

如何自定义加载器:
    1) 通过继承抽象类java.lang.ClassLoader的方式,实现自己的类加载器。
    2) 如果没有复杂的需求,可以直接继承URLClassLoader类,可以避免编写findClass()方法及获取字节码的方式,使自定义加载器更加简洁。
/**
 * 自定义类加载器
 */
public class MyClassLoader extends ClassLoader{

 private String classPath;

 public MyClassLoader(String classPath){
 this.classPath = classPath;
 }

 @Override
 protected Class<?> findClass(String name) {
 byte[] classByte = loadClassByte(name);
 return defineClass(name, classByte, 0, classByte.length);
 }

 private byte[] loadClassByte(String name) {
 // 获取该类在文件系统中保存的格式 (即路径加文件名)
 String fileName = classPath + File.separator + name.replace(".",File.separator) +".class";

 File file = new File(fileName);
 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
 try(InputStream is = new FileInputStream(file)){

 int len;
 byte[] b = new byte[1024*8];
 while ((len = is.read(b)) != -1) {
 outputStream.write(b, 0, len);
 }
 } catch (FileNotFoundException e) {
 e.printStackTrace();
 } catch (IOException e) {
 e.printStackTrace();
 }
 return outputStream.toByteArray();
 }
}
public static void main(String[] args) throws Exception {
 //路径请使用classpath外的一个class文件,并且类路径下不能包含此class,才能使用到自定义的类加载器
 MyClassLoader myClassLoader = new MyClassLoader("F:\\chenxi");
 Class clazz = myClassLoader.loadClass("com.MyClassLoading");
 System.out.println(clazz + "-" + clazz.newInstance()+ "-" +clazz.getClassLoader());
}

类加载过程:

分为三个阶段:1.加载  2.链接  3.初始化

加载阶段: 
     1.通过一个类的全限定名获取定义此类的二进制字节流。
     2.将字节流所代表的静态存储结构化为方法区的运行时数据结构。
     3.在内存中生成一个代表这个类的java.lang.class对象作为一个方法区,这个类的各种数据访问入口。

     加载.class文件的方式:
         1.Jar,War格式的基础,从zip压缩包中读取
         2.动态代理技术,运行时计算生成。

链接阶段:
    1.验证:确保class文件中的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全(四种验证:1.文件格式 2.元数据 3.字节码 4.符号引用)。
            
     
    2.准备:为变量分配内存并设置该类变量的默认初始值(零值)(不包含用final修饰的static,因为final在编译时就分配了,准备阶段会显式初始化)。

    3.解析:将常量池内的符号引用转换为直接引用的过程,解析操作随着Jvm在执行空初始化后再执行(解析主要针对于:类、接口、字段、类方法、接口方法、方法类型等)。

初始化阶段:
    执行类构造器的方法<clinit>()的过程。此方法不需要定义,是Javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
    static int a = 1;
    static{
      a = 2;
    }
    构造器方法中指令按语句在源码文件中出现的顺序执行。
    <clinit>()不同于类的构造器。若该类具有父类,Jvm会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕。
    虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁。(保证一个类只加载一次)
    <init>对应类中的构造器。

    IDEA安装jclasslib插件:





双亲委派机制

工作原理:

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

    2) 如果父类的加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终达到顶层的引导类加载器(启动类加载器)。

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

优势:
    1) 避免重复加载 
    2) 保护程序安全,防止核心Api被随意篡改(沙箱安全机制)。