jvm之程序计数器(寄存器)、虚拟机栈、本地方法栈、堆、方法区

源代码——>二进制字节码(jvm指令)——>解释器将jvm指令解释成机器码——>机器码交给CPU执行
二进制字节码组成:类的基本信息、常量池、类方法的定义,包含了虚拟机指令

程序计数器:正在执行的字节码指令的地址

有以下特点:
**一、**记录当前线程所执行的jvm指令的行号指示器(程序中断之后能继续上一次执行)

JVM的多线程是通过CPU时间片轮转(即线程轮流切换并分配处理器执行时间)算法来实现的。当某个线程在执行过程中可能会因为时间片耗尽而被挂起,而另一个线程获取到时间片开始执行。被挂起的线程重新获取到时间片的时候,它要想从被挂起的地方继续执行,就必须知道它上次执行到哪个位置,在JVM中,通过程序计数器来记录某个线程的字节码执行位置。

**二、**程序计数器是具备线程隔离的特性,每个线程工作时都有属于自己的独立计数器。

**三、**执行native本地方法时,程序计数器的值为空(Undefined)。因为native方法是java通过JNI直接调用本地C/C++库,可以近似的认为native方法相当于C/C++暴露给java的一个接口,java通过调用这个接口从而调用到C/C++方法。由于该方法是通过C/C++而不是java进行实现。那么自然无法产生相应的字节码,并且C/C++执行时的内存分配是由自己语言决定的,而不是由JVM决定的。

**四、**程序计数器,是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError的区域(即没有内存溢出问题,只是指向一个地址)。

**五 、**程序计数器占用内存很小,在进行JVM内存计算时,可以忽略不计。

**六、**线程私有,随线程创建而创建,随线程销毁而销毁。

虚拟机栈:每个线程运行时所需要的内存
栈:后进先出
虚拟栈的基本元素:栈帧(Stack Frame)

每个栈有多个栈帧组成
栈帧:每个方法运行时需要的内存(存 参数、局部变量、返回地址)
每一个栈帧包含的内容:局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息
在这里插入图片描述
在编译代码时,栈帧需要多大的局部变量表,多深的操作数栈都可以完全确定,并写入到方法表的code属性中。

该线程中,虚拟机有且也只会对当前栈帧进行操作,即每个线程只有一个活动栈帧(即当前执行的这个方法)
每一个方法从调用到方法返回都对应着一个栈帧入栈出栈的过程。
当前栈帧:最顶部的栈帧
当前方法:栈帧所关联的方法
当前类:定义这个方法的类

垃圾回收:回收堆内存,不涉及栈内存
虚拟机栈里的栈帧即对应代码中的一个方法。方法运行的过程,即栈帧入栈出栈的过程。

一个方法执行完,栈帧出栈后,即被销毁。只有入栈出栈这样简单的操作,不需要设计复杂的垃圾回收算法来回收。随着方法的执行,线程的结束正常回收即可。

在递归函数中,该方法还没有结束,就一直不会出栈,如果循环的次数过多,栈空间有被挤爆的可能。会出现StackOverflowError 栈溢出。栈溢出也是内存溢出的一种情况。可通过**-Xss size 设置栈大小**。
在这里插入图片描述
栈内存设置很大,那么可分配的线程数就变少了(物理内存固定情况下)

如果局部变量没有逃离方法的作用域,不用考虑线程安全,每个线程都有自己独立的栈帧,局部变量不会共享;如果局部变量被return了,别的线程可以获取到并修改,此时就可能存在线程安全问题(已经超过了它的方法作用域) eg: StringBuilder被返回,存在线程安全问题,所以一般都使用StringBuffer
static的成员变量可能被多个线程共享,需要考虑线程安全

栈内存溢出StackOverflowError情况:
1.栈帧过多(死递归)
2.栈帧过大(局部变量过大、过多)
经典问题:spring中循环依赖问题(如:json转换案例 a 引用 b , b 引用 a , 无限循环导致StackOverflowError)

cpu占用高问题
排查: 1、top查cpu占用高的PID —— top -Hp pid
2、ps H -eo pid,tid,%cpu | grep PID —— H:进程数 -eo:输出想要的内容,如pid(进程 id),tid(线程id),%cpu(cpu占用)
3、jstack pid
4、找到cpu占用高的tid,将tid(十进制)转成16进制,查找16进制的线程 —— tid(16进制)=nid
5、根据查到的nid可以查看到对应出问题的java行数
死锁时也可以用jstack pid排查一下,进程一直在运行没有得到结果

本地方法栈:给本地方法运行提供内存空间,(具体做法:本地方法栈登记native方法,在Execution Engine 执行时加载本地方法库。如:Object类中的clone()方法等,带有native关键字,一般用c/c++编写的)---------生命周期:与线程一致,属线程私有。

堆(heap):通过new 关键字,创建对象都会使用 堆内存
堆是线程共享的,堆中对象都要考虑线程安全问题,有垃圾回收机制(堆中不在被引用的对象被释放)

堆内存溢出OutOfMemoryError:创建对象太多(如:在循环中字符串string拼接很容易就堆溢出了)
-Xmx Size设置堆内存大小(eg: -Xmx8m)

堆内存诊断:jps命令(查看当前有哪些进程),jmap工具(某时刻查看堆内存占用情况),jconsole工具(查看连续时间检测)
查看:1.jps
2.jmap -heap pid (查看Eden space 和Old Generation的使用used,总和就是占用的堆内存)
3.可在项目命令窗口敲 jconsole
4.如果看到老年代还是占用,再使用 jvisualvm 查看——>监视——>点击 堆Dump——>右侧检查中 查找最大对象 《查找》——>点击占用最大的类

方法区:共享区域,存储类相关结构(方法、构造器、成员变量等代码),虚拟机启动时被创建,逻辑上是堆的一部分,1.8以后用元空间(操作系统的内存)存储,1.8以前堆存储(永久代)
方法区也可能导致OutOfMemoryError

1.6及1.8方法区内存结构

在这里插入图片描述
方法区溢出:元空间溢出 -XX:MaxMetaSpaceSize=8m
场景:spring mybatis中的动态代理,动态生成字节码文件

(Constant pool)常量池:java字节码中所带信息,就是一张信息表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息(#1:用#号和数字表示符号地址)
字面量:基础类型,如字符串、整型、布尔类型等统称为字面量

运行时常量池:常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

运行时常量池中的StringTable:懒加载形式,当代码运行到那一条代码才会将常量加到String Table中(如:运行到String a=“a”;时,把a符号变为 "a"字符串对象,然后加到StringTable——串池)
StringTable结构:hashTable,不能扩容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值