JVM

详细介绍JVM

  • 1.JVM生命周期
  • 2.JVM体系结构
  • 3.JVM运行时数据区
    • 3.1 Java堆(Heap)
    • 3.2 方法区
    • 3.3 Java虚拟机栈(VM Stack)
    • 3.4 本地方法栈(Native Method Stack)
    • 3.5 程序计数器(Program Counter Register)
    • 3.6 直接内存(Direct Memory)
  • 4. Java代码的编译和执行过程
    • 4.1 Java源码编译机制
    • 4.2 类加载机制(ClassLoader)

1.JVM生命周期

  • 启动 启动一个Java程序时, 一个JVM实例就产生了, 任何一个拥有pulic static void main(String[] args)函数的class都可以作为JVM实例运行的起点.
  • 运行 main()作为该程序初始线程的起点, 任何其他线程均由该线程启动.
  • 消亡 当程序中的所有非守护线程都终止时, JVM才退出; 若安全管理其允许, 程序也可以使用Runtime类或者System.exit()来退出.

   一个运行中的Java虚拟机有着一个清晰的任务: 执行Java程序. 程序开始执行时他才运行, 程序结束时他就停止. 你在同一台机器上运行三个程序, 就会由三个运行中的Java虚拟机. Java虚拟机总是开始于一个main()方法, 这个方法必须是共有, 返回void, 接受一个字符串组. 在程序执行时, 你必须给Java虚拟机指明这个包含main()方法的类名. main()方法是程序的起点, 他被执行的线程初始化为程序的初始线程. 程序中其他的线程都有它来启动.

  Java中的线程分为两种: 守护线程(daemon)和普通线程(non-daemon). 守护线程时Java虚拟机自己使用的线程, 比如负责垃圾收集的线程就是一个守护线程. 当然, 你也可以把自己的程序设置为守护线程. 包含main()方法的初始线程不是守护线程.
  只要Java虚拟机中有普通的线程在执行, Java虚拟机就不会停止. 如果有足够的权限, 你可以调用exit()方法终止程序.

2.JVM体系结构

  1. 类装载器(ClassLoader) (用来装载.class文件)
  2. 执行引擎(执行字节码, 或者执行本地方法)
  3. 运行时数据区(方法区, 堆, Java栈, PC寄存器, 本地方法栈)

3.JVM运行时数据区

JVM_第1张图片

3.1 Java堆(Heap)

  • 被所有线程共享的一块内存区域, 在虚拟机启动时创建
  • 用来存储对象实例
  • 可以通过-Xmx和-Xms控制堆的大小
  • OutOfMemoryError异常: 当在堆中没有内存完成实例分享, 且堆也无法扩展时.
      Java堆是垃圾收集器管理的主要区域. Java堆还可以细分为: 新生代(New/Young), 旧生代/老年代(Old/tenured). 持久代(Permanent)在方法区, 不属于Heap.

JVM_第2张图片

新生代: 新建的对象都有新生代分配内存. 常常有被划分为Eden区和Survivor区. Eden空间不足时会把存活的对象转移到Survivor. 新生代的大小可由-Xmn控制, 也可以用-XX:S涮vivoRatio控制Eden和Survivor的比例.

**旧生代:**存放经过多次垃圾回收仍然存活的对象.

**持久代:**存放静态文件, 如Java类, 方法等. 持久代在方法区, 对垃圾回收没有显著影响.

3.2 方法区

  • 线程共享
  • 用于存储以备虚拟机加载的类信息, 常量, 静态变量, 即时编译器编译后的代码等数据
  • OutOfMemoryError异常: 当方法去无法满足内存的分配需求时.

运行时常量池

  • 方法区的一部分
  • 用于存放编译器生成的各种字面量与符号引用, 如String类型常量就存放在常量池
  • OutOfmemoryError异常: 当常量池无法再申请到内存时。

