JVM学习笔记
1、什么是JVM
JVM,即 Java Virtual Machine,Java 虚拟机。jvm是抽象化的计算机,包含了指令集,寄存器,堆,栈,方法区,垃圾回收,jvm负责将Java字节码文件翻译成不同操作平台的机器码并执行。
Java语言的跨平台并不是指Java语言本身跨平台,跨平台就是在不同操作平台安装不同的jvm,而字节码文件都是一样的,这是因为jvm屏蔽所有有关操作平台的信息,字节码文件翻译成机器码这个过程由jvm自主自动完成,我们只需要写源代码,再交由Javac编译成字节码文件即可。
2、JVM体系结构
3、类加载器
类加载器
作用:把.class文件加载成Class 对象
java类加载器种类:
-
启动类加载器(Bootstrap ClassLoader):负载加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
-
扩展类加载器(Extension ClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包;
-
应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的类
-
自定义类加载器:负责加载用户自定义路径下的类包
-
双亲委派机制
加载某个类时,会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有的父加载器在自己的类路径下找不到目标,则在自己的类加载路径中查找并载入目标。
为什么要设计双亲委派机制?
沙箱安全机制:自己写的String.class类不会被加载,这样可以防止核心api被随意更改。
避免类的重复加载:当父加载器以及加载的该类,就没必要子加载器再加载一次,保证被加载类的唯一性。
类加载过程
类加载:类加载器将class文件加载到虚拟机内存中
- 加载:在硬盘上查找并通过IO读入字节码文件
- 连接:执行校验、准备、解析步骤
- 校验:校验字节码的文件的正确性(-Xverifynone)
- 准备:给类的静态变量分配内存,并赋予默认值
- 解析:将符号引用替换为直接引用,该阶段会把一些静态方法替换成指向数据所存内存的指针或句柄(直接引用),这是所谓的静态连接过程(类加载期间完成),动态连接时程序运行期间完成的符合应用替换为直接引用
- 初始化:对类的静态变量初始化为指定的值,执行静态代码块
4、运行时数据区
4.1、 程序计数器(PC寄存器)
- 用来存储下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。
- 它是一块很小的内存空间,几乎可以忽略不记。也是运行最快的存储区域。
- 在JVM规范中,每个线程都有它自己的程序计数器,他是线程私有的,生命周期与线程的生命周期保持一致。
- 任何一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址;正在执行的是native方法,这个计数器的值为undefined。
- 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。
- 此区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
面试题:
1、使用PC寄存器存储字节码指令地址有什么用?为什么使用PC寄存器记录当前线程的执行地址? - 因为CPU需要不停的切换各个线程,当切换到一个线程时,就能知道该线程从那个地方开始继续执行。
2、PC寄存器为什么会被设定为线程私有? - 多线程在执行时,cpu会不停的切换任务,这样必然会导致线程的中断或者恢复。为了能够准确的记录各个线程正在执行的当前字节码指令地址,最好的办法就时每个线程都分配一个PC寄存器,这样各个线程之间便会进行独立计算,不会互相干扰。
4.2、栈
Java虚拟机栈(Java virtual Machine Stack),早期也叫java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的java方法调用。栈是线程私有的。
- 生命周期和线程一致。
- 作用:主管jiava程序的运行,它保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回。
栈的优点
- 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。
- JVM直接对java栈的操作只有两个:1、每个方法执行,伴随仅栈(入栈、压栈);
2、执行结束的出栈工作 - 对与栈来说不存在来及回收问题。
栈参数设置
- 设置栈内存大小
使用参数**-Xss**选选项来设置线程的最大栈空间大小,栈的大小直接决定函数调用的最大可达深度。
栈中的存储栈帧
- 每一个线程都有自己的栈,栈中的数据都是以栈帧(Stack Frame)的格式存在的。
- 在这个线程上正在执行的每一个方法都各自对应一个栈帧。
- 栈帧是一个内存区块,是一个数据集,维系者方法执行过程的各种数据信息。
- JVM直接对java栈的操作只有两个,就是对栈帧的压栈和出栈,遵循“先进后出”原则。
- 在一条活动线程中,一个时间点,只会有一个活动的栈帧。即只有当前正在执行的方法(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧,与当前栈帧对应的方法是当前方法,定义这个方法的类为当前类。
- 执行引擎运行的所有字节码指令只针对当前栈帧进行操作。
- 如果在该方法中调用的其他方法,对应的新栈帧会被创建出来,放在栈的顶端,称为新的当前栈帧。
- 不同的线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧之中引用另外一个线程的栈帧。
栈帧的内部结构
- 局部变量表(Local Variables)
- 局部变量表也被称为局部变量数组或本地变量表
- 定义一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、引用对象,以及retuanAddress类型。
- 由于局部变量表建立早线程的栈上,是线程的私有数据,因此不存在数据安全问题
- 局部变量表所需要的容量大小是编译期确定下来的,并保存在方法的code属性的maximun local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。
- 在栈帧中,与性能优化最为密切的部分就是局部变量表。在执行方法时,虚拟机使用局部变量表完成方法的传递。
- 在局部变量表中的变量也是最重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象时不会被回收的。
代码
- 操作数栈(OperandStack)
- 每一个独立的栈帧中除了包含局部变量表以外,还包含了一个先进后出的操作数栈,也可以称为表达式栈。
- 操作数栈可理解为java虚拟机栈中的一个用于计算的临时数据存储区。
- 操作数栈,在方法的执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)/出栈(pop)
- 动态链接(Dynamic Linking)(或指向运行时常量池的方法引用)
- 每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)。比如:invokedynamic指令。
- 方法返回地址(Return Address)(或指方法正常退出或者异常退出的定义)
- 存放的是该方法的pc寄存器的值
- 方法执行后会退出,在退出后都会返回到该方法被调用的位置。该方法正常退出时,调用者的pc计数器的值就会作为返回地址,即调用该方法的指令的下一条指令的地址。
- 一些附加信息
面试题
1、栈可能会出现的异常
- Java虚拟机规范java栈的大小是动态的或者固定不变的。
如果采用固定大小的java虚拟机栈,那每一个线程的java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过java虚拟机允许的最大容量,java虚拟机将会抛出一个** 异常。
如果java虚拟机栈可以动态扩展,并尝试扩展的时候无法申请到足够的内存。或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈。那java虚拟机将会抛出一个OutOfMemoryError**异常。
4.2 本地方法栈
什么是本地方法?
简单地讲,一个Native Method 就是一个java调用非Java代码的接口。使用本地方法是为了与java环境外交互。
- java虚拟机栈用于管理java方法的调用,而本地方法栈用于管理本地方法的调用。
- 本地方法栈是线程私有的。
- 允许被实现成固定或者是可动态扩展的内存大小。
如果线程请求分配的栈容量超过本地方法栈允许的最大容量,java虚拟机将会抛出一个StackOverFlowError异常。
如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存区创建对应的本地方法栈,那么java虚拟机将会抛出一个OutOfMemoryError异常。
- 本地方法是使用c语言实现的。
- 它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。
4.3 堆(Heap)
堆的核心概念
- 一个JVM实例只有一个堆内存,堆是内存管理的核心区域。
- java堆区在JVM启动时被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间。
- 所有的线程共享java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer, TLAB).
- 《Java虚拟机规范》中对java堆的描述是:所有的对象实例以及数组都应该在运行是分配在堆上。(视频中提到:”几乎“所有对象实例都在这里分配内存,有可能在栈上分配)。
- 数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或数组在堆中的位置。
- 方法结束后,堆中的对象不会被马上移除,仅仅在垃圾回收的时候才会被移除。
堆内存细分
设置堆空间内存的大小(年轻代、老年代 )
- -Xms:用于表示堆区的起始内存,等价与 -XX:InitialHeapSize,默认值是物理内存的1/64
- -Xmx:用于表示堆区的最大内存,等价与 -XX:MaxHeapSize,默认值是物理内存的1/4
一旦堆区的内存大小超过所指定的最大值,将会抛出OUtOfMemoryError异常。
通常会将-Xms、-Xmx 设置相同值,其目的是为了能够在java垃圾回收后,不需要重新分隔计算堆区大小,从而提供性能。
查看设置内存方式
方式1:通过jps命令查看
方式二:通过加参数 XX:+PrintGCDetail
年轻代、老年代
存储在jvm的java对象可以划分为两类:
- 一类是生命周期较短的瞬时对象,这类对象创建和消亡非常迅速
- 另一类对象是生命周期非常长的对象,某些极端的情况下还能够与Jvm的生命周期保持一致。
java堆区进一步细分的化,可以划分为年轻代(YoungGen)和老年代(OldGen)。
其中年轻代有可以划分为Eden空间、Sourvior0空间和Survivor1空间(有时也叫做from区、to区)。
对象分配一般过程
关于垃圾回收:频繁收集新生区,很少收集老年区,几乎不收集永久区/元空间
Minor GC、Major GC、Full GC
TLAB
堆空间常用参数设置
堆是分配对象的唯一选择吗 ?
4.4方法区
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。 虽然《Java虚拟机规范》 中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区分开来。
设置方法区大小
方法区的内部结构
运行时常量池
方法区的演进细节
方法区的垃圾回收
5、对象的创建
对象内存布局
6、直接内存
7、执行引擎(Execution Engine)
8、String
9、GC
什么是垃圾
垃圾标记阶段算法
引用计数算法
可达性分析算法
对象的finalization机制
垃圾清除阶段
标记-清除(Mark-Sweep)算法
复制(Copy)算法
标记-压缩(标记-整理、Mark-Compect)算法
三种算法的对比
分代算法
增量收集算法
分区算法
垃圾收集相关概念概述
System.gc()
内存溢出
内存泄露
Stop The World
并发与并行
安全点、安全区域
引用
强引用(Strong Reference)
软引用
弱引用
虚引用
10、垃圾回收器
垃圾回收器的分类
按线程数分类
按工作模式分类
按碎片处理方式分类
评估GC性能指标
垃圾回收器发展史
7种经典垃圾回收器
Serial回收器:串行回收
ParNew回收器:并行回收
Parallel Scavenge回收器:吞吐量优先
CMS回收器:低延迟
cms工作原理
G1回收器:区域化分代式
G1优势
G1参数设置
G1使用场景
在这里插入图片描述](https://img-blog.csdnimg.cn/20200624164024278.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwNjc5ODAw,size_16,color_FFFFFF,t_70)
分区Region:化整为零
G1垃圾回收过程
年轻代GC
并发标记过程
混合回收
Full GC
垃圾回收器总结
日志分析