jvm
2021-03-06 22:16:20 165 举报
AI智能生成
JVM(Java虚拟机)是运行所有Java程序的抽象计算机。它是Java技术的核心,负责执行Java字节码,使Java程序能够跨平台运行。JVM在执行Java程序时,将字节码翻译成特定平台的机器语言,从而实现了Java程序的一次编写,到处运行的目标。此外,JVM还提供了内存管理、垃圾回收等服务,确保了Java程序的稳定性和高效性。
作者其他创作
大纲/内容
垃圾回收
垃圾回收器
Serial
单线程的收集器
在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(STW)
Serial 收集器对于运行在 Client 模式下的虚拟机来说是一个很好的选择
ParNew
Serial 收集器的多线程版本
许多运行在 Server 模式下的虚拟机中首选的新生代收集器
Parallel Scavenge
Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量
提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的
-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的
-XX:GCTimeRatio参数
-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的
-XX:GCTimeRatio参数
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
Serial Old
是一个单线程收集器,使用“标记-整理”算法
Parallel Old
Parallel Scavenge 收集器的老年代版本
同样吞吐量优先
CMS
基于“标记—清除”算法实现
初始标记
并发标记
重新标记
并发清除
缺点
导致吞吐量降低
CMS 收集器无法处理浮动垃圾
产生空间碎片
G1
G1 特点
并行与并发
分代收集
空间整合
可预测的停顿
运作步骤
初始标记
并发标记
最终标记
选回收
ZGC
垃圾回收算法
标记清除
分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,
在标记完成后统一回收所有被标记的对象。
在标记完成后统一回收所有被标记的对象。
问题
效率问题,标记和清除两个过程的效率都不高
空间问题,标记清除之后会产生大量不连续的内存碎片
标记整理
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,
而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
适用于老年代
复制
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。
当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,
然后再把已使用过的内存空间一次清理掉
当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,
然后再把已使用过的内存空间一次清理掉
代价是将内存缩小为了原来的一半
适用于年轻代
分代
新生代
复制算法
老年代
标记整理或者标记清除
GC日志分析
回收判断
可达性分析
通过一系列的称为 "GC Roots" 的对象作为起始点,从这些节点开始向下搜索,
搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots
没有任何引用链相连时,则证明此对象是不可用的
搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots
没有任何引用链相连时,则证明此对象是不可用的
可作为 GC Roots 的对象
a. 虚拟机栈(栈帧中的本地变量表)中引用的对象
b. 方法区中类静态属性引用的对象。
c. 方法区中常量引用的对象。
d. 本地方法栈中 JNI(Native方法)引用的对象
e. 强引用 Object obj = new Object
可达性分析算法
1. 如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,
那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有
必要执行 finalize() 方法
那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有
必要执行 finalize() 方法
2. 当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,
虚拟机将这两种情况都视为“没有必要执行”,直接进行第二次标记。
虚拟机将这两种情况都视为“没有必要执行”,直接进行第二次标记。
3. 如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象将会放置在
一个叫做 F-Queue 的队列之中,并在稍后由一个由虚拟机自动建立的、低优
先级的 Finalizer 线程去执行它
一个叫做 F-Queue 的队列之中,并在稍后由一个由虚拟机自动建立的、低优
先级的 Finalizer 线程去执行它
引用计数法
给对象添加一引用计数器,被引用一次计数器值就加 1;当引用失效时,
计数器值就减 1;计数器为 0 时,对象就是不可能再被使用的
计数器值就减 1;计数器为 0 时,对象就是不可能再被使用的
缺点是无法解决对象之间相互循环引用的问题
jvm执行子系统
类的加载机制
类的生命周期
加载
连接
验证
准备
解析
初始化
使用
卸载
类加载器
根类加载器
扩展类加载器
系统类加载器
加载过程
1. 检测此Class是否载入过,即在缓冲区中是否有此Class,如果有直接进入第8步,否则进入第2步。
2. 如果没有父类加载器,则要么Parent是根类加载器,要么本身就是根类加载器,则跳到第4步,如果父类加载器存在,则进入第3步。
3. 请求使用父类加载器去载入目标类,如果载入成功则跳至第8步,否则接着执行第5步。
4. 请求使用根类加载器去载入目标类,如果载入成功则跳至第8步,否则跳至第7步。
5. 当前类加载器尝试寻找Class文件,如果找到则执行第6步,如果找不到则执行第7步。
6. 从文件中载入Class,成功后跳至第8步。
7. 抛出ClassNotFountException异常。
8. 返回对应的java.lang.Class对象。
类加载机制
全盘负责
双亲委派
1. 采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有
优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加
载了该类时,就没有必要子ClassLoader再加载一次。
优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加
载了该类时,就没有必要子ClassLoader再加载一次。
2. 考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个
名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器
在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过
来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API
库被随意篡改。
名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器
在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过
来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API
库被随意篡改。
缓存机制
执行引擎
常用工具
jps:JVM Process Tool:显示指定系统内所有的虚拟机进程
jstat:JVM Statistics Monitoring Tool 收集HotSpot虚拟机各方面的运行数据
jinfo:Configuration Info for Java 显示虚拟机配置信息
jmap:Memory Map for java 内存转储快照
jhat:JVM Heap Dump Browser 用于分析heapdump文件,建立一个HTTP/HTML服务器让用户浏览
jstack:Stack Trace for Java 显示虚拟机的线程快照
内存区域
jdk1.6,1.7,1.8对比
详细内存区域
本地方法栈(私有)
与虚拟机栈所发挥的作用非常相似,为虚拟机使用到的 Native 方法服务
如:lang包下Sysytem类中的currentTimeMillis,Runtime类中的获取硬件
相关信息的方法
如:lang包下Sysytem类中的currentTimeMillis,Runtime类中的获取硬件
相关信息的方法
栈(私有)
局部变量表
局部变量表是存放方法参数和局部变量的区域
操作栈
操作栈是个初始状态为空的桶式结构栈
动态链接
每个栈帧中包含一个在常量池中对当前方法的引用,
目的是支持方法调用过程的动态连接
目的是支持方法调用过程的动态连接
方法返回地址
方法执行时有两种退出情况:正常退出,即正常执行
到任何方法的返回字节码指令,如 RETURN、IRETURN、
ARETURN 等;异常退出。
到任何方法的返回字节码指令,如 RETURN、IRETURN、
ARETURN 等;异常退出。
堆(共享)
此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存
方法区(共享)
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
运行时常量池(Runtime Constant Pool)是方法区的一部分
运行时常量池(Runtime Constant Pool)是方法区的一部分
程序计数器(私有)
是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器
jvm堆参数设置
-Xms 初始堆大小
-Xmx 最大堆空间
-Xmn 设置新生代大小
-XX:SurvivorRatio 设置新生代eden空间和from/to空间的比例关系
-XX:PermSize 方法区初始大小
-XX:MaxPermSize 方法区最大大小
-XX:MetaspaceSize 元空间GC阈值(JDK1.8)
-XX:MaxMetaspaceSize 最大元空间大小(JDK1.8)
-Xss 栈大小
-XX:MaxDirectMemorySize 直接内存大小,默认为最大堆空间
jvm是什么
对象的创建
类加载检查
1 new 指令创建对象
2 检查这个指令的参数能否在常量池中找到类的符号引用
3 检查这个符号引用是否已经被加载,解析,初始化过
4 没有的话先进行类加载过程
分配内存
指针碰撞
假设内存规整,指针一边是空闲空间一边是被分配空间,通过指针移动表示使用了多少内存
空闲列表
两种方式取决于垃圾收集器类型,带有压缩整理的垃圾收集器可以使用指针碰撞
并发情况下线程不安全
内存分配好的话将内存空间初始化为0,然后将hashCode,分代年龄等信息保存在对象头
初始化零值
将分配到的内存空间都初始化为零值(不包括对象头)
这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,
程序能访问到这些字段的数据类型所对应的零值
程序能访问到这些字段的数据类型所对应的零值
对象内存布局
A.对象头
第一部分存储hashCode,分代年龄,锁信息等数据(MarkWord)
第二部分类型指针(通过这个指针确定是哪个类的实例)
数组的话还存储数据的长度
B:实例数据
类的字段信息,包括父类的字段信息等
存储顺序受分配策略的影响:相同宽度的字段总是被分配到一起
父类中的字段信息在子类之前
C:对齐填充
虚拟机要求对象大小是8字节的整数倍
如果对象大小不是8字节的整数倍的话,需要通过占位符补全
内存分配策略
年轻代
对象主要分配在新生代的Eden区域
启动本地线程分配缓存的话,则优先在TLAB上分配
优先将对象分配在新生代上, Eden区域内存不够时发送Minor GC
老年代
大对象直接进入老年代
可以配置参数指定大于多大对象直接进入老年代,防止年轻代内存复制频繁
长期存活的对象进入老年代
虚拟机给对象添加年龄计数器
动态年龄判断
.空间分配担保
1.Minor GC之前, JVM会判断老年代最大可用的内存是否大于年轻代所有对象内存总和,条件成立那么认为是安全的
2.如果不成立,尝试进行一次Minor GC,这个是有风险的,因为老年代剩余的内存可能不够
3.老年代会判断历史晋升到老年代对象的平均值是否大于老年代剩余的内存, 大于则需要FULL GC 来腾出更多的内存
0 条评论
下一页