JVM底层原理与性能调优

JVM底层

在这里插入图片描述

又叫线程栈 是用来放局部变量的 以下面的这个类为例
math就是个局部变量 a b c也是 放在栈内存空间中

public class Test {
    public static final int a=100;
    public static User user=new User();

    public int math(){
        int a=100;
        int b=2;
        int c=(a + b)*10;
        return c;
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.math();
    }
}

在程序运行的时候 jdk会给每个线程在栈中分配一个栈空间 放局部变量 每有一个新线程 都会在栈中开辟相应的栈空间 是每个线程独享的
每一个方法对应一块栈帧内存区域
栈帧:每次调到方法的时候 都会在栈中 开辟一块栈帧 保存 这个方法里的局部变量
在这里插入图片描述
即:当我们main方法调用的时候 调用到了math方法 将math压入栈执行 math方法执行完之后
弹栈 进行执行main方法
栈 这种数据结构 非常符合我们程序代码的运行顺序 所以我们使用栈 先进后出

栈帧结构

在这里插入图片描述

使用 javap -c Test.class 对该文件进行反汇编

Compiled from "Test.java"
public class Test {
  public static final int a;

  public static User user;

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

  public int math();
    Code:
       0: bipush        100
       2: istore_1
       3: iconst_2
       4: istore_2
       5: iload_1
       6: iload_2
       7: iadd
       8: bipush        10
      10: imul
      11: istore_3
      12: iload_3
      13: ireturn

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class Test
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method math:()I
      12: pop
      13: return

  static {};
    Code:
       0: new           #5                  // class User
       3: dup
       4: invokespecial #6                  // Method User."<init>":()V
       7: putstatic     #7                  // Field user:LUser;
      10: return
}

我们 以 math方法 为例 作 探讨

 public int math();
    Code:
       0: bipush        100
       2: istore_1
       3: iconst_2
       4: istore_2
       5: iload_1
       6: iload_2
       7: iadd
       8: bipush        10
      10: imul
      11: istore_3
      12: iload_3
      13: ireturn

将一个8位带符号的整数100压入栈(1位置 保存的是100)
将int类型常量1存入局部变量1(a)
将int类型常量2压入操作数栈
将int类型常量2存入局部变量2(b)
//也就是说 先在局部变量中 将b这个变量对应的内存空间分配出来 然后 它会将常量2 弹出操作数栈 把常量2 放到a局部变量 对应的内存空间去
从局部变量1中装载在int类型的值
从局部变量2中装载在int类型的值
执行int类型的加法 将运算的结果重新压回操作数栈
将一个8位带符号的整数10压入栈(9位置保存的是10)
执行int类型的乘法 将运算的结果重新压回操作数栈
将int类型常量3存入局部变量3(c)
取出c
返回

局部变量表
保存局部变量

操作数栈
程序运行时 作运算 赋值时 临时的内存空间 作中转空间

程序计数器
记录程序 运行的位置 是每一个线程独享的
每新建一个线程的时候 都会在程序计数器里面 分配一个空间给这个线程 作为这个线程的私有程序计数器
如上 所示 每一行代码 所示的数字 就可以看成程序这一行代码对应的内存位置的标记 程序计数器里面存储的是 当前程序正在运行的代码的位置 它里面的值是动态变化的 是字节码执行引擎去修改的
在多线程中 是特别重要的 因为 当这个线程的时间片 使用完之后 当它继续占有时间片之后 就可以通过程序计数器 继续执行为完成的操作 而不是 从头开始

动态链接
类似于 多态 找对应的实现
将符号引用转变为直接引用

方法出口
当math方法执行完之后 我们需要回到main方法 所以 我们需要知道 我们要回到main方法的哪一行
哪个代码的位置
即 当math方法 执行完之后 回到main方法的哪一行位置

在main栈中 我们知道 有一个叫test的对象局部变量 但是 对象 是放到堆里面的
这两个 test 是不一样的 test这个局部变量 代表了一个空间 简单来说 这一段内存空间 就放的是一段地址 即是引用 指针 在这里插入图片描述
即 栈和堆的关系就是 栈中有很多对象指针 这些对象的实例都在堆中

