• jvm体系结构


    JVM 位置

    jvm体系结构图

    • 类装载器ClassLoader

      负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定

    • 类装载器ClassLoader分类

    虚拟机自带的加载器

    • 启动类加载器(Bootstrap)C++
    • 扩展类加载器(Extension)Java
    • 应用程序类加载器(AppClassLoader)Java 也叫系统类加载器,加载当前应用的classpath的所有类
    • 用户自定义加载器
      Java.lang.ClassLoader的子类,用户可以定制类的加载方式

    Execution Engine

    执行引擎负责解释命令,提交操作系统执行

    Native Interface 本地接口

    本地接口的作用是融合不同的编程语言为 Java 所用

    Native Method Stack

    它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。

    PC 寄存器

    • 每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。

    方法区

    • 方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区属于共享区间。静态变量+常量+类信息(构造方法/接口定义)+运行时常量池存在方法区中。

    栈区

    • 栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放, 对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。8种基本类型的变量+对象的引用变量+实例方法都是在函数的栈内存中分配。

    栈存储什么?

    • 本地变量(Local Variables):输入参数和输出参数以及方法内的变量;
    • 栈操作(Operand Stack):记录出栈、入栈的操作;

    图示在一个栈中有两个栈帧:

    • 栈帧 2是最先被调用的方法,先入栈,然后方法 2 又调用了方法1,栈帧 1处于栈顶的位置,
    • 栈帧 2 处于栈底,执行完毕后,依次弹出栈帧 1和栈帧 2,线程结束,栈释放。
    • 每执行一个方法都会产生一个栈帧,保存到栈( 后进先出) 的顶部,顶部栈就是当前的方法,该方法执行完毕 后会自动将此栈帧出栈。

    栈针内存储:

    • 局部变量表
    • 操作数栈
    • 指向运行时常量池的引用
    • 方法返回地址
    • 动态链接

    栈+ 堆+ 方法区的交互关系

    JAVA虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位、去找到对象所在堆中的具体位置,所以对象的方法方法是取决于虚拟机实现
    而定的。

    现在主流的方法方式有两种:使用句柄和直接指针

    • 通过句柄方式访问对象

    img

    • 使用句柄访问的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含啥了对象的实例数据和类型数据各自的具体地址信息
    • 使用句柄访问的好处是reference中储存的是稳定的对象的句柄地址,当对象被移动(垃圾回收时进行对象移动)时候,只需要更新句柄中的对象实例部分指针,reference本身不用被修改。

    对象实例数据和对象类型数据

    对象实例数据(堆):对象中各个实例字段的数据
    对象类型数据(方法区):对象的类型、父类、实现的接口、方法等
    静态区(也在方法区中)用来存放静态变量,静态块
    

    什么是句柄?

    句柄(Handle)来标示应用程序中不同的对象和同类中不同的实例。也可以理解为它是指向实例对象的指针的指针。

     String s;//句柄
     String s1=new String();句柄=实例对象
     s1 = "a";//通过句柄操作对象
    
    • 通过直接指针访问对象

    • 使用直接指针访问,那么Java堆中的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址
    • 使用直接指针访问的最大好处是 速度更快,它节省一次指针定位时间的开销(节省了句柄访问方法中的句柄池寻找实例池中的寻址时间),由于对象的访问在Java中非常频繁,因此这类开销积少成多时也是一项非常可观的成本。
    • 一般的虚拟机都是通过指针直接访问对象而通过句柄的这种方法并不是很常用。

    扩展:

    HotSpot 是使用指针的方式来访问对象:
    Java 堆中会存放访问类元数据的地址,reference 存储的就直接是对象的地址
    

    例子:

    a b c是三个整型变量,Student是学生类,变量表中存储着 基本数据类型和引用类型的reference,主要以Student类来说明三者调用关系 
    

    在这里插入图片描述

    每个线程都要有一个线程栈,栈内存放栈帧,内部为线程中的变量表,既然是在栈中都要按照先入后出的原则进行入栈和出栈。
    变量表中student指向堆中的student类实例的地址,同时指向方法区中Student类的类型数据(类型数据存储类信息),内部还有其他引用类型的数据,这里举例String类型的name变量,student实例内部存储name由于是引用类型,也会使用指针,在堆中开辟存储空间存储name的内容存储内容“xiaoming”,实质是String类型,在方法区中也有对应类型指向。
    

    Heap 堆(java8以前)

    一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、
    常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行,堆内存分为三部分:
    
    英文名称 中文名称 缩写
    Young Generation Space 新生区 Young/New
    Tenure generation space 养老区 Old/ Tenure
    Permanent Space 永久区 Perm

    Heap堆( Java8)

    堆内存 逻辑上 分为三部分:新生+ + 养老+ + 方法区

    方法区

    • 类信息
      • 类的版本
      • 字段
      • 方法
      • 接口
    • 静态变量
    • 常量
    • 类信息(构造方法/接口定义)
    • 运行时常量池
      永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。
      如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。或者大量动态反射生成的类不断被加载,最终导致Perm区被占满。
    

    版本差异:

    Jdk1.6及之前: 有永久代, 常量池1.6在方法区
    Jdk1.7: 有永久代,但已经逐步“去永久代”,常量池1.7在堆
    Jdk1.8及之后: 无永久代,常量池1.8在元空间
    

    方法区(Method Area),是各个线程共享的内存区域,它用于存储虚拟机加载的:
    虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。对于HotSpot虚拟机,很多开发者习惯将方法区称之为“永久代(ParmanentGen)” ,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于是一个接口interface)的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。
    常量池(Constant Pool)是方法区的一部分,Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,这部分内容将在类加载后进入方法区的运行时常量池中存放。

    方法区-运行时常量池

    它是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到常量池中

    Java7

    Java8

    扩展:

    • 双亲委派机制
      双亲委派模型的式作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完全这个加载请求时,子加载器才会尝试自己去加载。

    • 沙箱机制
      沙箱机制是由基于双亲委派机制上 采取的一种JVM的自我保护机制,假设你要写一个java.lang.String 的类,由于双亲委派机制的原理,此请求会先交给Bootstrap试图进行加载,但是Bootstrap在加载类时首先通过包和类名查找rt.jar中有没有该类,有则优先加载rt.jar包中的类,因此就保证了java的运行机制不会被破坏.

  • 相关阅读:
    【Hadoop】HDFS客户端开发示例
    【Hadoop】HDFS原理、元数据管理
    【Linux】pv vg lv, 加盘,扩容磁盘
    【Hadoop】伪分布式环境搭建、验证
    【Docker安全】关于Docker使用root与非root用户的场景中的容器与host中的执行用户的研究
    【监控】使用 Grafana、collectd 和 InfluxDB 打造现代监控系统
    【网络】再谈select, iocp, epoll,kqueue及各种I/O复用机制 && Reactor与Proactor的概念
    【Hadoop】用 Ganglia 监控hadoop集群
    【自动部署】服务器自动化操作 RunDeck
    springmvc不通过controller进行页面跳转
  • 原文地址:https://www.cnblogs.com/snail-gao/p/11750106.html
Copyright © 2020-2023  润新知