JVM (Java Virtual Machine)
2022-03-03 18:09:44 0 举报
AI智能生成
jvm相关的知识和重点都有标注出来,以后的面试重点也会同步更新上去,花费了我很多精力和时间去整理这个资料,喜欢的可以下载下来
作者其他创作
大纲/内容
就是一台虚拟的计算机,是一款软件,用来执行一系列虚拟计算机指令,虚拟机分为程序虚拟机和系统虚拟机
含义
Visual Box、Vmware 就是系统虚拟机,他们完全是对物理计算机的仿真。提供了一个可运行完整操作系统的软件平台
系统虚拟机
典型的就是Java虚拟机,专门为执行单个计算机程序而设定,在java虚拟机中执行指令被我们成为java字节码指令
程序虚拟机
1、java虚拟机是一台执行java字节码的虚拟计算机,拥有独立的运行机制,其运行的java字节码也未必由java语言编译而成
2、JVM平台的各种语言可以共享java虚拟机带来的跨平台性,优秀的垃圾回收器以及可靠的及时编译器
3、java的核心技术就是java虚拟机(Java Virtual Machine)所有的java程序都是运行在java虚拟机内部
java虚拟机含义
Java虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释和编译为对应平台上机器指令,每一条java指令,jvm规范中都有详细的定义(怎么操作取数、怎么处理操作数,处理结果)
java虚拟机的作用
一次编译处处运行
自动内存管理
自动内存回收
java虚拟机的特点
虚拟机(Virtual Machine)
设计和实现更简单,适用于资源受限的系统
避开了寄存器的分配难题,使用零地址指令方式分配
指令流中的指令大部分都是零地址指令,其执行的过程中依赖于操作栈,指令集更小(8位),编译器容易实现
不需要硬件支持,可移植性更好,更好的实现跨平台
基于栈式架构
典型的应用是x86的二进制指令集,比如传统pc以及安卓的Davlik虚拟机
指令集架构完全依赖硬件,可移植性差
性能优秀和执行高效
花费更少的指令(16位)执行一项操作
在大部分情况下,基于寄存器架构的指令集往往都是以1地址指令,2地址指令,3地址指令为主,而基于栈式架构的指令集却是以零地址为主
基于寄存器架构
JVM架构模型
java虚拟机的启动是通过引导类加载器(Bootstrap class loader)创建一个初始类(init class)来完成,这个类是由虚拟机的具体实现指定
虚拟机启动
一个运行中的java虚拟机有着一个清晰的任务,执行java程序
程序开始执行时他才运行,程序结束时他就停止
执行一个所谓的java程序时,真正在执行的是一个叫Java虚拟机的进程
虚拟机执行
程序正常执行结束
程序在执行过程中遇到了异常或错误而异常终止
由于操作系统出现错误而导致Java虚拟机进程终止
某线程调用RunTime 类或者System类中exit方法,或者RunTime 类的halt方法,并且Java安全管理器也允许这次exit或者halt操作
除此之外,JNI(Java Native Interface)规范描述了JNI Invocation API 来加载或者卸载java虚拟机时,虚拟机退出情况
虚拟机退出
JVM生命周期
oracle 的HotSpot虚拟机
oracle 的JRockit
IBM公司J9
VM虚拟机
JVM基础信息
概念
作用
自动内存管理缺点
标记:遍历整个内存区域,对需要回收的对象打上标记
清除:再次遍历内存,对标记过的内存进行回收。
步骤
只有小部分对象需要进行回收的,适用于老年代的垃圾回收,因为老年代一般存活对象会比回收对象要多。
适用场景
简单、收集速度快,但会有空间碎片,空间碎片会导致后面的GC频率增加。
优点
效率问题;遍历了两次内存空间(第一次标记,第二次清除)。
空间问题:容易产生大量内存碎片,当再需要一块比较大的内存时,虽然总的可用内存是够的,但是由于太过分散,无法找到一块连续的且满足分配要求的,因而不得不再次触发一次GC。
缺点
标记–清除算法
将内存划分为等大的两块,每次只使用其中的一块。当一块用完了,触发GC时,将该块中存活的对象复制到另一块区域,然后一次性清理掉这块没有用的内存。下次触发GC时将那块中存活的的又复制到这块,然后抹掉那块,循环往复。
过程
只有少量对象存活的场景,这也正是新生代对象的特点,所以一般新生代的垃圾回收器基本都会选择标记复制法。
收集速度快,可以避免空间碎片,但是有空间浪费,存活对象较多的情况下复制对象的过程等会非常耗时,而且需要担保机制。
1、会浪费一部分空间:通过上面的图我们也不难发现,总是会有一块空闲的内存区域是利用不到的,这也造成了资源的浪费。
2、存活对象多会非常耗时:因为复制移动对象的过程是比较耗时的,这个适合不仅需要移动对象本身还需要修改使用了这些对象的引用地址,所以当存活对象多的场景会非常耗时,这也提示我们标记复制法比较适合存活对象较少的场景。
3、需要担保机制:因为复制区总会有一块空间的浪费,而为了减少浪费空间太多,所以我们会把复制区的空间分配控制在很小的区间,但是空间太小又会产生一个问题,就是在存活的对象比较多的时候,这时复制区的空间可能不够容纳这些对象,这时就需要借一些空间来保证容纳这些对象,这种从其他地方借内存的方式我们称它为担保机制。
复制算法
标记:对需要回收的进行标记
整理:让存活的对象,向内存的一端移动,然后直接清理掉没有用的内存。
内存吃紧,又要避免空间碎片的场景,老年代想要避免空间碎片问题的话通常会使用标记整理法。
相对于标记复制法不会浪费内存空间,相对标记清除法则可以避免空间碎片,但是速度比其他两个算法慢。
标记整理法是三种垃圾回收算法中性能最低的一种,因为标记整理法在移动对象的时候不仅需要移动对象,还要额外的维护对象的引用的地址,这个过程可能要对内存经过几次的扫描定位才能完成,做的事情越多那么必然小号的时间也越多。
标记–整理算法
分代收集算法
垃圾回收算法
GC类型
Minor GC工作原理
Full GC工作原理
内存模型
GC 日志开启
理解GC日志
JVM常用参数
GC日志
Serial 收集器
ParNew收集器
Parallel Scavenge收集器
Serial Old收集器
Parallel Old收集器
CMS收集器
Garbage First收集器
垃圾收集器
垃圾回收
<JVM规范> 中对java 堆的描述是: 所有的对象实例以及数组都应当在运行时分配在堆上
堆是GC 执行垃圾回收的重点区域
简介
几乎所有的Java 对象都在Eden中被new 创建出来的
可以使用选项 -Xmn 设置新生代最大内存大小
年轻代( YongGen)和老年代 (OldGen)
总结
对象分配过程
年轻代GC(Young GC/Minor GC) 触发机制
老年代GC(Major GC /Full GC) 触发机制
Full GC 触发机制
为什么需要把Java 堆分代?不分代就不能正常工作?
为什么需要把Java 堆分代? 不分代就不能正常工作了?
堆空间分代思想
内存分配策略 ( 或 对象提升(Promotion)规则)
定义
意义
说明
为对象分配内存TLAB(Thread Local Allocation Buffer)
-XX:+PrintFlagsInitial : 查看所有的参数默认值大小
-XX:+PrintFlagsFinal : 查看所有参数的最终值
-Xms : 堆初始默认值大小(默认物理内存的1/64)
-Xmx: 堆最大空间大小(默认物理内存1/4)
-Xmn: 设置新生代的大小
-XX:NewRadio: 配置新生代与老年代在堆结构的占比
-XX:SurvivoRatio: 设置新生代中 Eden 与 S0/S1空间的比例
-XX:MaxTenuringThreshold : 设置新生代垃圾的最大年龄
-XX:+PrintGCDetails : 输出详细的GC处理日志
-XX: HandlePromotionFailure : 是否设置空间内存担保
堆参数设置
在<深入理解java虚拟机>关于java 堆内存有这样一段描述:
堆是分配对象存储的唯一选择?
这是一种可以有效减少java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法
逃逸分析的基本行为就是分析对象动态作用域
概述
参数设置
代码优化
逃逸分析
Java堆(Heap)
背景
与线程一致
生命周期
执行结束后的出栈工作
JVM 直接对Java 栈的操作只有两个
对于栈来说不存在垃圾回收问题
outofmemoryError(内存溢出)
stackOverflowError(栈溢出)
常见异常
在这个线程上正在执行的每个方法都各自对应一个栈帧
基本介绍
执行引擎运行的所有字节码指令只针对在当前栈帧进行操作
运行原理
局部变量表也被称为局部变量数组或本地变量表
Slot 的理解
Slot 重复利用问题
静态变量与局部变量的对比
补充
局部变量表 (Local Variables)
栈中的任何一个元素都是可以任意的Java 数据类型
常见的i++和++i区别
操作数栈 (Operand Stack)(或表达式栈)
栈顶缓存(Top of Stack Cashing) 技术
为什么需要常量池呢?
动态链接 (或指定运行时常量池的方法引用)
静态链接
动态链接
早期绑定 (Early Binding)
晚期绑定 ( Late Binding)
方法调用
虚方法
其它方法称之为 虚方法
非虚方法
虚拟机提供了一下几条方法调用指令
静态类型语言(Java)与动态类型语言区别
Java 语言中方法重写的本质
虚方法表
存放调用该方法的pc 寄存器的值
方法返回地址 (方法正常退出或者异常退出的定义)
一些附加信息
通过 -Xss=255k 设置栈大小
举例栈溢出的情况?(StackOverFlowError)
分配的栈内存越大越好?
垃圾回收是否涉及到虚拟机栈?
方法中定义的局部变量表是否线程安全?
面试题
栈帧的内部架构
栈的存储单位
虚拟机栈(JVM Stacks)
位置(在哪里)
HotSpot 方法区
设置方法区/元空间 内存大小
如何解决这些oom?
为什么需要常量池?
常量池里面有什么?
运行时常量池 是方法区的一部分
运行时常量池
方法区演进细节
为永久代设置空间大小是很难确定的
对永久代进行调优非常困难
永久代为什么要被元空间替换?
StringTable 为什么要调整?
方法区的垃圾收集主要回收两部分内容: 常量池中废弃的常量和不再使用的类型
方法区内常量池主要存放的两大类常量 : 字面量和符号引用
回收废弃常量与回收java 堆中的对象非常类似
方法区的垃圾收集
方法区(Method Area) 存储一些什么?
方法区(Method Area)
现状
本地方法栈(Native Method Stacks)
字节码解释器在工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
它是唯一一个在Java 虚拟机规范中没有规定任何 outofMemoryError 情况的区域
介绍
CPU时间片
JVM 的字节码解释器就需要通过改变pc 寄存器的值来明确下一条应该执行什么样的字节码指令
使用pc 寄存器存储字节码指令地址有什么用呢?
pc 寄存器为什么会被设定为线程私有的?
常见问题
程序计数器(Program Counter Register)
Java7之前的内存逻辑上区分为 新生代+老年代+永久区
Java 8之后的内存逻辑上区分为 新生代+老年代+元空间
Java 7 与Java 8堆的区别
默认情况下 初始内存大小 : 物理电脑内存大小的/64
如何设置堆内存大小
jps 查看程序运行端口
jstat -gc 端口
-XX: +PrintGCDetails
如何查看使用gc情况
整堆收集(Full GC): 收集整个java堆和方法区的垃圾收集器
堆
常见面试问题
运行时数据区域
这个类加载使用C/C++语言实现,嵌套在JVM内部
它是用来加载java的核心内库(rt.jar,resource.jar或者sun.boot.class.path)路径下面的内容,提供JVM自身需要的类
并不继承与java.lang.ClassLoader没有父类加载器
加载扩展类加载器和应用程序加载器并指定他们的父类加载器
启动类加载器(Bootstrap ClassLoader)
java 语言编写,有Launcher的内部类
派生于ClassLoader类
父类加载器启动为启动类加载器
从java.ext.dirs系统属性所指定的目录中加载类库或从JDK的安装目录的jre/lib/ext子目录(拓展目录)下,如果用户创建的jar放在此目录,也会由拓展类加载器加载
拓展类加载器(Extension ClassLoader)
java语言编写,有Launcher的内部类
父类加载器启动为拓展类加载器
它负责加载环境classpath或系统属性,java.class.path指定路径下的类库
该类加载时程序中默认的类加载器,一般来说,java应用类都是由它来完成
通过ClassLoader#getSystemClassLoader()方法可以直接获取该类加载器
应用程序加载器(Application ClassLoader)
日常开发中,类加载器几乎由上述3种类加载器互相配合执行,必要时也可以自定义类加载器来定制类的加载方式
隔离加载类
修改类加载的方式
扩展加载源
防止源码泄露
什么情况需要自定义类加载器
用户自定义类加载器(User Defined ClassLoader)
JVM虚拟机自带的加载器(从上至下依次加载)
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器(Bootstrap ClassLoader)中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类
(向下委派)避免类的重复加载,保障所有类都被加载
(向下委派)保护程序安全,防止核心API被恶意篡改
双亲委派模式优势
沙箱安全机制
双亲委派机制
类的完整类名必须一致,包括包名
加载这两个类的ClassLoader必须相同
在JVM中表示两个class对象是否为同一个类存在的两个必要条件
总之就是这两个类对象必须来源同一个class文件,被同一个虚拟机所加载,只要加载他们的classLoader不同,那么这两个类对象也是不相同的
创建类的实例
创建某个类或接口的静态变量或者对该静态变量赋值
调用类的静态方法
反射
初始化一个类的子类
Java虚拟机启动时被标记为启动类的类
JDK1.7开始提供的动态语言支持
类的主动使用
除了主动使用外,其他都是被动使用,都不会导致类的初始化
类的被动使用
类的主动使用和被动使用
类的加载过程
jps
jstat
jmap
jhat
jstack
JVM常用命令
jconsloe
VisualVM
图形化工具
通过jstatd启动RMI
配置JMX管理Tomcat
远程机器视图
远程机器配置JVM可视化图
性能检测工具
选择合适的垃圾回收器
调整内存大小
设置符合预期的停顿时间
调整大对象的标准
常见调优策略
JVM调优实例
JVM(Java Virtual Machine)
收藏
0 条评论
回复 删除
下一页