jvm虚拟机学习
2024-11-07 17:13:44 0 举报
AI智能生成
JVM(Java Virtual Machine)是一种用于执行Java字节码的虚拟机。它通过将字节码转换成机器语言,使得Java程序能够在各种平台上运行。JVM的核心内容包括类加载机制、运行时数据区、垃圾回收机制等。学习JVM可以帮助我们更好地理解和优化Java程序性能,提高程序稳定性和兼容性。我们可以从官方文档、教程、博客和书籍等多种途径学习JVM。
作者其他创作
大纲/内容
虚拟机类型
sun ClassicVm 虚拟机 - 服务器
是一款纯解释器编译。【解释的功能是把机器码翻译为本地代码】
当时需要外挂编译器类型才能够使用,外挂的其目的是加快执行速度。比较直接使用本地代码速度直接跳过翻译环境。自然是快的
配套的编译器 Sunwjit(Sun workshop JIT),Symantec JIT 和 ShuJIT。。
这导致了Java【慢】的原因
使用方式:
jdk1.2 之前唯一虚拟机
jdk1.2 与 hotspot vm并存 classic默认虚拟机
jdk1.3 hotspot vm 为默认虚拟机
jdk1.4 退出历史舞台【sun labs research vm】实验室级别的??
Exact Vm - 服务器
性能更好,提供了诸多的高级特性
热点探测JIT技术
两级即时编译器。哪两级
编译器与解释器混合模式。【mix混合模式】
准确式内存管理
精确定位地址是引用地址还是数据地址。一次查找功能
用于垃圾回收器准确识别定位引用对象的前提
优于基于句柄的对象查找方式【理解为指针的指针,二级对象地址】
HotSpot Vm 【武林萌主】- 服务器
故名思义,hot spot是热点检测技术的代名词。
出生于1997之前,Sun收购于LongView Technologies公司
来源于 strong talk 虚拟机,strong talk相当多的技术实现来自self语言。追溯到 20世纪80年代SmallTalk
特点
Exact vm的精确式内存管理
热点代码探测技术【Exact vm 也有】
执行计数器检测出最有直接编译价值的代码。即时编译器就以方法为单位执行编译
方法内有效循环很多,执行OSR【栈上替换编译 on stack replacement】
编译+解释 并存
历史
1997年成名
2006年 Oracle收购Sun之后,把BEA JRockit中的优秀特性融入HotSpot Vm,并移除永久代,
JRockit 中的 Java Mission Control监控工具
Mobile/Embed-ded Vm【小家碧玉】- 移动嵌入式
KVM 也是一种,早期用于android、ios系统
JAVA Card VM 精简到能放入智能卡、SIM卡、银行信用卡、借记卡
元循环虚拟机
javaInJava 用java实现的虚拟机。用于研究
无即时编译器,只能解释执行
Maxine VM
先进即时编译器 还有 GC
性能接近 Hotspot的client模式水平
Jikes RVM
无特别介绍
高级虚拟机
Graal编译器【服务端编译器】
无语言倾向 RUN Programs Faster Anywhere【区别于Write Once, Run anywhere】
Java ,Scala, Groovy , Kotlin , 【C、C++、Rust。基于LLVM的语言】,javascript,Ruby,Python 、R
无额外开销混合语言,而且有时候会比源码更高效率
IR表示是 Graal的中间表示, 特化方式【specialized】等价转换为C或C++语言
虚拟机基本结构
java直接内存
NIO直接操做的内存区
不受jvm的控制【不受-XMX制约】,是系统的内存,大小由操作系统决定
java堆
新生代
eden【8】
s0【from】【1】
s1【to】【1】
老年代
tenured
参数【-XMx】
堆溢出参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.bin【第7章使用】
线程私有内存
本地方法栈
jnc,通常是c语言编写的。
高性能计算可以用c语言
native接口
java栈
局部变量表
参数
局部变量
操作数栈
更细粒度的变量
中间结果
n地址指令
四地址 a1 p a2 =》 a3, a4位next
三地址 a1 p a2 =》 a3, (pc)=pc+1
一般三地址是四地址的优化
二地址 a1 p a2 =》 a1 , (pc) = pc + 1
单地址 a1 p =》 a1,(pc) = pc + 1
a++
无地址
清除clear
关机
数据区
常量池指针
参数【-xss】决定函数调用的最大深度
实战
递归调用的深度由其大小
26 字,一个字【cpu64位 = 8字节,32位 4字节】
一个long = 2位l
pc寄存器
当前方法不是本地方法 pc=当前方法地址
本地方法。pc=undifined
方法区
通过类加载子系统加载类信息到该区
实战
Todo写个proxy. NewInstance创建大量类的实例
运行时常量池
字符串字面量 “aa”+“并”
数字常量
方法区不同版本的存储地址
Jdk1. 7之前 永久代 perm【jvm内置空间】
之后 元数据空间【存储在堆外直接内存】
特点
创建动态代理类,错了每次创建就需要大量的元空间或者perm空间大小。
动态生成类,而非代理类。代理类一般放在内存空间,其类一般只有一个。
不同的类,才会存储在元空间中
一般动态代理对象是单例或者少量的,所以spring会启动会创建单例代理对象。确保空间大小的不受大的影响
类加载子系统【9章】
用于加载class信息【来源于网络或者文件系统】
参数
-verbose:class 用于显示加载的类和卸载的类,以及来自的jar文件
等价。-XX:+TraceClassLoading [跟踪类加载]&& -XX:+TraceClassUnloading【跟踪类卸载】
-XX:+PrintClassHistogram
执行引擎【11章】
负责执行字节码
提高执行效率
jit
栈上分配
是私有线程独有的对象,尽可能分配到栈上,线程调用会理解释放,gc不用介入就能销毁对象
逃逸分析
目的是用来分析对象是否会被其他线程访问到。
非逃逸对象。jvm会分配到栈上,不用分配到堆中,提高执行效率
启动方式 【三者都要启动才能使用栈上分配】
-XX:+DoEscapeAnalysis 开启逃逸分析
-XX:+EliminateAllocations 开启栈上分配
标量替换,就是一个对象可以由多个标量组成,在分析过程中,把对象分解,用标量来替换,那么这个对象可有可无,此时就可以针对这些被拆解后堆标量进行分析与优化了。原先的对象就无需指针引用了,提高的执行效率
针对的是小对象,因为栈有空间限制,大对象就没办法进行分配了。只要不超过栈的大小
-server
未开启之前是大量gc
开启之后
分类
新生代串行
-XX: +UseSerialGC 新生代与老年代都是串行gc
专注性和独占性,性能经得起考验,老牌
Stw这是比较常用的新生代垃圾回收器,一般是单工作线程,会堵塞整个线程
复制算法
老年代串行gc
标记压缩法
参数详解
-XX: +UseSerialGC 新生代与老年代都是串行
-XX: +UseParNewGC 新生代是ParNew 老年代串行
ParNew是对串行的并行化,会在回收过程中stw.
-XX: ParallelGCThreads指定并行线程数量。一般与cpu一致。大于8 则为3+((5×CPU_Count)/8)
关联参数
-XX:+UseConcMarkSweepGC 新生代ParNew ,老年代CMS
历史记录查看
-XX: +UseParallelGC 新生代Parrallel老年代串行
关注吞吐
-XX: MaxGCPauseMillis,最大停顿时间值越小可能分配的堆越小,大量gc,反而会减少吞吐
-XX: GCTimeRatio 比如99=>1/(1+99)的最多时间去收集垃圾[吞吐量]
支持 UseAdaptiveSizePolicy参数自适应 。【书中表达ParNew不支持】
调节 上面的 最大停顿时间、吞吐量、堆大小及其内部的新生代老年代占比
如果想要老年代做并行【非并行哈,】的可以使用UseParallelOldGC
标记压缩算法
ParallelGc + ParallelOld老年代
ParallelGc + ParallelOld老年代
并行PSYoungGen 是历史记录
-XX:+UseConcMarkSweepGC
关注停顿的,所以很多流程是并发的
关注停顿的,所以很多流程是并发的
新生代用ParNew 老年代CMS
老年代gc虽然是cms (标记清除)但是有机会触发一次串行gc(标记压缩算法)
-XX:+UseCMSCompactAtFullCollection ,运行cms gc之后会进行独占式内存碎片整理
-XX:CMSFullGCsBeforeCompaction 可以指定多少次cms后回首一次。
Cms阶段
初始标记(stw 标记根节点)
并发标记(并发标记所有其他节点)
预清理(清理前的准备)
参数准备
控制清理时遇到新生代gc而产生双停顿
-XX: -CMSPreckeaningEnabled可以不进行预处理
重新标记(stw 修正并发缺失的对象)
并发清理(正式清理对象)
并发重置(重置cms数据结构为下一步做准备)
关联参数
-XX: +UUseConcMarkSweepGC 启动
-XX: ConcGCTheads设置并发线程数
-XX: ParallelGCThreads设置并发线程数。。算法一致(ParallelGCThreads + 3)/4默认并发线程数
-XX: CMSInitiatngOccupancyFraction(初始占有率)达到老年代堆的现有比率就触发gc
如果内存增长慢,调大一点,内存快,就调小一点
-XX: +UseG1GC
开启G1
开启G1
特性
并行,多个GC线程并行处理
并发,在部分阶段可以与应用线程交替执行
空间整理, 与CMS不同【标记清除,之后必须要进行一次空间压缩】,g1是在执行的过程可以移动对象。避免碎片的产生
分代,分代回收器,兼顾老年代和新生代
可预见性【停顿时间可控】,可以只回收部分区域
主要阶段
gc【4-5章】
一次young gc触发的时机是 eden 区无法容纳的时候尝试gc
对象进入老年代的时机
desired_survivor+size = survivor_capacity * TargetSurvivorRatio /100
desired_survivor+size = survivor_capacity * TargetSurvivorRatio /100
1) 最大年龄,第一次gc,会清空eden,并把剩余的垃圾对象放在from区【充分非必要条件】
MaxTenuringThreshold 默认 15次
-Xmx1024M -Xms1024M -XX:+PrintGCDetails -XX:MaxTenuringThreshold=15 -XX:+PrintHeapAtGC
PrintHeapAtGC 每次打印gc的日志之外,还打出堆的信息
2)自行运行推断年龄,不一定是最大年龄
age或者MaxTenuringThreshold中值最小的作为最终晋升年龄
-XX:TargetSurvivorRatio=15. 默认 位50【即50%】【参数同PretenureSizeThreshold】
只针对 -XX: +UseParNewGC 有效, 针对默认的 -XX: +UseParallelGC 无效。
3) 大对象直接进入老年代
-XX:PretenureSizeThreshold=1000. [单位字节]
只针对 -XX: +UseParNewGC 有效, 针对默认的 -XX: +UseParallelGC 无效。
gc回收区域
Java堆
方法区(堆外直接内存)
直接内存(特指NIO)
虚拟机优化目标
1. 实现书上讲 尽可能将对象预留在新生代,减少老年代的gc次数
常用虚拟机参数
日志打印
-XX:PrintGC
只是对堆的大小做了打印,这里是堆从5m下降到377k,总堆大小为16M,以及本次gc总耗时
JDK9 或者10默认使用g1 使用-Xlog:gc
-XX:+PrintVMOptions 打印显示的系统参数
-XX:+PrintCommandLineFlags, 很显然, initialHeapSize MaxHeapSize都未指定,是系统默认给的参数
-server 和 -client模式的区别,
不同模式默认参数也会不一样。-XX: PrintFlagsFinal,可以看参数的默认设置(JIT默认阈值和最大堆大小)
一般后端服务长期运行的用server。因为其在启动会根据系统信息做更多的优化。如果是那种客户用户界面的java程序,其关注启动速度,可以使用client模式
基本概念(第四章)
状态有 可触及性`可复活`不可触及。三种
可复活的概念是finalize方法内一个对象=this
以上代码第一次gc复活了,第二次gc就会消失。说明finalize只能允许一次复活
Gc回收算法
并行处理
Gc执行和应用程序停顿,多线程并发处理gc,内部
并发处理
Gc与应用程序在cpu时间片段上交替执行形成的并发处理
并发处理
对象可触及性 reachable 可触及性
只有不可触及的对象才能被垃圾回收
对象引用类型分类
强引用
一般如果不可触及才能被gc
软引用
内存不足才会gc
弱引用
发生gc就会被回收
虚引用
发生gc就会被回收
必须要与一个referenceQueue搭配使用,用于跟踪对象的回收。
使用get方法获取虚引用是失败的
内存泄露与内存溢出的区别
内存溢出是现象,内存泄露是内存溢出的一个原因
内存溢出是应用在运行过程中占用的内存超过了可能内存
内存泄露往往指错误地过疏忽大意的代码编写导致存在一些不会再用的内存而无法被gc清理。(不能释放不再被使用的内存)
内存泄露案例
过去版中Sting. Substr大字符串切割小字符串。大字符串被回收之后,小字符串的存在就是内存泄露。除非小字符串也被回收
即入引用和即出引用
即入引用表示引用当前对象的对象,一般通过该引用能够看到当前对象在哪个类或者对象被引用 incoming
即出引用 为当前对象引用的对象或者说是当前对象的属性 outgoing
浅堆和深堆
浅堆是指一个对象的结构存储大小
基础类型与引用本身大小
对象头大小
按照cpu规格的4或者8倍字节对齐
🟧A对象本身
深堆是指仅仅被当然对象直接或者间接引用的对象 retained heap
被gc回收的是内存,因为是直接引用的
所有自身`直接或间接引用对象的浅堆大小之和
又叫保留集
🟧A对象的深堆 A + D
实际对象大小,深堆+共享对象
所有能接触到的浅堆之和
🟧A对象的实际大小 = A + C+D
保留对象【retained】
jvm释放的大小,比如 删除B保留A,那么E为retained对象
常用虚拟机工具
1.线程监控,实时查看堆信息
线程异常原因调查
runable -+ suspended
可能是被人为debug,但是没有关闭debug功能一直占有
vmtool --action interruptThread -t 98951 去终止。但是实际上并没有效果
或者在应用启动过程中使用【使用arthas重置代码】
jad --source-only arthas.VmTool > /tmp/VmTool.java
vim /tmp/VmTool.java。 修改里面的
查找加载的 ClassLoader
sc -d arthas.VmTool | grep classLoaderHash
sc -d arthas.VmTool | grep classLoaderHash
mc -c 491451e7 /tmp/VmTool.java -d /tmp
redefine /tmp/arthas/VmTool.class
问题
idea 问题
出现build失败,但是mvn complile成功
1、 在命令行中 mvn idea:idea
2、在idea中 rebuild project
1、build acttion to maven 委托给 maven 执行
mvn 编译输出乱码 如下说是
需要查看当前mvn的编码方式
需要在 -Dfile.encoding=GBK
如何解决【idea 无效的标记: --release】
参考资料【csdn】
https://blog.csdn.net/weixin_43887251/article/details/140835596
https://blog.csdn.net/Hello_World_QWP/article/details/135452175
执行步骤
打开 IntelliJ IDEA,点击菜单栏的 File -> Project Structure。
选择 Project,确保 Project SDK 和 Project language level 都设置为 Java 9 或更高版本。
mvn clean
mvn dependency:purge-local-repository
mvn install
mvn dependency:purge-local-repository
mvn install
出现 throws java.lang.ClassFormatError accessible: module java.base does not “opens java.lang“
在 vm中添加 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.base/java.time=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/jdk.internal.access=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/sun.reflect.generics.repository=ALL-UNNAMED --add-opens java.base/sun.reflect.generics.reflectiveObjects=ALL-UNNAMED
参考资料
https://blog.csdn.net/qq_45856381/article/details/138534403
https://blog.csdn.net/cnttr/article/details/132130932
字节码专题
局部变量表 LocalVariableTable
查看方式 Javac -g xxxx. java 然后用javap -c verbose
是存储方法内局部变量和入参的数据结构
作用范围 就是编译之后的编译之后字节码所在代码行数的意思,[start_pc start_pc+length-1]表示字节码
如下所示为案例index 或者slot表示第几个变量。long和double占用两个槽,其他类型都是一个槽。
加载器
基本的工作流程
加载
通过全限定名获取类文件的二进制流
文件有Class文件 , jar zip等归档文件内的class文件
读取方式
磁盘
数据库
网络
解析数据结构之后放入方法区中
实例化一个Class实例 (反射的关联入口)
连接
验证
Class格式检查 (魔术版本)
语义上的检查
是否继承Object
final字段过方法过类有没有被继承或者重写
abstract方法是否被实现
签名一样但是返回值不一样也是问题
字节码验证(最复杂部分)
类型检查
方法参数和调用类型是否一致
赋值类型是否一致
跳转指令是否到了一个不可知的地方
但是无法校验method no defound
符号引用的直接引用存不存在
在编译阶段,只会存储符号引用,比如 类`接口`字段`方法的描述符和名称
如果不存在那么就会报noSuchMethod等异常
直接引用表示的是代码过数据的真实内存地址
准备
Public final static常量,在准备阶段赋值[非字节码的ldc和put stiatic操作]
Ldc loadConstant操作
非final的静态变量,一般是在cinit操作。[类似于静态块中的操作]cinit为初始化
解析
符号引用转为直接引用
绑定内存地址,前面的验证阶段后面的第四点会在后面操作的
初始化
表示字节码的行为
cinit
静态非final变量
静态块
这个过程是线程安全的
注意死锁问题
不易察觉,dump thread也看不出死锁的信息
注意点
这个流程可以分阶段。可以只有加载操作没有初始化操作。
子类加载父类的静态方法,那么子类只会被加载,不会初始化
使用-XX:+TraceClassLoading可以查看加载过程
双亲委派机制
检查类是否在当前类加载器,如果没有向上检查,加载流程自顶向下加载。
问题,父类无法访问子类,如何解决,用线程上下文打破
使用方式
主动
只有主动的操作才会加载
可以看到如果是final类型的字段,那么不会导致包裹类的加载,就更不用说初始化了
被动
0 条评论
下一页