方法区

放 常量 静态变量 类信息
即 我们的Test.class 通过类装载子系统 加载到方法区中
上文中的 user a 都会放到方法区中
当然 这个 user 还是存在于堆中 方法区里的是指针

当Test.class通过类装载子系统装载之后 会放到方法区 然后 字节码执行引擎进行 执行

本地方法栈

存放着 native 方法 这些方法 都是c语言方法 Java通过调这些方法 实现jdk的一些功能
比如说 Thred 中的 start 方法 就是通过jdk的C语言方法 来调用的

private native void start0();

在这里插入图片描述
一般来说 堆的默认大小为600MB 老年代 占2/3 年轻代占1/3
年轻代 又分成Eden区 Form区 To区 占比为8:1:1
new 出来的对象 首先放到eden区
当eden区到一定容量的时候 会去做minor GC 或者younger GC
这个是 字节码引擎在后台开启的垃圾收集线程 它会有一个专门的线程 去做这个垃圾收集
会将eden元区的垃圾清楚 然后 空出新的内存 放新的对象

根据 GCroot 对象为起点 从这些节点开始向下搜索对象,找到的对象都标记为非垃圾对象,其余为标记的对象 都是垃圾对象
GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等。
第一次 标记完之后 会将 这些对象 放到 survivor区域中的一个 然后 将eden区 清空 (标记清除)
到了 第二次 不光标记eden 也会标记 survivor区中含对象的区 将这些继续标记 将标记了的对象 复制到survivor中无对象的区(复制算法)
当 一个对象 每经历过一次minor GC的时候 就会将自己的分代年龄就会加1 等到15的时候 就会转到老年代
分代年龄 存在于对象头中

通过 在黑窗口 通过jvisualvm 命令 可以打开jvm调优诊断的工具
当它启动的时候 会去自动识别本地所有的jvm进程
因为 jdk不自带 所以我们需要去下载 它 Visual GC
以这个例子为例

public class HeapTest {
   byte[] a=new byte[1024];

    public static void main(String[] args) throws InterruptedException {
        ArrayList<HeapTest> lis = new ArrayList<>();
        while (true){
            lis.add(new HeapTest());
            Thread.sleep(10);
        }
    }
}

在这里插入图片描述
可以看到 各个区的 运行状态

当老年代 放满的时候 就会执行 fullGC
它会收集整个堆 进行 扫描 收集所有的垃圾对象
如果 这些 对象都被引用着 就会产生 OOM(JVM 内存严重不足)

当线程进行垃圾回收的时候 会进行一个操作 STW
STW:将所有的线程 停止掉 只保留垃圾回收线程
暂定其余线程的原因:防止其余线程进行执行了之后 标记对象变成垃圾对象 但是 minorGC不知道的情况

Java虚拟机调优的目的

Java虚拟机在做GC的时候 会导致 用户线程的停顿 导致 用户线程的卡顿
所以 我们要增加程序运行的效率 即 减少STW时间 减少用户停止的时间
如果 减少 即 减少 fullGC的次数 减少一个fullGC的执行时间
一般 来说 要设置好 survivor 区的大小
因为 当一次送过来的对象 大于survivor区的一半时 就会转到老年区
当然 还有大对象

设置命令
java -Xms3072M -Xmx3072M -Xss1M -XX:MetaspaceSize=512M -xx:MaxMetaspaceSize=512M
-jar xxxx.jar

8个g分配给堆空间 初始堆和最大堆3个g
按默认比例 3个g old2个g eden800M s0 s1 各100M

当线程每秒产生60M的对象时间 运行25s占满eden区 当gc的时候 最后一秒产生的对象(假设对象的生命周期为1s) 不会被GC 会被转移到survivor区 因为大于survivor区的一半容量 就会被放到old区

这个时候 我们可以对Java虚拟机的内存 做个调优
将 年轻代调大一些 老年代调小一点
-Xmn2048M 设置年轻代的大小

java -Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M
-xx:MaxMetaspaceSize=256M -jar xxxx.jar

这样的话 老年区为1g eden区1.6g s0 s1 200M
满足需求

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值