JVM虚拟机(1)- 内存结构

# 一、什么是JVM?

1. 好处

  • 一次编译,到处运行
  • 自动内存管理,垃圾回收功能
  • 数组下标越界检查
  • 多态

2. JVM、JRE、JDK的关系图:

逐级包含的关系

JVM虚拟机(1)- 内存结构_第1张图片

3. 常见JVM实现

不同的公司只要遵循虚拟机规范,都可以自己实现

JVM虚拟机(1)- 内存结构_第2张图片

4. 学习路线

JVM虚拟机(1)- 内存结构_第3张图片

  • ClassLoader:将编译后的二进制文件加载到JVM中运行
  • Method Area:类放在方法区中
  • Heap:存放类的实例对象
  • JVM Stack、PC Register、本地方法栈:在类调用方法时使用
  • 解释器:程序执行时由解释器逐行执行
  • JIT即时编译器:方法中的热点代码被频繁调用后执行优化
  • GC垃圾回收:GC对堆中不用的对象进行回收
  • 本地方法接口:直接调用本地操作系统的方法

二、 内存结构

JVM虚拟机(1)- 内存结构_第4张图片

1. 程序计数器

定义

Program Counter Register 程序计数器(寄存器)

作用

是记录下一条 jvm 指令的执行地址行号

JVM虚拟机(1)- 内存结构_第5张图片

  • 解释器转换代码会机器码,将下一行代码的地址存到计数器当中,执行完之后去计数器中拿取下一条数据
  • 多线程环境,寄存器会记录下一行指令的地址行号,以便切换回来时可以继续执行

特点

  • 线程私有
  • 不会存在内存溢出

2. 虚拟机栈

  • 每个线程运行时所需要的内存,成为虚拟机栈
  • 每个栈由多个栈帧(Frame)组成,对应着每次调用方法时所占用的内存
  • 每个线程只能有一个活动栈帧对应着当前正在执行的方法

JVM虚拟机(1)- 内存结构_第6张图片

问题分析

  1. 垃圾回收是否涉及栈内存?

不会,栈内存是方法调用产生的,方法调用结束后会弹出栈

  1. 栈内存分配越大越好?

不是。物理内存是一定的,栈内存越大,可以支持更多的递归调用,但是可执行的线程数就会越少。因为线程执行时,调用方法需要申请栈帧内存,单个栈帧内存越大,可申请到的栈帧就越少。Xss 1m:用来设置栈内存大小

  1. 方法内的局部变量是否线程安全?
    • 如果方法内局部变量没有逃离方法的作用区,它是线程安全的
    • 如果局部变量引用了对象,并且逃离了方法的作用区,它是线程不安全的

JVM虚拟机(1)- 内存结构_第7张图片

内存溢出

  • 循环递归调用,入栈的栈帧比较多,并且没有收集回收
  • 栈帧过大导致内存溢出

JVM虚拟机(1)- 内存结构_第8张图片

线程运行诊断

案例1:cpu占用过多

定位:

  • top命令:查看是哪个进程占用资源过高

  • ps H -eo pid,tid,%cpu | grep 进程id:定位具体是哪个线程引起的占用cpu过高

  • jstack 进程id (需要将十进程线程号转换成十六进制号)

在这里插入图片描述

JVM虚拟机(1)- 内存结构_第9张图片

JVM虚拟机(1)- 内存结构_第10张图片

JVM虚拟机(1)- 内存结构_第11张图片

案例2:程序运行很长时间都没有得到自己想要的结果(线程死锁)

JVM虚拟机(1)- 内存结构_第12张图片

JVM虚拟机(1)- 内存结构_第13张图片
JVM虚拟机(1)- 内存结构_第14张图片

3. 本地方法栈

不是由Java代码实现的方法,是给本地方法执行的时候提供内存空间

4. 堆

Heap堆

  • 通过new关键字,创建的对象都会使用堆空间

特点

  • 线程共享,堆中的对象都需要考虑线程安全的问题
  • 自动控制的垃圾回收机制

堆内存溢出

a = a + a进行字符串拼接,每次拼接都会生成新的字符串对象,而且arrayList集合一直处于引用范围之内,就导致垃圾回收不了

JVM虚拟机(1)- 内存结构_第15张图片

堆内存诊断

  • 查看当前系统中有哪些java进程:jps
  • 查看堆内存占用情况:jmap -heap pid
  • 图形界面,多功能的监测工具,可以连续监测:jconsole
  • 可视化的虚拟机:jvisualvm

GC之后内存依旧占用很高

JVM虚拟机(1)- 内存结构_第16张图片

查找出最大的对象

JVM虚拟机(1)- 内存结构_第17张图片

可以发现其中最大的属性值是哪一个,可以发现每一个age属性都有1m多,总共创建了999个对象
JVM虚拟机(1)- 内存结构_第18张图片

JVM虚拟机(1)- 内存结构_第19张图片

5. 方法区

5.1 特点

  • 线程共享

  • 存储类的结构相关信息

    • 成员方法
    • 构造器方法
    • 运行时常量池
  • 虚拟器启动时创建,逻辑上是堆的一部分,根据虚拟机厂商来进行实现(hotspot 1.8以前是永久代,1.8之后是元空间,使用的操作系统的内存,不使用jvm来管理内存了)

  • 也会导致内存溢出

JVM虚拟机(1)- 内存结构_第20张图片

5.2 内存溢出

-XX: MaxMetaspaceSize=8m 调整方法区的大小(默认使用的系统内存,很难演示出元空间OOM),我这里只测出了Compressed Class space的异常。

JVM虚拟机(1)- 内存结构_第21张图片
Compressed Class Space OOM 异常,网上搜寻了这么多文档,我自己理解来看,在64位的操作系统中需要开启class对象指针压缩,如果不压缩,有可能导致指针膨胀进而消耗内存,而Compress空间就是用来保存压缩指针后的class对象。而64位操作系统中,其地址总线是64根,所以在计算内存单元的寻址时需要将32位的进行计算成64位的进行操作,因为32位的cpu寻址能力大概为4G,而64位的寻址能力达到了上百万T。所以就需要进行计算。(详细请看我汇编语言中CPU寻址方式)。而且Compressed的空间跟元空间的大小设置应该是一起的,所以就会导致一直是Compressed Class Space OOM。将 Compressed压缩指针选项关掉,就会出MetaSpace OOM的异常了

参考文档:https://cloud.tencent.com/developer/article/1408827
理解不对,请大佬们轻喷。

5.3 使用场景

  • Spring
  • mybatis:CGlib 动态代理

5.4 运行时常量池

javap -v .class 可以查看类的基本信息。

常量池

就是一张表,虚拟机会根据指令去常量表中找到需要执行的类名,方法名称、参数类型、字面量等信息

运行时常量池

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

你可能感兴趣的:(JVM,java,jvm)