深入理解Java虚拟机-01

深入理解Java虚拟机-01

1. JVM与JRE与JDK的关系

2. JVM体系结构与工作方式概览

3. 类加载机制(双亲委派模型)

类加载机制

  • 类加载指将类的字节码文件(.class)中的二进制数据读入内存,将其放在运行时数据区的方法区内,然后在堆上创建java.lang.Class对象,封装类在方法区内的数据结构。类加载的最终产品是位于堆中的类对象,类对象封装了类在方法区内的数据结构,并且向JAVA程序提供了访问方法区内数据结构的接口。
  • 字节码的加载第一步,其后分别是认证、准备、解析、初始化
  • 如下是类加载器的层次关系图。

什么是双亲委派模型

  • 双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。

4. Java虚拟机的内存布局(运行时数据区域)

  • 运行时数据区,主要分为方法区、Java堆、虚拟机栈、本地方法栈、程序计数器。其中方法区和Java堆一样,是各个线程共享的内存区域,而虚拟机栈、本地方法栈、程序计数器是线程私有的内存区。

Heap(堆)

  1. Java堆是Java虚拟机所管理的内存中最大的一块,被进程的所有线程共享,在虚拟机启动时被创建。
  2. 该区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存
  3. Java堆是垃圾收集器管理的主要区域。由于现在的收集器基本都采用分代收集算法,所以Java堆中还可以分为老年代和新生代(Eden、From Survivor、To Survivor)。
  4. 根据Java虚拟机规范,Java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可。该区域的大小可以通过-Xmx和-Xms参数来扩展,如果堆中没有内存完成实例分配,并且堆也无法扩展,将会抛出OutOfMemoryError异常。
  5. heap划分
    1. 年轻代
      1. Eden(新生代)
      2. s0
      3. S1
    2. 老年代

MethodArea(方法区) - 1.8后取消

  1. 线程共享
  2. 用于存储被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  3. 不同于Java堆的是,Java虚拟机规范对方法区的限制非常宽松,可以选择不实现垃圾收集。但并非数据进入了方法区就“永久”存在了,这区域内存回收目标主要是针对常量池的回收和对类型的卸载。如果该区域内存不足也会抛出OutOfMemoryError异常。
    • 常量池:它是方法区的一部分。Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译期生成的各种字面量和符号引用。Java虚拟机运行期间,也可能将新的常量放入常量池(如String类的intern()方法)。

JVM Stacks(虚拟机栈)/Native Method Stacks(本地方法栈) - 1.7之后合并

  1. 线程私有,生命周期与线程相同。
  2. 方法在执行时会创建一个栈帧(Stacks Frame)用于存储局部变量表、操作帧数、动态链接、方法出口灯信息
  3. 方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
  4. 局部变量表所需的内存空间在编译期完成分配,而且分配多大的局部变量空间是完全确定的,在方法运行期间不会改变其大小
  5. 出栈后空间释放
  6. 如果请求的站深度大于虚拟机所允许的深度,将抛出StackOverflowError异常,虚拟机栈在动态扩展时如果无法申请到足够的内存,就会抛出OutOfMemoryError异常。

Program Count Register(程序计数器)

  1. 作用
    1. 当线程执行的字节码的行号指示器,通过改变指示器来选取下一个需要执行的字节码指令
  2. 特征
    1. 在线程创建时创建
    2. 每一个线程都有一个
    3. 指向下一条指令的地址

5. 堆和栈的区别

存储内容

  • 最主要的区别就是栈内存用来存储局部变量和方法调用。
  • 而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。

独有还是共享

  • 栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。
  • 而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。

异常错误

  • 如果栈内存没有可用的空间存储方法调用和局部变量,JVM会抛出java.lang.StackOverFlowError。
  • 而如果是堆内存没有可用的空间存储生成的对象,JVM会抛出java.lang.OutOfMemoryError。

空间大小

  • 栈的内存要远远小于堆内存,如果你使用递归的话,那么你的栈很快就会充满。如果递归没有及时跳出,很可能发生StackOverFlowError问题。
  • 你可以通过-Xss选项设置栈内存的大小。-Xms选项可以设置堆的开始时的大小,-Xmx选项可以设置堆的最大值。