头图

1. JVM从编译到执行

1.1 Java的执行过程

过程:Java 文件->编译器>字节码->JVM(识别class文件)->机器码。
(任何class文件都要经过类加载器,一般情况情况下走字节码解释器,有时会走JIT编译器)
image.png

1.2 JVM的作用

作用:从软件层面屏蔽不同操作系统在底层硬件和指令的不同。 (宏观方面)
JVM 是一个虚拟化的操作系统, 类似于 Linux 或者 Windows 的操作系统, 只是它架在操作系统上, 接收字节码也就是 class, 把字节码翻译成操作系统上的机器码且进行执行

1.3 JVM的两大特性

  1. 跨平台性
    不同的jdk版本地址:https://www.oracle.com/java/t...
  2. 语言无关性

2. JVM的知识体系

JVM 是一个虚拟化的操作系统, 所以除了要虚拟指令之外, 最重要的一个事情就是需要虚拟化内存

  1. 内存结构、
  2. 垃圾回收(回收内存)
  3. 类加载(加载到内存)
  4. 性能调优(内存优化)
  5. JVM 自身优化技术(内存优化)
  6. 执行引擎(与内存密不可分)
  7. 类文件结构(内存的设计)
  8. 监控工具等(监控内存)
  9. ......

image.png

2.1 JVM内存区域(重点)

2.1.1 运行时数据区域

定义:Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域
内存划分:堆、 程序计数器、 方法区、 虚拟机栈和本地方法栈
按照与线程的关系:
私有线程(一个线程拥有单独的一份内存区域)
共享线程(被所有线程共享, 且只有一份)

不是运行时数据区:
直接内存:又称对外内存,会被频繁使用,没有被虚拟机化的操作系统上的其他内存,JVM 是借助一些工具使用

image.png

2.1.2 JAVA方法的运行与虚拟机栈

虚拟机栈
  1. 定义:线程运行 java 方法所需的数据, 指令、 返回地址。(一个线程是可以运行多个方法的)

    示例:

    package ex1;
    
    public class MethodAndStack {
         public static void main(String[] args) {
            A();
        }
        public static void A(){
            B();
        }
        public static void B(){
            C();
        }
        public static void C(){
            System.out.println("over");
        }
    }
    

    上述示例由于main方法调用A,A调用B,B调用C,这些方法的调用都运行在main方法所在的线程,同时在执行每个方法的时候都会打包成一个栈帧。比如main方法运行时,会打包一个栈帧送入到虚拟机栈。
    image.png

    C方法运行完了,C方法出栈,接着B运行完了,B出栈、接着A运行完了,A出栈,最后main方法运行完了,main 方法这个栈帧就出栈了

  2. 栈的数据结构:先进后出(FILO)的数据结构
  3. 虚拟机栈是基于线程的:线程私有,一个线程对应一个虚拟机栈

    方法运行时的内存区域
    image.png
  4. 虚拟机栈的大小:缺省为1M,可用参数 –Xss 调整大小,例如-Xss256k。参数官方文档(JDK1.8): https://docs.oracle.com/javas...

    栈溢出问题
    虚拟机栈这个内存也不是无限大, 它有大小限制, 默认情况下是 1M。
    如果不断的往虚拟机栈中入栈帧(调用方法), 但是就是不出栈的话, 那么这个虚拟机栈就会爆掉。

    示例1:

    package ex1;
    
    public class MethodAndStack {
         public static void main(String[] args) {
            A();
        }
        public static void A(){
            A();
        }
    }
    

    无限递归后输出异常信息:Exception in thread "main" java.lang.StackOverflowError

  5. 栈帧(核心):每个 Java 方法被调用的时候, 都会创建一个栈帧, 并入栈。 一旦方法完成相应的调用, 则出栈。

    栈帧大体包含的四个区域:

    1. 局部变量表:存放我们的局部变量的(方法中的变量)(8大基本类型+对象地址)
    2. 操作数栈(最核心的):存放 java 方法执行的操作数的, 它就是一个栈。执行引擎(类比CPU)的一个工作区域,对应操作系统的缓存, 操作数栈本质上是 JVM 执行引擎的一个工作区, 也就是方法在执行, 才会对操作数栈进行操作,如果代码不不执行,操作数栈其实就是空的
    3. 动态连接:Java 语言特性多态(此处略过)
    4. 返回地址:正常返回(调用程序计数器中的地址作为返回)、 异常的话(通过异常处理器表<非栈帧中的>来确定)

    栈帧执行对内存区域的影响
    image.png

    在 JVM 中, 基于解释执行的这种方式是基于栈的引擎, 这个说的栈, 就是操作数栈。

总结:虚拟机栈就是用来存储线程运行方法中的数据的。
一个线程对应一个虚拟机栈
一个方法对应一个栈帧

程序计数器

较小的内存空间, 当前线程执行的字节码的行号指示器; 各线程之间独立存储, 互不影响。
由于 Java 是多线程语言, 当执行的线程数量超过 CPU 核数时, 线程之间会根据时间片轮询争夺 CPU 资源。 如果一个线程的时间片用完了, 或者是其它原因导致这个线程的 CPU 资源被提前抢夺, 那么这个退出的线程就需要单独的一个程序计数器, 来记录下一条运行的指令。
另外:程序计数器也是 JVM 中唯一不会 OOM(OutOfMemory)的内存区域

运行时数据区及 JVM 的整体内存结构
  1. 本地方法栈(含有native关键字)
  2. 方法区:方法区主要是用来存放已被虚拟机加载的类相关信息, 包括类信息、 静态变量、 常量、 运行时常量池、 字符串常量池等。方法区是 JVM 对内存的“逻辑划分”。

    1)符号引用
    2)常量池与运行时常量池
    3) 元空间
  3. 堆:堆是JVM 上最大的内存区域, 我们申请的几乎所有的对象, 都是在这里存储的。 我们常说的垃圾回收, 操作的对象就是堆。堆空间一般是程序启动时, 就申请了, 但是并不一定会全部使用。 堆一般设置成可伸缩的

    堆大小参数:
    -Xms: 堆的最小值;
    -Xmx: 堆的最大值;
    -Xmn: 新生代的大小;
    -XX:NewSize; 新生代最小值;
    -XX:MaxNewSize: 新生代最大值;
    例如- Xmx256m
  4. 直接内存(堆外内存)(文中有描述):直接内存主要是通过 DirectByteBuffer 申请的内存, 可以使用参数“MaxDirectMemorySize” 来限制它的大小

进击的萝卜头
1 声望1 粉丝

实践是检验真理的唯一标准!