一、JVM的常见问题
1.1.基本问题
- 介绍下Java内存区域(运行时数据区域)
- Java对象的创建过程(5步)
- 对象的访问定位的两种方式(句柄和直接指针两种方式)
二、运行时数据区域
JDK1.8之前: JDK1.8:
线程私有的:
- 程序计数器
- 虚拟机栈
- 本地方法栈
线程共享的:
- 堆
- 方法区(JDK1.8之前)
- 元空间(JDK1.8)
- 直接内存(非运行时数据区的一部分)
2.1、程序计数器
(1)什么是程序计数器
是一块较小的内存空间,可以看做是当前线程所执行的 字节码行号指示器。字节码解释器 工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
(2)程序计数器作用(2个)
- 字节码解释器 通过改变程序计数器来依次读取指令,从而实现代码的流程控制。如:顺序执行、选择、循环、异常处理。
- 在多线程情况下,用于记录当前线程的执行位置,从而当线程被切换回来的时候能知道该线程上次运行到哪儿了。
注意:程序计数器是唯一一个不会出现OutOfMemoryError (内存溢出)的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
2.2、Java虚拟机栈
(1)什么是虚拟机栈
描述的是Java方法执行的内存模型,每次方法的调用都是通过栈传递的。Java内存可以分为堆内存和栈内存,Java虚拟机栈由一个个栈帧组成,每个栈帧中都有:局部变量表、操作数栈、动态链接、方法出口信息。
(2)局部变量表存放了啥
存放了编译器可知的各种 数据类型(boolean,byte,char,short,int,float,long,double)、对象引用。
(3)Java虚拟机栈会出现两种异常:StackOverFlowError(栈溢出) 和 OutOfMemoryError(内存溢出)
StackOverFlowError:若Java虚拟机栈的内存大小不允许动态扩展,那么当前线程请求栈的深度超过当前Java虚拟机栈的最大深度时候,就会抛出该异常。
OutOfMemoryError:若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展,就会抛出该异常。
(4)方法/函数如何调用?
Java中的栈可类比数据结构中的栈,主要保存的内容是栈帧,每一次函数/方法调用都会有一个对应的栈帧被压入Java栈。每一个函数/方法调用结束后低有一个栈帧被弹出。
Java方法/函数有两种返回方式:
- return
- 抛出异常
不管哪种,都会导致栈帧被弹出
2.3、本地方法栈
(1)什么是本地方法栈 / 作用
和虚拟机栈作用类似。区别是:虚拟机栈为虚拟机执行java方法/函数(也就是字节码)服务,而本地方法栈则为虚拟机使用到的本地方法服务。在HotSpot虚拟机中和Java虚拟机栈合二为一。
本地方法被执行的时候在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。方法执行完后相应栈帧也会出栈并释放内存空间。
也会出现StackOverFlowError(栈溢出) 和 OutOfMemoryError (内存溢出)两种异常。
2.4、堆
(1)什么是堆
Java虚拟机所管理的内存中最大的一块,Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。几乎所有对象实例和数组都在这分配内存。是垃圾收集器主要管理区域,也称GC堆。由于GC一般采用分代垃圾收集算法,所以Java堆还可以分为:新生代、老年代。
(2)堆版本变化
JDK1.8之前:
- 新生代(Young Ceneration)
- 老生代(Old Generation)
- 永生代(Permanent Generation)
JDK1.8:去除了HotSpot 的永久代,取而代之的是元空间,元空间使用的是直接内存
(3)简单了解下分配内存流程
大部分情况下,对象首先在Eden区域分配内存,再一次新生代垃圾回收后,如果对象还存活,则会进入s0或者s1,并且对象额年龄加1(Eden区——>Survivor区后对象的初始年龄变为1),当它的年龄达到默认15岁,就会晋升到老年代中。
这个阀值的理解:
对象晋升到老年代阈值可以通过-XX:MaxTenuringThreshold 来配置。Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,
...待更新