图灵学院笔记-java虚拟机底层原理

Table of Contents generated with DocToc

笔记来源:图灵学院

一、java虚拟机概述

  1. java虚拟机用于将字节码文件.class转化为不同操作系统上的机器码

  1. java虚拟机的内存模型


二、栈内存解析

2.1 概述

java虚拟机为每一个java线程分配一个栈内存空间,存放变量

public class Math{
    public static final int initData = 666;//常量存储在方法区
    public int compute() { //一个方法对应一块栈帧内存区域
        int a=1;
        int b=2;
        int c = (a+b)*10;
        return c;
    }
    public static void main(String[] args){
        Math math = new Math();
        math.compute();
    }
}

a,b,c 保存在main线程的compute()栈帧中

2.2 栈帧内部结构

学习栈帧内部结构前,我们首先要阅读字节码文件。

javac Math.java
javap -c Math.class > Math.txt

打开生成的Math.txt,可以看到反汇编后的代码如下:

Compiled from "Math.java"
public class jvm.Math {
  public static final int initData;

  public jvm.Math();
    Code:
       0: aload_0
       1: invokespecial #12            // Method java/lang/Object."<init>":()V
       4: return

  public int compute();
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: bipush        10
       9: imul
      10: istore_3
      11: iload_3
      12: ireturn

  public static void main(java.lang.String[]);
    Code:
       0: new           #1                  // class jvm/Math
       3: dup
       4: invokespecial #25                 // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #26                 // Method compute:()I
      12: pop
      13: return
}
2.2.1 我们来解析一下compute()里面的代码:
public int compute() { //一个方法对应一块栈帧内存区域
    int a=1;
    int b=2;
    int c = (a+b)*10;
    return c;
}

//=======================================

public int compute();
    Code:
       0: iconst_1	//将常量1压入操作数栈
       1: istore_1  //将int类型的值存入局部变量1 -- a
       2: iconst_2  //将常量2压入操作数栈
       3: istore_2  //将int类型的值存入局部变量2 -- b
       4: iload_1	//从局部变量1装载int类型的值
       5: iload_2   //从局部变量2装载int类型的值
       6: iadd		//1+2
       7: bipush        10
       9: imul		//*10
      10: istore_3	//将int类型的值存入局部变量3 -- c
      11: iload_3	//装载c的值
      12: ireturn

赋值过程:

装载,执行过程(操作数栈:临时存储需要操作的值):

2.2.2 main函数内存区域

​ main中存储了一个math对象的指针,指向堆(堆中有为这个对象开辟的内存空间):

三、本地方法栈

早期 JAVA 需要与C/C++进行交互,所以用本地方法栈存储C/C++方法

四、堆

首先我们看看堆的结构:

​ 堆假设只有600M的话,那么年轻代占了1/3,也就是200M,老年代占了2/3。最开始的时候,对象都存放在Eden伊甸区)中。

​ 当Eden区装满时,则字节码执行引擎开启一个线程,运行minor gc,扫描伊甸区中的对象,如果对象被引用了,则在s0区中复制一份,否则留在Eden中,被垃圾回收机制清理。

​ 其他区域也是类似的处理(在Eden,s0,s1中移动时,“年龄“会加大,当达到15时,会转入老年区,controller、service、bean、数据库持久层、缓存都是常见的老年区对象):

​ 当老年代区放满后,字节码执行引擎开启一个线程,运行full gc,对所有区域进行垃圾回收,减少堆的空间,但是老年区被引用的对象还是不会被回收,所以当老年区再次被放满溢出时,会触发OutOfMemoryError的异常,即java堆不够用了。

扩展:STW【stop the world】,当 JVM 调用gc清理堆时,会暂停掉所有用户程序,比如电商购物时,点击按钮后通常会卡顿一下

垃圾回收,减少堆的空间,但是老年区被引用的对象还是不会被回收,所以当老年区再次被放满溢出时,会触发OutOfMemoryError的异常,即java堆不够用了。

扩展:STW【stop the world】,当 JVM 调用gc清理堆时,会暂停掉所有用户程序,比如电商购物时,点击按钮后通常会卡顿一下

所以 JVM 调优就是:尽量想方设法减少 STW 时间

原文地址:https://www.cnblogs.com/theory/p/13338728.html