VM_01 内存结构(程序计数器、虚拟机栈、本地方法栈)

[本文笔记参考自小滴课堂和传智播客JVM学习教程整合而来

](https://blog.csdn.net/weixin_43591980/article/details/109593587)

一、JVM 入门介绍

JVM 定义

Java Virtual Machine,JAVA程序的运行环境(JAVA二进制字节码的运行环境)

JVM 优势

  • 一次编写,到处运行
  • 自动内存管理,垃圾回收机制
  • 数组下标越界检查

常见的JVM

在这里插入图片描述

:我们笔记所使用的的是HotSpot 版本

JVM JRE JDK的比较

JVM JRE JDK的区别:

img

学习步骤

学习顺序如下图:(由简到难)

在这里插入图片描述

二、内存结构

整体架构

在这里插入图片描述

1、程序计数器(寄存器)

Program Counter Register

在这里插入图片描述

1.1 作用

程序计数器用于保存JVM中下一条所要执行的指令的地址

0:getstatic #20 					 // PrintStream out = System.out;
1:astore_1 							// --
2:aload_1 							// out.println(1);
3:iconst_1 							// --
4:invokevirtual #26 				 // --
5:aload_1 						    // out.println(2);
6:iconst_2 							// --
7:invokevirtual #26 				 // --
8:aload_1 						    // out.println(3);
9:iconst_3 						    // --
10:invokevirtual #26 				 // --
11:aload_1 							// out.println(4);
12:iconst_4 						// --
13:invokevirtual #26 				 // --
14:aload_1 						    // out.println(5);
15:iconst_5 						// --
16:invokevirtual #26 				 // --
return

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在这里插入图片描述

Java指令执行流程

  • 每一条二进制字节码(JVM指令) 通过 解释器 转换成 机器码 然后 就可以被 CPU 执行了!

  • 解释器 将一条jvm 指令转换成 机器码后 其会 向程序计数器 递交 下一条 jvm 指令的执行地址

  • 程序计数器在硬件层面 其实是通过 寄存器 实现的!

  • 所以程序计数器的作用就是:用于保存JVM中下一条所要执行的指令的地址!

1.2 特点
  • 线程私有
    • CPU会为每个线程分配时间片,当当 前线程的时间片使用完以后,CPU就会去执行另一个线程中的代码
    • 程序计数器是每个线程私有的,当另一个线程的时间片用完,又返回来执行当前线程的代码时,通过程序计数器可以知道应该执行哪一句指令
  • 不会存在内存溢出

在这里插入图片描述

2、虚拟机栈

Java Virtual Machine Stacks

在这里插入图片描述

2.1 定义
  • 每个线程运行需要的内存空间,这一空间被称为虚拟机栈(Frames)
  • 每个栈由多个栈帧(Frame) 组成,对应着每个方法运行时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的方法,当方法执行时压入栈,方法执行完毕后 弹出栈
2.2 演示

在这里插入图片描述

代码

/**
 * @Auther: csp1999
 * @Date: 2020/11/10/11:36
 * @Description: 演示栈帧
 */
public class Demo01 {
    public static void main(String[] args) {
        methodA();
    }
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">methodA</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token function">methodB</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">methodB</span><span class="token punctuation">(</span><span class="token keyword">int</span> a<span class="token punctuation">,</span> <span class="token keyword">int</span> b<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">int</span> c <span class="token operator">=</span> a <span class="token operator">+</span> b<span class="token punctuation">;</span>
    <span class="token keyword">return</span> c<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

我们打断点来Debug 一下看一下方法执行的流程:

在这里插入图片描述

接这往下走,使方法B执行完毕:

在这里插入图片描述

然后方法A执行完毕,其对应的栈帧出栈,main方法对应的栈帧为活动栈帧;最后main执行完毕 栈帧出栈,虚拟机栈为空,代码运行结束!

2.3 面试问题辨析
  • 1.垃圾回收是否涉及栈内存?

    • 不需要。因为虚拟机栈中是由一个个栈帧组成的,在方法执行完毕后,对应的栈帧就会被弹出栈。所以无需通过垃圾回收机制去回收内存。
  • 2.栈内存的分配越大越好吗?

    在这里插入图片描述

    • 不是。因为物理内存是一定的,栈内存越大,可以支持更多的递归调用,但是可执行的线程数就会越少。
    • 举例:如果物理内存是500M(假设),如果一个线程所能分配的栈内存为2M的话,那么可以有250个线程。而如果一个线程分配栈内存占5M的话,那么最多只能有100 个线程同时执行!
  • 3.方法内的局部变量是否是线程安全的?

    在这里插入图片描述
    在这里插入图片描述

    从图中得出:局部变量如果是静态的可以被多个线程共享,那么就存在线程安全问题。如果是非静态的只存在于某个方法作用范围内,被线程私有,那么就是线程安全的!

看一个案例

/**
 * 局部变量的线程安全问题
 */
public class Demo02 {
    public static void main(String[] args) {// main 函数主线程
        StringBuilder sb = new StringBuilder();
        sb.append(4);
        sb.append(5);
        sb.append(6);
        new Thread(() -> {// Thread新创建的线程
            m2(sb);
        }).start();
    }
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">m1</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">// sb 作为方法m1()内部的局部变量,是线程私有的 ---&gt; 线程安全</span>
    StringBuilder sb <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>sb<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">m2</span><span class="token punctuation">(</span>StringBuilder sb<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">// sb 作为方法m2()外部的传递来的参数,sb 不在方法m2()的作用范围内</span>
    <span class="token comment">// 不是线程私有的 ---&gt; 非线程安全</span>
    sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>sb<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> StringBuilder <span class="token function">m3</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">// sb 作为方法m3()内部的局部变量,是线程私有的</span>
    StringBuilder sb <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// sb 为引用类型的变量</span>
    sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> sb<span class="token punctuation">;</span><span class="token comment">// 然而方法m3()将sb返回,sb逃离了方法m3()的作用范围,且sb是引用类型的变量</span>
    <span class="token comment">// 其他线程也可以拿到该变量的 ---&gt; 非线程安全</span>
    
    <span class="token comment">// 如果sb是非引用类型,即基本类型(int/char/float...)变量的话,逃离m3()作用范围后,则不会存在线程安全</span>
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

该面试题答案

  • 如果方法内局部变量没有逃离方法的作用范围,则是线程安全
  • 如果局部变量引用了对象,并逃离了方法的作用范围,则需要考虑线程安全问题
2.4 内存溢出

Java.lang.stackOverflowError 栈内存溢出

发生原因

在这里插入图片描述

  • 1.虚拟机栈中,栈帧过多(无限递归),这种情况比较常见
  • 2.每个栈帧所占用内存过大(某个/某几个栈帧内存直接超过虚拟机栈最大内存),这种情况比较少见

举2个案例:

案例1

/**
 * 演示栈内存溢出 java.lang.StackOverflowError
 * -Xss256k 可以通过栈内存参数 设置栈内存大小
 */
public class Demo03 {
    private static int count;
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span>
        <span class="token function">method1</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Throwable</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">method1</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    count<span class="token operator">++</span><span class="token punctuation">;</span><span class="token comment">// 统计栈帧个数</span>
    <span class="token function">method1</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 方法无限递归,不断产生栈帧 到虚拟机栈</span>
<span class="token punctuation">}</span>

}

最后输出结果:
java.lang.StackOverflowError
at com.haust.jvm_study.demo.Demo03.method1(Demo03.java:21)
...
...
39317// 栈帧个数,不同的虚拟机大小能存放的栈帧数量不一样

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

我们可以通过修改参数来指定虚拟机栈内存大小

在这里插入图片描述

当我们将虚拟机栈内存缩小到指定的256k的时候再运行Demo03后,会得到其栈内最大栈帧数为:3816 远小于原来的39317

案例2

/**
 * 两个类之间的循环引用问题,导致的栈溢出
 * 
 * 解决方案:打断循环,即在员工emp 中忽略其dept属性,放置递归互相调用
 */
public class Demo04 {
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> JsonProcessingException <span class="token punctuation">{<!-- --></span>
    Dept d <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Dept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    d<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"Market"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Emp e1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Emp</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    e1<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"csp"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    e1<span class="token punctuation">.</span><span class="token function">setDept</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">;</span>

    Emp e2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Emp</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    e2<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"hzw"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    e2<span class="token punctuation">.</span><span class="token function">setDept</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">;</span>

    d<span class="token punctuation">.</span><span class="token function">setEmps</span><span class="token punctuation">(</span>Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span>e1<span class="token punctuation">,</span> e2<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 输出结果:{"name":"Market","emps":[{"name":"csp"},{"name":"hzw"}]}</span>
    ObjectMapper mapper <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ObjectMapper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 要导入jackson包</span>
    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>mapper<span class="token punctuation">.</span><span class="token function">writeValueAsString</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

/**

  • 员工
    /
    class Emp {
    private String name;
    @JsonIgnore// 忽略该属性:为啥呢?我们来分析一下!
    /
    *

    • 如果我们不忽略掉员工对象中的部门属性
    • System.out.println(mapper.writeValueAsString(d));
    • 会出现下面的结果:
    • {
    • “name”:“Market”,“emps”:
    • [c
    •  {"name":"csp",dept:{name:'xxx',emps:'...'}},
      
    •  ...
      
    • ]
    • }
    • 也就是说,输出结果中,部门对象dept的json串中包含员工对象emp,
    • 而员工对象emp 中又包含dept,这样互相包含就无线递归下去,json串越来越长…
    • 直到栈溢出!
      */
      private Dept dept;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public Dept getDept() {
    return dept;
    }

    public void setDept(Dept dept) {
    this.dept = dept;
    }
    }

/**

  • 部门
    */
    class Dept {
    private String name;
    private List<Emp> emps;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public List<Emp> getEmps() {
    return emps;
    }

    public void setEmps(List<Emp> emps) {
    this.emps = emps;
    }
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
2.5 线程运行诊断

案例1:CPU占用过高

  • Linux环境下运行某些程序的时候,可能导致CPU的占用过高,这时需要定位占用CPU过高的线程

    • top命令,查看是哪个进程占用CPU过高

      在这里插入图片描述
      在这里插入图片描述

    • ps H -eo pid, tid(线程id), %cpu | grep 刚才通过top查到的进程号 通过ps命令进一步查看具体是哪个线程占用CPU过高!

      在这里插入图片描述

    • jstack 进程id 通过查看进程中的线程的nid,刚才通过ps命令看到的tid来对比定位,注意jstack查找出的线程id是16进制的需要转换

      • 可以通过线程id,找到有问题的线程,进一步定位到问题代码的源码行数!

      在这里插入图片描述
      在这里插入图片描述

我们可以看到上图中的thread1 线程一直在运行(runnable)中,说明就是它占用了较高的CPU内存;

在这里插入图片描述
在这里插入图片描述

3、本地方法栈

在这里插入图片描述

一些带有native 关键字的方法就是需要JAVA去调用本地的C或者C++方法,因为JAVA有时候没法直接和操作系统底层交互,所以需要用到本地方法!

如图:
在这里插入图片描述
在这里插入图片描述

  • 本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候是C/C++横行的时候,要想立足,必须由调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies
  • 目前该方法的使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用Web Service等等,不多做介绍
  • 本地方法栈(Native Method Stack):(它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库)
  • native方法的举例: Object类中的clone wait notify hashCode 等 Unsafe类都是native方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值