43

Java性能 -- JVM内存模型

 4 years ago
source link: https://www.tuicool.com/articles/EFjAVb6
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
  1. 堆是JVM内存中 最大 的一块内存空间,被所有 线程共享几乎所有对象和数组 都被分配到堆内存中
  2. 堆被划分为 新生代老年代 ,新生代又被划分为 Eden 区和 Survivor 区( From Survivor + To Survivor)
  3. 永久代
    • 在Java 6 中,永久代在 非堆 内存中
    • 在Java 7 中,永久代的 静态变量运行时常量池合并到堆
    • 在Java 8 中,永久代被 元空间 取代
UBfaAnI.png!web

程序计数器

  1. 程序计数器是一块 很小 的内存空间,主要用来记录各个 线程 执行的字节码的地址
  2. Java是 多线程语言 ,当执行的线程数量 超过 CPU数量时,线程之间会根据时间片 轮询争夺CPU资源
    • 当一个线程的时间片用完了,或者其他原因导致该线程的CPU资源被提前抢夺
    • 那么 退出的线程 需要 单独的程序计数器 来记录 下一条运行的指令

方法区

  1. 方法区 != 永久代
  2. HotSpot VM使用了永久代来实现方法区 ,但在其他VM( Oracle JRockitIBM J9 )不存在永久代一说
  3. 方法区只是JVM规范的一部分 ,在HotSpot VM中,使用了永久代来实现JVM规范的方法区
  4. 方法区主要用来存放 已被虚拟机加载的类相关信息
    • 类信息 (类的版本、字段、方法、接口和父类等信息)、 运行时常量池字符串常量池
  5. JVM在执行某个类的时候,必须经过 加载连接 (验证、准备、解析)、 初始化
    • 加载类时,JVM会先加载class文件,在 class文件 除了有类的版本、字段、方法、接口等描述信息外,还有 常量池
      • 常量池( Constant Pool Table ),用于存放 编译期 生成的各种 字面量符号引用
      • 字面量: 字符串String a="b" ), 基本类型的常量 (final修饰)
      • 符号引用: 类和方法的全限定名字段的名称和描述符方法的名称和描述符
    • 当类加载到内存中后,JVM会将 class文件常量池中的内容 存放到 运行时常量池
    • 在解析阶段,JVM会把 符号引用 替换为 直接引用 (对象的索引值)
  6. 运行时常量池全局共享 的,多个类共用一个运行时常量池
    • class文件中的常量池多个相同的字符串在运行时常量池 只会存在一份
  7. 方法区和堆类似,都是一个 共享内存区 ,所以方法区是 线程共享
    • 假设两个线程都试图访问方法区中同一个类信息,而这个类还没有装入JVM
    • 那么此时只允许一个线程去加载该类,另一个线程必须等待
  8. HotSpot VM
    • 在Java 7中,已经将永久代的 静态变量运行时常量池 转移到 中,其余部分则存储在JVM的 非堆 内存中
    • 在Java 8中,已经用 元空间 代替永久代来实现 方法区 ,并且元空间的存储位置是 本地内存
      • 之前永久代中 类的元数据 存储在 元空间 ,永久代的 静态变量运行时常量池 与Java 7一样,转移到
    • 移除永久代,使用元空间的好处
      • 移除永久代是为了 融合HotSpot VM和JRockit VM
      • 永久代内存经常不够用或者发生 内存溢出 (java.lang.OutOfMemoryError: PermGen)
      • 为永久代分配多大的空间很难确定,依赖很多因素

虚拟机栈

  1. Java虚拟机栈是 线程私有 的内存空间,和Java线程一起创建
  2. 当创建一个线程时,会在虚拟机栈中申请一个 线程栈
    • 用来保存方法的局部变量、操作数栈、动态链接方法和返回地址等信息,并参与方法的调用和返回
  3. 每一个 方法的调用 都伴随着栈帧的 入栈 操作,每一个 方法的返回 都伴随着栈帧的 出栈 操作

本地方法栈

  1. 本地方法栈跟Java虚拟机栈的 功能类似
  2. Java虚拟机栈用来管理 Java函数的调用 ,而本地方法栈用来管理 本地方法(C语言实现)的调用

JVM运行原理

public class JVMCase {
    // 常量
    private final static String MAN_SEX_TYPE = "man";
    // 静态变量
    public static String WOMAN_SEX_TYPE = "woman";

    public static void main(String[] args) {
        Student student = new Student();
        student.setName("nick");
        student.setSexType(MAN_SEX_TYPE);
        student.setAge(20);

        JVMCase jvmCase = new JVMCase();
        // 调用非静态方法
        jvmCase.sayHello(student);
        // 调用静态方法
        print(student);
    }
    
    // 非静态方法
    private void sayHello(Student student) {
        System.out.println(student.getName() + " say: hello");
    }

    // 常规静态方法
    private static void print(Student student) {
        System.out.println(student);
    }
}

@Data
class Student {
    private String name;
    private String sexType;
    private int age;
}
  1. JVM 向操作系统申请内存
    • JVM第一步是通过配置参数或者默认配置参数向操作系统申请内存空间,根据内存大小找到具体的 内存分配表
    • 然后把内存段的 起始地址终止地址 分配给JVM,最后JVM进行 内部分配
  2. JVM获得内存空间后,会根据配置参数分配 以及 方法区 的内存大小
  3. class文件 加载验证准备解析
    • 其中 准备 阶段会为 类的静态成员(字段和方法) 分配内存,初始化为系统初始值
  4. 初始化
    • JVM首先会执行构造器 <clinit> 方法,编译器会在.java文件被 编译 成.class文件,收集所有类的 初始化代码
    • 初始化代码: 静态变量赋值语句静态代码块静态方法
  5. 执行方法
    • 启动main线程,执行main方法,执行第一行代码
    • 内存中会创建一个 Student对象对象引用student 存放在
    • 再创建一个JVMCase对象,并调用sayHello非静态方法
      • sayHello方法属于对象jvmCase,此时sayHello方法 入栈 ,并调用栈中Student引用调用堆中的Studentd对象
    • 调用静态方法print
      • print方法属于JVMCase类,从静态方法中获取后放入到栈中,通过Student引用调用堆中的Studentd对象

准备阶段

Ajeayeu.jpg!web

初始化阶段

2iU32yU.jpg!web

创建一个Student对象

FR7VRvA.jpg!web

创建一个JVMCase对象,并调用sayHello非静态方法和print静态方法

QZZFjaq.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK