zoukankan      html  css  js  c++  java
  • 十七、java内存模型_JVM_JDK_类加载器及过程

    1、Java内存模型
      共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
    按照官方的说法:Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。
     
    从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
    1)首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
    2)然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
    下面通过示意图来说明这两个步骤:
     
    如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。
    从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。
    总结:什么是Java内存模型:java内存模型简称jmm,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。
    2、Java内存模型包含的内容
    Java内存模型定义了一种多线程访问Java内存的规范。java内存模型的几部分内容:
    1)Java内存模型将内存分为了 主内存和工作内存 。类的状态,也就是类之间共享的变量,是存储在主内存中的,每次Java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在自己的工作内存中有一份拷贝,运行自己线程代码的时候,用到这些变量,操作的都是自己工作内存中的那一份。在线程代码执行完毕之后,会将最新的值更新到主内存中去。
    2)定义了几个原子操作,用于操作主内存和工作内存中的变量。
    3)定义了volatile变量的使用规则。
    4)happens-before,即先行发生原则,定义了操作A必然先行发生于操作B的一些规则,比如在同一个线程内控制流前面的代码一定先行发生于控制流后面的代码、一个释放锁unlock的动作一定先行发生于后面对于同一个锁进行锁定lock的动作等等,只要符合这些规则,则不需要额外做同步措施,如果某段代码不符合所有的happens-before规则,则这段代码一定是线程非安全的。
    3、JVM内存管理
    JVM主要管理两种内存:堆和非堆
    1)堆内存(Heap Memory)是在 Java 虚拟机启动时创建
      >堆是Java代码可及的内存,留给开发人员使用的
      >存放java对象
    2)非堆内存(Non-heap Memory)是在JVM堆之外的内存。
      >非堆是JVM留给自己用的,包含方法区、JVM内部处理或优化所需的内存(如 JITCompiler,Just-in-time Compiler,即时编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码。
      >存放类加载信息和其它meta-data
    3)其他内存
      >存放JVM 自身代码等
      在JVM启动时,就已经保留了固定的内存空间给Heap内存,这部分内存并不一定都会被JVM使用,但是可以确定的是这部分保留的内存不会被其他进程使用,这部分内存大小由-Xmx 参数指定。而另一部分内存在JVM启动时就分配给JVM,作为JVM的初始Heap内存使用,这部分内存是由 -Xms 参数指定。
    4、CAS 理解
      CAS,全称为Compare and Set,即比较-设置。假设有三个操作数: 内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false 。当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功。
    5、JRE与JVM、JDK的区别:
    1)JVM就是我们常说的java虚拟机,它是整个java实现跨平台的 最核心的部分,所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行,也就是说class并不直接与机器的操作系统相对应,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行。JVM 的主要工作是解释自己的指令集(即字节码)到 CPU 的指令集或 OS 的系统调用,保护用户免被恶意程序骚扰。JVM 对上层的 Java 源文件是不关心的,它关注的只是由源文件生成的类文件( class file )。类文件的 组成包括 JVM 指令集,符号表以及一些补助信息。
    2)JRE java runtime environment
     JRE是指java运行环境。光有JVM还不能成class的 执行,因为在解释class的时候JVM需要调用解释所需要的类库lib。 在JDK的安装目 录里你可以找到jre目录,里面有两个文件夹bin和lib,在 这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib和起来就称为jre。所以,在你写完java程序编译成.class之后,你可以把这个.class文件 和jre一起打包发给朋友,这样你的朋友就 可以运行你写程了。(jre里有运行.class的java.exe)JRE里面有一个 JVM , JRE 与具体的 CPU 结构和操作系统有关,我们从 Sun 下载 JRE 的时候就看到了不同的各种版本,同 JVM 一起组成 JRE 的还有 一些 API (如 awt , swing 等), JRE 是 运行 Java 程序必不可少的。
    3)JDK -- java development kit
      JDK是java开发工具包,基本上每个学java的人都会先在机器 上装一个JDK,在JDK的安装目录下面 六个文件夹、一个src类库源码压缩包、和其他几个声明文件。其中,真正在运行java时起作用的 是以下四个文件夹:bin、include、lib、 jre。现在我们可以看出这样一个关系,JDK包含JRE,而JRE包 含JVM。
      bin:最主要的是编译器(javac.exe)
      include:java和JVM交互用的头文件
      lib:类库
      jre:java运行环境
    (注意:这里的bin、lib文件夹和jre里的bin、lib是 不同的)总的来说JDK是用于java程序的开发,而jre则是只能运行class而没有编译的功能。eclipse、idea等 其他IDE有自己的编译器而不是用JDK bin目录中自带的,所以在安装时你会发现他们只要求你 选中jre路径就ok了
    三者之间的关系:Java 程序的字节码文件可以放到任意装有 JRE 的计算机运行,再由不同 JRE 的将它们转化成相应的机器代码,这就实现了 Java 程序的可移植性。
    6、JVM类加载原理
    6.1 JVM在运行时会产生三个ClassLoader:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader.
    1)Bootstrap是用C++编写的,我们在Java中看不到它,是null,它用来加载核心类库。
      关于Bootstrap ClassLoader,在JVM源代码中这样写道:
      static const char classpathFormat[] =
      "%/lib/rt.jar: "
      "%/lib/i18n.jar: "
      "%/lib/sunrsasign.jar: "
      "%/lib/jsse.jar: "
      "%/lib/jce.jar: "
      "%/lib/charsets.jar: "
      "%/classes ";
      为什么不需要在classpath中加载这些类了?是因为在JVM启动的时候就自动加载了,并且在运行过程中根本不能修改Bootstrap加载路径。
    2)Extension ClassLoader用来加载扩展类,即/lib/ext中的类
    3)AppClassLoader才是加载Classpath的
      ClassLoader加载类用的是委托模型。即先让Parent类(而不是Super,不是继承关系)寻找,Parent找不到才自己找。看来ClassLoader还是蛮孝顺的。三者的关系为:AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为Bootstrap ClassLoader。加载一个类时,首先BootStrap先进行寻找,找不到再由ExtClassLoader寻找,最后才是AppClassLoader。

    问题:为什么要使用双亲委托这种模型?
      a. 这样可以避免重复加载,当父亲已经加载了该类的时候,子类就没有必要,也不应该再加载一次。
           b. 核心类通过Java自带的加载器加载,可以确保这些类的字节码没有被篡改,保证代码的安全性。
           JVM在判定两个Class对象是否相同时,不仅要满足两个类名相同,而且要满足由同一个类加载器加载。只有两者同时满足的情况下,JVM才认为这两个Class对象是相同的。

    6.2 类加载的过程:装载--链接(验证、准备、解析)--初始化--使用--卸载

      1)类装载(Load):即查找和导入class文件
                    a、通过一个类的全限定名获取定义此类的二进制字节流
                    b、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
                    c、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。说白了就是类文件被类装载器装载进来之后,类中的内容(比如变量,常量,方法,对象等这些数据得要有个去处,也就是要存储起来,存储的位置肯定是在JVM中有对应的空间)
                    备注1:Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
                    备注2:方法区:类信息、常量、静态变量、即是编译器编译后的代码; 堆:代表某个类的class对象
           2)链接
                    a、验证(Verify):保证被加载类的正确性(文件格式验证、元数据验证、字节码验证、符号引用验证)
                    b、准备(Prepare):为类的静态变量分配内存,并将其初始化为默认值
                         示列:private static int age=10; 准备阶段完成之后age的值为0
                    c、解析(Resolve): 把类中的符号引用转换为直接引用,符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
                        解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。
                       
           3) 初始化(Initialize):对类的静态变量,静态代码块执行初始化操作
            
           4) 使用
                
           5) 卸载
          加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序进行,而解析阶段则不一定,它在某些情况下可能在初始化阶段后在开始,因为java支持运行时绑定。
        备注:加载(loading)阶段,java虚拟机规范中没有进行约束。但初始化阶段,java虚拟机严格规定了有且只有如下5种情况必须立即进行初始化(初始化前,必须经过加载、验证、准备阶段)
           1)使用new实例化对象时,读取和设置类的静态变量、静态非字面值常量(静态字面值常量除外)时,调用静态方法时。
           2)对内进行反射调用时
           3)当初始化一个类时,如果父类没有进行初始化,需要先初始化父类
           4)启动程序所使用的main方法所在类
           5)当使用1.7的动态语音支持时
           如上5种场景又被称为主动引用,除此之外的引用称为被动引用,被动引用有如下3种常见情况通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。定义对象数组和集合,不会触发该类的初始化类A引用类B的static final常量不会导致类B初始化(注意静态常量必须是字面值常量,否则还是会触发B的初始化).

    细水长流,打磨濡染,渐趋极致,才是一个人最好的状态。
  • 相关阅读:
    linux下安装vsftp
    CentOS 7.0编译安装Nginx1.6.0+MySQL5.6.19+PHP5.5.14
    Centos编译安装PHP 5.5笔记
    centos使用更新更快的yum源
    使用Android Studio搭建Android集成开发环境
    设计模式(三)原型模式
    overridePendingTransition
    Android手势密码实现
    UserInfoActivity用户图像修改和退出登录
    设计模式(二)建造者模式
  • 原文地址:https://www.cnblogs.com/jiarui-zjb/p/9623271.html
Copyright © 2011-2022 走看看