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