3.3 Java虚拟机栈(VM Stack)

  • 线程私有, 生命周期与线程相同
  • 存储方法的局部变量表(基本类型、对象引用)、操作数找、动态链接,方法出口等信息。
  • Java方法执行的内存模型,每个方法执行的同时都会创建一个栈帧,每个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
  • StackOverflowError异常:当线程请求的栈深度大于虚拟机允许的深度
  • OutOfMemoryError异常:如果栈的扩展时无法申请到足够的内存

  JVM栈时线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量、部分的返回结果以及Stack Frame。其他引用类型的对象在JVM栈上仅存放变量名和指向堆上对象实例的首地址

3.4 本地方法栈(Native Method Stack)

  • 与虚拟机栈相似,主要为虚拟机使用到的Native方法服务,在HotSpot虚拟机中直接把本地方法与虚拟机找二合一

3.5 程序计数器(Program Counter Register)

  • 当前线程所执行的字节码的行号指示器
  • 当前线程私有
  • 不会出现OutOfMemoryError情况

3.6 直接内存(Direct Memory)

  • 直接内存并不是虚拟机运行的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁使用。
  • NIO可以使用Native函数库直接分配堆外内存,堆中的DirectByteBuffer对象作为这块内存的引用进行操作‘
  • 大小不受Java堆大小的限制,受本机(服务器)内存限制
  • OutOfMemoryError异常:系统内存不足时

  总结: Java对象实例存放在堆中,常量存放在方法区的常量池;虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据放在方法区;以上区域是所有线程共享的。栈时线程私有的,存放该方法的局部变量表(基本类型、对象引用)、操作数找。动态链接。方法出口等信息。
  一个Java程序对应一个JVM,一个方法(线程)对应一个Java栈。

4. Java代码的编译和执行过程

Java代码的编译和执行包含了三个重要机制:

  1. Java源码编译机制(.Java源代码文件 -> .class字节码文件)
  2. 类加载机制(ClassLoader)
  3. 类执行机制(JVM执行引擎)

4.1 Java源码编译机制

  Java源代码是不能被机器识别的,需要先经过编译器编译成JVM可以执行的.class字节码文件,再由解释器解释运行。即:Java源文件(.java) --> Java编译器 --> Java字节码文件(.class) --> 执行。流程图如下:

JVM_第3张图片

字节码文件(.class)是平台无关的。
Java中字符只以一种形式存在:Unicode。字符转换发生JVM个OS交界处(Reader/Writer)。
最后生成的class文件有以下部分组成:

  • 结构信息。包括class文件格式版本号及各部分的数量与大小的信息
  • 元数据。对应于Java源码中声明与常量的信息。包含类、继承的超类、实现的接口的声明信息、域与方法声明信息和常量池
  • 方法信息。对应Java源码中语句和表达式对应的信息。包含字节码、异常处理器表、求值栈与局部标量区大小、求值栈的类型记录、调试符号信息

4.2 类加载机制(ClassLoader)

  Java程序并不是一个可执行文件,是由多个独立的类文件组成。这些类文件并非一次性全部装入内存,而是依据程序逐步载入
  JVM的类加载时通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

JVM_第4张图片

  1. Bootstrap ClassLoader
  • JVM的根ClassLoader,由C++实现
  • 加载Java的核心API:$JAVA_HOME中jre/lib/rt.jar中所有class文件的加载,这个jar中包含了Java的规范定义的所有接口以及实现。
  • JVM启动时即初始化此ClassLoader
  1. Extension ClassLoader
  • 加载Java扩展API(lib/ext中的类)
  1. App ClassLoader
  • 加载Classpath目录下定义的class
  1. Custom ClassLoader
  • 属于应用程序根据自身需要自定义的ClassLoader,如Tomcat、jboss都会根据J2EE规范自行实现ClassLoader

  加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个ClassLoader已加载就视为已加载此类,保证此类所有ClassLoader只加载一次。而加载的顺序是自顶向下,也就是有上层来逐层尝试加载此类。

你可能感兴趣的:(Java,专属)