一、
JVM由三部分组成:类加载子系统、执行引擎、运行时数据区。
1. 类加载子系统,可以根据指定的全限定名来载入类或接口。
2. 执行引擎,负责执行那些包含在被载入类的方法中的指令。
3. 当程序运行时,JVM需要内存来存储许多内容,例如:字节码、对象、参数、返回值、局部变量、运算的中间结果,等等,JVM会把这些东西都存储到运行时数据区中,以便于管理。而运行时数据区又可以分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。
加分回答
运行时数据区是开发者重点要关注的部分,因为程序的运行与它密不可分,很多错误的排查也需要基于对运行时数据区的理解。在运行时数据区所包含的几块内存空间中,方法区和堆是线程之间共享的内存区域,而虚拟机栈、本地方法栈、程序计数器则是线程私有的区域,就是说每个线程都有自己的这个区域。
二、
JVM内存结构
类→方法区
实例对象→堆
调用的方法→虚拟机栈、程序计数器、本地方法栈
三、
1、程序计数器(Program Counter Register)(寄存器)
作用:是记住下一条jvm指令的执行地址
特点:是线程私有的,唯一一个不会存在内存溢出的区
2、栈
作用:线程运行时需要的内存空间
由多个栈帧组成(栈帧:每个方法运行时需要的内存)(参数、局部变量、返回地址)
每个线程只有一个活动栈帧,对应着当前正在执行的那个方法
(2)方法的局部变量是否线程安全?
如果方法内局部变量没有逃离方法的作用访问,他是线程安全的
如果局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
(3)栈内存溢出
栈帧过大导致栈内存溢出
栈帧过多,导致栈内存溢出
(方法递归的调用)
线程运行诊断:定位,用 top 定位哪个进程对cpu占用过高
cpu占用过多:用 ps 命令定位哪个线程对cpu占用过高,JDK提供的工具看哪一个线程:jstack进程id(可以根据线程id找到有问题的线程,进一步定位到有问题代码的源码行号)
java程序运行很长时间无结果:发生了死锁
(4)通过native等去调用本地方法的接口:本地方法栈:为本地方法的运行提供一个空间(本地方法的接口:c语言书写的底层)
3、堆(Heap)
(1)堆和方法区是线程共享的,需要考虑线程安全的问题
(2)有垃圾回收机制
(3)堆内存溢出:不断产生对象,并不断使用着这些对象
OutofMemoryError:java heap space (报错文)
(4)堆内存诊断
jps工具:查看当前系统中有哪些java进程
jmap工具:查看堆内存占用情况
jconsole工具:图形界面的,多功能的检测工具,可以连续监测
(5)内存回收后:内存占用率依然很高?
Terminal: jps: jmap-heap 17748
Edenspace: 内存区的占用(新生代) old generation 老年代
jconsole:连接到进程,执行GC
可能存在一些编程失误,导致一些对象始终被调用而无法释放内存
可用 jvitualvm 可视化展示虚拟机内容
4、方法区
(1)与类的结构相关的一些信息(成员变量、方法数据、成员方法、构造器方法、特殊方法:类的构造器 运行时常量池)
(2)在虚拟机启动时被创建,逻辑上是堆的组成部分
(3)方法区内存溢出:
类加载器:可以用来加载类的二进制字节码(二进制字节码 (类的基本信息、常量池、类方法定义) 包含了虚拟机的指令)
1.8以前是永久代(permGenspace)空间导致的方法区内存溢出,1.8之后是(metaspace)元空间导致的
4、运行时常量池
常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
运行时常量池,常量池是*.class文件中的当该类被加载,他的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址