JVM

155 阅读4分钟

image.png

同样的java程序在不同平台生成的机器码是不同的。

运行代码时,把class文件加载到运行时数据区

image.png

image.png

只要有线程运行了,JVM就会给他分配一个JVM线程栈空间。当线程中的某个方法执行了,就给这个方法在jvm线程栈中分配一个内存空间(叫做栈帧),用于存放这个方法的局部变量,操作数,方法出口。

image.png

image.png

java代码

 
public class Add {
    private static User user=new User();
    user存在方法区中,指向队中的对象User
    private final int x=9;
    private static final int y=8;
 
    public   int add(){
        int a=1;
        int b=2;
        int c=(a+b)*10;
        return c;
    }

    public static void main(String[] args) {
        Add a=new Add();   
        a存在线程栈的局部变量表中,并执行队中的Add对象。
        int res=a.add();

    }
}

JVM指令码:

指令码中的每条指令前面的序号存在程序计数器中。 局部变量表中存方法中声明的局部变量。 操作数栈中存计算时的操作数,中间数、结果等。add方法里的1,2,10这些都是操作数 动态链接: 方法出口:方法返回后,要执行哪一个指令。

程序计数器的值是谁修改的? 字节码执行引擎。jvm指令就是字节码执行引擎来执行的。

本地方法栈:

线程的start方法:private native void start0();
有native修饰的方法
最终是由c实现的,而执行c也需要一块内存

image.png

Compiled from "Add.java"
public class com.wj.lock.Add {
  public com.wj.lock.Add();
    Code:
       0: aload_0
       1: invokespecial #1           // Method java/lang/Object."<init>":()V
       4: aload_0
       5: bipush        9
       7: putfield      #2                  // Field x:I
       10: return

  public int add();
    Code:
       0: iconst_1     在栈帧中创建一个局部变量1,存在栈帧的局部变量表中。
       1: istore_1     将int类型的值赋值给局部变量1。从操作数栈中弹出这个值赋值给局部变量1.
       2: iconst_2     在栈帧中创建一个局部变量2,存在栈帧的局部变量表中。
       3: istore_2       将int类型的值赋值给局部变量24: iload_1       拿到局部变量1d的值 
       5: iload_2       拿到局部变量2d的值
       6: iadd          执行int类型的加法,操作数为前两步拿到的。
       7: iconst_10      
       8: imul          执行int类型的乘法,结果存入操作数栈
       9: istore_3      结果从操作数栈中弹出,赋值给局部变量310: iload_3       拿到局部变量3的值
      11: ireturn        把上一步拿到的值返回




  public static void main(java.lang.String[]);
    Code:
       0: new           #2    // class com/wj/lock/Add  局部变量表中存了一个对象的引用。
       3: dup                  
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method add:()I
      12: istore_2
      13: return
      
      
  static {};
    Code:
       0: new           #5                  // class com/wj/lock/User
       3: dup
       4: invokespecial #6            // Method com/wj/lock/User."<init>":()V
       7: putstatic     #7                  // Field user:Lcom/wj/lock/User;
      10: return
}

方法区(元空间)

存静态变量、常量、类信息

垃圾回收是由字节码执行殷勤进行的 image.png

垃圾回收

image.png GC Time:每个凸起都是依次垃圾回收,这时候,开始minor gc,清理新生代,eden区被清理,s0,s1交替。老年代增大。

当老年代满了,会进行full gc,回收堆中的所有区域。如果还是满,那就会内存溢出OOM.

垃圾收集器

垃圾分析

可达性分析

GC Roots为起点向下搜索。 哪些可以作为GC Roots:根是只会引用其他对象但不会被其他对象引用的。有线程栈的本地变量、方法区静态变量,本地方法栈的变量.

分析时首先把所有GC Root都找出来。

比如main方法中定义了一个对象user,这就是一个根节点,从它开始,还要找user对象的成员。

eden区增长一段时间后就没了,有些被回收了,有些进入了survivor区,非垃圾对象在s0和s1之间来回放。当对象分代年龄达到15时,进入老年代。老年代一直增长.

STW

为什么垃圾收集过程要STW,如果没有STW,那么GC会一直找下去,线程都结束了,GC还在进行。 而线程结束不意味着对象没有了,对象还在堆中,只是线程的局部变量没有了,GC roots根也就没有。没有了根,上下的对象就都成了垃圾。

jvm参数设置

image.png 假设程序运行时,每s产生60MB的对象, 14s占满edan,那么每14s就要minor gc。gc时要触发stw。但是可能有些线程执行的慢了,到了14s还没有执行结束,那这些线程用到的一些对象就不是垃圾,不会被回收,这部分对象就从edan一道survivor区。

这个时候会触发一个机制:如果要移到survivor区的对象很大,可以直接移到老年代。多大的对象需要直接移动到老年代?可以手动设置。

如果大小设置的不合理,会让老年代很快满了,导致频繁的full gc。