JVM

JVM

JVM简介

  • JVM(Java Virtual Machine)
    • 虚拟机:通过软件模拟的具有完整硬件功能,运行在完全隔离环境中的计算机系统
    • 常见虚拟机:VMWare/Box
  • JVM是通过软件模拟Java字节码指令集,JVM中只保留了PC寄存器,而普通的虚拟机有很多寄存器
  • 从JDK1.3至今,HotSpot为默认JVM

Java内存区域划分 – 共六块内存区

线程私有内存:每个线程都有,彼此之间完全隔离

  1. 程序计数器(Program Counter Register) – 记录上一次执行到的地址

    程序计数器是比较小的内存空间,当前线程所执行的字节码的行号指示器

    • 若当前线程执行的是Java方法,计数器记录的是正在执行的JVM字节码指令地址
    • 若当前线程执行当是 Native本地方法时, 计数器为空

    程序计数器是唯一一块不会产生OOM(OutOfMemoryError)异常的区域

  2. 虚拟机栈(Virtual Machine Stacks) – Java方法执行的内存模型

    虚拟机栈描述了Java方法执行的内存模型:每个方法执行时都会创建一个栈帧存储局部变量表(决定了栈帧开辟内存空间的大小),操作数栈方法出口,动态链接等信息。每个方法从调用到执行完毕的过程就相当于对应一个栈帧从虚拟机的入栈的出栈过程。

    局部变量表:存放编译器可知的各种数据类型(8大基本数据类型),对象引用(大小固定为4个字节)。局部变量所需要的空间是在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量是完全确定的,在执行期间不会再改变局部变量表的大小

    • 生命周期好线程相同:在创建线程同时创建此线程的虚拟机栈,线程执行结束,虚拟机栈会和线程一同被回收

    可能会产生两种异常:

    1. 若线程请求深度大于JVM允许的深度(-Xss设置栈容量),会抛出StackOverflowError异常。(发生情况一般在单线程模式下)
    2. 虚拟机在进行栈的动态扩展时,若没有申请到足够内存,会抛出OOM(OutOfMemoryError)异常。(发生情况一般在多线程模式下)

    注意:Java中可以大致的分为堆内存(Heap)和栈内存(Stack),这个概念很粗糙,这里的栈内存其实就是这里的虚拟机栈,也可以说是虚拟机栈中局部变量表的部分。

  3. 本地方法栈(Native Method Stack)

    • 本地方法与虚拟机栈的作用类似,但是虚拟机栈为虚拟机执行Java方法服务,本地方法为虚拟机使用的Native方法服务, 在HotSpot虚拟机中,本地方法个虚拟机栈是同一块内存区域,合二为一
    • 与虚拟机栈一样,本地方法也会抛出StackOverflowError和OutOfMemoryError异常

线程共有内存:所有线程共享此内存空间,此内存空间所有线程可见

  1. 堆(GC堆 Garbage Collected Heap)

    Java堆(Java Heap)时JVM管理的最大内存区域。在JVM启动时创建,所有线程共享此内存,该内存中存放的都是 对象实例以及数组

    • Java堆时垃圾回收器管理的最主要内存区域。Java堆对可以处于物理上不连续,只要是逻辑上连续的即可,就例如磁盘空间一般。在实现时,即可以实现成固定大小,也可以是可扩展的(-Xmx设置堆最大值)(-Xms设置堆的最小值)
    • 若堆中没有足够的内存完成对象实例分配并且堆无法再次扩展时,会抛出OOM(OutOfMemoryError)异常
  2. 方法区(Method Area)

    • 用于存储已经被JVM加载的类信息,常量,静态变量数据。JDK8以前,方法区也叫作 永久带(Permanent Generation),JDK8之后称之为 元空间(Meta Space)
    • 当方法区无法满足内存分配需求时,会抛出OOM(OutOfMemoryError)异常
  3. 运行时常量池(Runtime Constant Pool)

    运行时常量池是方法区的一部分,存放字面量与符号引用。

    • 字面量:字符串常量(JDK1.7移到堆中),final常量,基本数据类型的值。
    • 符号引用:类,字段,方法的完全限定名,方法的名称,描述符。

    运行时常量池具备动态性,java语言并没有要求常量一定只有编译期才能产生,也就是并非提前置入Class文件中常量池的内容才能进入方法区常量池,运行期间也可以将新的常量放入池中,就比如String类的intern()手动入池方法

直接内存

关于OOM产生的原因:
  • 内存溢出:内存中对象确是还应该存活,但是由于堆内存不够用产生的异常
  • 内存泄漏:无用对象无法被GC

那么如何区分OOM是那种原因产生

  • 比如:存在一个递归,并且递归没有出口,这时运行程序,就会产生OOM异常,当我们要判断该异常是是什么原因产生的时,只需要将堆内存的可扩展性扩大,如果不会产生OOM异常,则是由于内存溢出而导致,否则为内存泄漏,在该栗子中,很明显当我们将堆内存扩大时并不能解决OOM问题,所以该异常是由于内存泄漏引起的。

判断对象是否已死

引用计数法(Reference Counting)

算法思想:给每个对对象附加一个引用计数器,每当有一个地方引用此对象时,计数器+1;每当有一个引用失效时,计数器-1;在任意时刻,只要计数器值为0的对象就是不能再被使用,即对象已死。

  • 引用计数法实现简单,判定效率较高。Python就是采用引用计数法来管理内存。但是无法解决循环引用问题

可达性分析算法(Reachability Analysis)

Java采用可达性分析算法来判断对象是否存活(C#,Lisp)

算法思想:通过一系列“GC Roots”的对象作为起点,从这些节点开始向下搜索对象,搜索走过的路径,称为“引用链”,当一个对象到任意一个GC

Root对象没有任何的引用链相连时(从GC Roots到对象不可达),证明对象已死。

Java中能够作为GC Roots的对象包含一下四种 :

  1. 虚拟机栈(栈帧中的本地变量表)中引用对象
  2. 类静态变量引用的对象
  3. 常量引用的对象
  4. 本地方法栈中引用的对象