原创

JVM学习 JVM模型一

所有我们学过的知识,是用来推导未知的...


1、为什么学习JVM


  • 性能调优

  • 对debug的处理有帮助

  • 提高性能

  • 内存管理变的傻瓜式,当出问题的时候就束手无策了,所以需要了解


2、platform



  • Java里面最小的执行单位: 线程

  • 线程只负责执行,但不存储。存储的数据均在堆栈里面,通过程序计数器得到数据。
    image.png


3、JVM模型


3.1 JVM运行时数据区



即为JVM运行时的数据区




  • 程序计数器(Program Counter Register)

  • 虚拟机栈(Java Virtual Machine Stacks)

  • 本地方法栈(Native Method Stack)

  • Java堆(Java Heap)

  • ☆方法区(Method Area)
    665375-20160126212928129-1855187537.png
    JVM的内存由堆内存 + 方法区内存 + 剩余内存,也就是剩余内存=操作系统分配给JVM的内存 - 堆内存 - 方法区内存。


3.1.1 程序计数器(Program Counter Register)



学习地址:
https://www.cnblogs.com/yulinfeng/p/7153391.html




  • 指向当前线程正在执行的字节码指令的地址(行号)

  • 每个线程都有一个程序计数器,线程私有。

  • 程序计数器,这是JVM规范中唯一一个没有规定会导致OutOfMemory(内存泄露,下文简称OOM)的区域。换句话上图中的其余4个区域,都有可能导致OOM。


听说程序计数器还可以为空?



  • Java在执行Native方法时,不是Java在工作,而是调用了操作系统中的方法,所以此时不需要Java的程序计数器,此时为空。


既然程序计数器此时为空,Native方法执行完了以后,该回到哪里呢?



  • Java每个方法执行的时候,都会在Java虚拟机栈创建一个栈帧。一个方法对应一个栈帧。Java控制不了寄存器,但是可以用内部堆栈来保存这个上下文,当方法执行完后,恢复局部变量表、操作数栈和程序计数器(pc指针),让PC指向方法调用指令后面的一条指令。


Native方法也是方法,当他被调用了后,会进入本地方法栈。


3.1.2 虚拟机栈(Java Virtual Machine Stacks)FILO





    1. 概念 stack
      image.png




  • JVM里面通称的栈。 线程私有。一个虚拟机栈对应一个线程。




  • 设置JVM参数”-Xss228k”(栈大小为228k)。




  • -Xss设置的是每个线程的栈容量,也就是说可以创建的线程数量 = 剩余内存 / 栈内存。此时如果栈内存越大,可以创建的线程数量就少,就容易出现OOM;如果栈内存越小,可以创建的线程数量就多,就不容易出现OOM。




  • first in , last out。一个方法调用时一个栈帧。



    • 每个方法的执行和结束对应着栈帧的入栈和出栈。

    • 潜逃方法出现时, 最初的方法在栈底,最后的方法在栈顶。

    • 入栈表示被调用,出栈表示执行完毕或者返回异常。




  • 每个方法在执行的同时都会开辟一段内存区域用于存放方法运行时所需的数据,成为栈帧。(一个方法调用时一个栈帧)





    1. 栈帧



    • 一个栈帧包含如:


      • 局部变量表:(编译期已知的各种定义的基本数据类型、引用类型和指向一条字节码指令的returnAddress类型))



        • 每个局部变量空间(最小单位Slot)有32位,所以long和double类型的数据会占用两个局部变量空间,其他类型包括对象引用占用一个。(虚拟机没有明确指明一个Slot的内存空间大小。但是boolean、byte、char、short、int、float、reference、returnAddress类型的数据都可以用32位空间或更小的内存来存放。这些类型占用一个Slot。Java中的long和double类型是64位,占用两个Slot。(只有double和long是jvms里明确规定的64位数据类型))

        • 在一个实例方法的调用时,局部变量表的第0位是一个指向当前对象的引用,也就是Java里的this。

        • 局部变量表所需的内存空间。在方法运行的阶段是不会改变局部变量表的大小的。

        • 局部变量表所需的内存在编译期间完成分配(代码已知), 运行期间不会更改。

        • 不存储全局变量

        • code
          List list = null; //把变量对应的局部变量表清空,打断了GC Roots与局部变量表之间的关联。
          此时GC Root无法达到xxx对象,满足回收条件。


          • Full GC回收条件:局部变量表里的各种数据与GC Root没有关联。






      • 操作数栈:



        • 每个栈帧都包含一个被叫做操作数栈的后进先出的栈,叫操作栈,或者操作数栈。

        • 存储方法内一些进行了运算操作后的结果。

        • 操作数栈有什么用?

          • 栈桢刚创建时,里面的操作数栈是空的。

          • Java虚拟机提供指令来让操作数栈对一些数据进行入栈操作,比如可以把局部变量表里的数据、实例的字段等数据入栈。同时也有指令来支持出栈操作。

          • 向其他方法传参的参数,也存在操作数栈中。

          • 其他方法返回的结果,返回时存在操作数栈中。






      • 动态链接: Dynamic Linking. 在方法内调用接口,通过字面量链接到具体的实现类,实现Java的动态特性




      • 方法出口: return或者发生Exception等








  • 意义 :JVM是基于栈的,所以每个方法从调用到执行结束,就对应着一个栈帧在虚拟机栈中入栈和出栈的整个过程。




  • 可能出现的异常



    • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。

    • 如果在动态扩展内存的时候无法申请到足够的内存,就会抛出OutOfMemoryError异常。




  • 通过javap命令分析java汇编指令



    • 找到class文件,目录 /data/target/hello.class

    • 运行命令: javap -v /data/target/hello.class > 0228.txt




3.1.3 本地方法栈(Native Method Stack)


java中的jni调用。





    1. 本地方法栈和虚拟机栈相似,区别就是虚拟机为虚拟机栈执行Java服务(字节码服务),而本地方法栈为虚拟机使用到的Native方法服务。本地方法栈中使用的语言,使用方式,数据结构没有强制要求。





    1. 场景分析:当一个线程调用一个本地方法时,本地方法又回调虚拟机中的另一个Java方法。 见下图。

      990532-20160827203431726-2050515871.png




该线程首先调用了两个Java方法,而第二个Java方法又调用了一个本地方法,这样导致虚拟机使用了一个本地方法栈。假设这是一个C语言栈,其间有两个C函数,第一个C函数被第二个Java方法当做本地方法调用,而这个C函数又调用了第二个C函数。之后第二个C函数又通过本地方法接口回调了一个Java方法(第三个Java方法),最终这个Java方法又调用了一个Java方法(它成为图中的当前方法)。
“一个线程可能在整个生命周期中都执行Java方法,操作它的Java栈;或者它可能毫无障碍地在Java栈和本地方法栈之间跳转。”


3.1.4 Java堆(Java Heap)



  • 栈指向堆。

  • 线程共享。

  • 所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。

  • 堆中不存放基本类型和对象引用,只存放对象本身。

  • JAVA堆内存的划分
    0515871.png

    • JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation),非堆内存就一个永久代(Permanent Generation)。

    • 年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。

    • 堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收

    • 非堆内存用途:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。



正文到此结束
广告是为了更好的提供数据服务
本文目录