JVM——从源码编译到类执行与内存管理全流程梳理
从Java源码编译开始说起
分为三个步骤:
1:分析和输入到符号表
分析:词法和语法分析,将代码字符串转变为token序列,由token序列生产抽象语法树
输入:将符号输入到符号表,确定类的超类型和接口,添加默认构造器,将类中出现的符号输入到自身的符号表中
2:注解处理
处理用户自定义的annotation,可以自动生成代码或者进行一些特殊检查
3:语义分析和生产class文件
基于之前的抽象语法树进行语义分析
将一些名字、表达式和变量、方法这些联系到一起;检查语句可达,检查语法检查变量
之后开始生成class文件,步骤为:
1:先执行初始化块或声明属性时制定的初始值,再执行构造器里制定的初始值。
1:将静态成员初始化器收集为()
2:将抽象语法树生成字节码,后序遍历
3:从符号表生成class文件
4:class文件中的其他东西
1:结构信息
2:元数据:源码中声明和常量的信息
3:方法信息:源码中语句与表达式的信息
综上所述:class文件是个完整的子描述文件,字节码在其中只占了很小的部分
JVM通过类加载来装载class文件
分为三个步骤:
1:装载
JVM通过类的全限定名和类加载器完成类的加载
1:通过类的全限定名找到二进制字节流
2:把其中与方法区有关的东西变成方法区的运行时数据结构
3:在堆中生成这个类的访问入口
如果是数组,数组型类中的元素类型由所在的ClassLoader负责加载,数组类由JVM直接创建
2:链接
1:对二进制字节码的格式进行校验
2:初始化类中的静态变量,赋默认值
3:解析类中调用的接口、类,确保其调用的属性、方法存在
3:初始化
在执行初始化过程之前,首先必须完成链接过程中的校验和准备阶段,解析阶段则不强制
初始化就是执行类中的静态初始化代码、构造器代码及静态属性的初始化
四种情况初始化会被触发:
1:调用了new
2:反射调用类中方法
3:子类调用初始化
4:JVM启动过程中指定的初始化类
类加载要通过ClassLoader及其子类
Bootstrap ClassLoaderL:并非ClassLoader的子类,代码中无法获取,Sun JDK启动时会初始化这个类
Extension ClassLoader:加载扩展功能的一些jar包
System Classloader:加载指定的Classpath中的jar包及目录
User-Defined ClassLoader:自行实现的ClassLoader
同一个ClassLoader实例对象中只能加载一次同样名称的Class
ClassLoader抽象类提供的关键方法
loadClass:加载指定名字的类
先从已经加载的类中寻找,没找到的话从parent ClassLoader中寻找,没有的话找System ClassLoader,最后调用findClass方法,要改变加载顺序就覆盖findClass方法
findLoadedClass:从当前ClassLoader实例对象的缓存中寻找已加载的类,调用native的方法
findClass:直接抛出ClassNotFoundException,只能以自定义的方式加载相应的类
findSystemClass:负责从System ClassLoader中寻找类,没找到就找Bootstrap ClassLoader
defineClass:负责将二进制的字节码转换成Class对象
resolveClass:负责完成Class对象的链接,如果链接过,则会直接返回
PS:JVM不允许ClassLoader直接卸载加载了的类,只有JVM才能卸载,在Sun JDK中,只有当
ClassLoader对象没用引用时,此ClassLoader对象加载到类才会被卸载
类加载方面的常见异常
1:ClassNotFoundException:在当前的ClassLoader种加载类时未找到类文件
2:NoClassDefFoundError:加载的类中引用到的另外的类不存在
3:LinkageError:此类已经在ClassLoader加载过了,重复加载
4:ClassCastException:两个A对象由不同的ClassLoader加载,如果将其中某个A对象造型成另外一个A对象,也会报出该错误
之后该类执行了
字节码解释执行
JVM在运行期对字节码进行解释并执行
invokestatic调用static方法invokevirtual调用对象实例的方法invokeinterface调用接口的方法invokespecial调用private方法和编译源码生成的<init>方法
线程创建后产生PC和栈
每个方法调用都会产生帧栈,帧栈中有局部变量区(局部变量和参数)和操作数栈(存放中间结果)
1:指令解释执行
获取下一条指令,解码并分派,然后执行
令解释执行更加高效的优化:
1:栈顶缓存:
减少寄存器和内存的交换,将操作数栈顶的值缓存在寄存器,直接在寄存器计算,然后放回操作数栈
2:部分栈帧共享:
后一方法可将前一方法的操作数栈作为当前方法的局部变量,节省数据copy的消耗
字节码编译执行
Sun JDK中对执行频率高的代码将字节码编译为机器码
C1编译器的主要优化有:
方法内联:
把调用到的方法的指令直接植入当前方法中
去虚拟化:
发现类中的方法只提供一个实现类,那么对于调用此方法的代码,也可以实现方法内联
冗余削除:
在编译时,根据运行情况进行代码折叠或削除
C2编译器的主要优化有:
重在全局优化
逃逸分析是C2很多优化的基础
一、对象被赋值给堆中对象的字段和类的静态变量。二、对象被传进了不确定的代码中去运行。如果满足了以上情况的任意一种,那这个对象JVM就会判定为逃逸。
标量替换:
用标量替换聚合量,如果创建的对象并未用到其中的全部变量,则可以节省一定的内存
栈上分配:
如果p变量没有逃逸,就在栈上直接创建Point对象实例,而不是在堆中,这样快速,且方法结束时对象可以一起回收
同步削除:
如果JVM通过逃逸分析,发现一个对象只能从一个线程被访问到,则访问这个对象时,可以不加同步锁。如果程序中使用了synchronized锁,则JVM会将synchronized锁消除。
PS:还有个OSR编译器只替换循环代码体的入口
用计数器权衡未编译期间解释执行方式速度
调用计数器
即方法被调用的次数
回边计数器
方法中循环执行部分代码的执行次数
一些阈值
CompileThreshold:指方法被调用多少次后,被编译为机器码
OnStackReplacePercentage:默认情况下client模式时为933,server模式下为140,方法上的回边计数器到达这个值时,触发后台的OSR编译
反射执行
编写时无法知道实现类,但通过反射机制去调用实现类中的execute方法
动态生成字节码,加载到JVM中执行
JVM内存管理
Sun JDK将内存空间划分为方法区、堆、本地方法栈、PC寄存器和JVM方法栈
方法区
存放的有:要加载的类的信息、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息
堆
存储对象实例及数组值,在32位操作系统上最大为2GB
新生代
大多数情况下Java程序中新建的对象都从新生代分配内存
TLAB
Eden Space上有一块TLAB,默认为Eden Space的1%,分配内存时不需要加锁,高效
PS:联想到基于逃逸分析的栈上分配
老年代
用于存放新生代中经过多次垃圾回收仍然存活的对象,例如缓存对象
也可能新建的对象在老年代上分配内存,比如大对象、大的数组对象
本地方法栈
支持native方法执行,存储每个native方法调用的状态
PC寄存器和JVM方法栈
每个线程都会创建PC寄存器和JVM方法栈
PC寄存器可能栈用CPU寄存器或操作系统内存
JVM方法栈占用操作系统内存,为线程私有,内存分配高效,运行完毕后释放
JVM内存回收
1:引用计数收集器
2:跟踪收集器
复制
标记-清除
标记-压缩
可用的GC
谈谈回收老年代和持久代对象吧
1:串行
基于标记-清除-整理实现
三个阶段:
1:从根集合对象开始扫描,三色着色法标识对象
2:遍历整个老年代或持久代空间,找出未标识的对象,回收内存
3:滑动压缩,把存活的都滑动到开始处
2:并行
采用标记-整理实现
三个阶段:
1:将代空间划分为并行线程个数的regions,根据根集合可直接访问到的对象以及并行线程个数进行划分,并行进行三色着色,当对象被着色时,更新其所在region存活的大小以及存活对象所在位置
2:分析判断适合压缩移动的region
3:并行进行对象移动和region回收
3:并发
采用标记-清除方式
CMS执行的扫描、着色和清除步骤如下:
1:第一次标记
扫描从根集合对象到老年代中国可直接访问的对象
2:并发标记
3:重新标记
4:并发收集
i-CMS模式,CMS仅启动一个处理器线程来并发扫描标记和清除,并且线程在执行一小段时间后会先将CPU使用权让出来,分多次多段的方式完成整个扫描标记和清除的过程
CMS提供了一个整理碎片的功能:-XX:+UseCMSCompactAtFullCollection
Full GC
被触发时,首先按照新生代所配置的GC方式对新生代进行GC
然后按照旧生代的GC方式对旧生代、持久代进行GC
执行的情况有四种:
1:老年代空间不足
2:永久代空间满
3:CMS GC时出现promotion failed(对象太大,连老年代也放不下)和concurrent mode failure(CMS过程中有对象要放入老年代,而此时老年代空间不足)
4:统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间
选择GC组合的参数
Sun JDK提供了两种简单的方式帮助选择
1:吞吐量优先
-XX:GCTimeRatio=n
2:暂停时间优先
-XX:MaxGCPauseMillis=n
Real-Time JDK
1:新的内存管理机制
immortal内存区:用于保存永久的对象
Scoped内存区,用于保留临时的对象
均不受GC管理
2:运行Java应用直接访问物理内存
JVM内存状况查看方法和分析工具
1:输出GC日志
2:GC Portal
3:JConsole:图形化查看JVM中内存的变化状况
4:JVisualVM:查看内存的消耗情况、线程的执行情况及程序中消耗CPU、内存的动作
5:JMap:分析JVM内存状况
6:JHat:分析jvm堆dump文件
7:JStat:统计分析JVM运行状况的工具
8:Eclipse Memory Analyzer:分析jvm堆dump文件的插件
JVM线程资源同步及交互机制
线程资源同步
线程对working memory 操作的指令由线程发出,分为use、assign、load、store、lock和unlock
对main memory的操作的指令由线程执行引擎发出,分为read、write、lock和unlock
操作指令含义
use:将变量的值从working memory中复制到线程执行引擎中
assign:将变量值复制到线程的working memory中
load:将main memory中read到的值复制到working memory中
store:将变量的值从working memory中复制到main memory中,等待main memory的write动作
read:由main memory发起,从main memory中读取变量的值
write:由main memory发起,将working memory的值写入到main memory中
lock:同步操作main memory,给对象加上锁
unlock:同步操作main memory,给对象去除锁
线程交互机制
典型的连接池
return方法:将连接返回到缓存列表中,将可使用的连接数+1
get方法:判断可用连接数到0后,进入等待状态
JVM提供的方式
wait方法:让当前线程进入等待状态,等待通知或者到达指定时间
notify方法:随即找wait此Object的一个线程
notifyAll方法:通知wait此Object的所有线程
JVM——从源码编译到类执行与内存管理全流程梳理相关推荐
- fork的黑科技,它到底做了个啥,源码级分析linux内核的内存管理
最近一直在学习linux内核源码,总结一下 https://github.com/xiaozhang8tuo/linux-kernel-0.11 一份带注释的源码,学习用. fork的黑科技,它到底做 ...
- xv6源码分析(四):内存管理
xv6通过页表机制实现了对内存空间的控制.页表使得 xv6 能够让不同进程各自的地址空间映射到相同的物理内存上,还能够为不同进程的内存提供保护. 除此之外,我们还能够通过使用页表来间接地实现一些特殊功 ...
- linux yum安装分区工具,搭建本地和网络yum源、源码编译安装软件及磁盘分区管理...
1.自建yum仓库,分别为网络源和本地源 1)挂载光盘镜像 [root@centos7 ~]#mount /dev/sr0 /mnt/ [root@centos7 ~]# df -h Filesyst ...
- linux 内存管理slab源码,Linux内核源代码情景分析-内存管理之slab-回收
图 1 我们看到空闲slab块占用的若干页面,不会自己释放:我们是通过kmem_cache_reap和kmem_cache_shrink来回收的.他们的区别是: 1.我们先看kmem_cache_sh ...
- Gradle教程 Spring源码编译
目录: gradle 安装配置 grovvy 语法介绍 gradle 仓库配置 gradle 配置文件讲解 gradle 案例:springboot + gradle打war包 gradle 多项目案 ...
- Android源码编译(基于Ubuntu18.0.4)
文章目录 一.环境搭建 硬件要求 软件要求 操作系统和 JDK 主要软件包 软件安装 Git安装 repo工具安装 安装 openJDK 8 其他依赖安装 二.源码下载 建立源码文件夹 初始化仓库 源 ...
- Android源码定制(1)——Android6.0源码编译
一.前言 最近在研究Xposed框架定制,恰好又看到看雪上两个大佬关于源码定制和Xposed源码定制的帖子,所以尝试基于Android6.0版本,详细记录一下从源码下载到Xposed框架定制的全过程. ...
- Flink 源码解析 —— 源码编译运行
更新一篇知识星球里面的源码分析文章,去年写的,周末自己录了个视频,大家看下效果好吗?如果好的话,后面补录发在知识星球里面的其他源码解析文章. 前言 之前自己本地 clone 了 Flink 的源码,编 ...
- make无法执行——源码编译、安装
在进行软件源码编译.安装时 ,出现make无法执行的情况下,是缺少基础开发包 执行命令即可: apt-get install build-essential
最新文章
- SQLiteOpenHelper类
- 沈向洋,被微软“耽搁”的独角兽催化大师
- Golang 随机获取本机可用端口
- 面向对象的多态性(1)
- 在HTML文档内引入CSS
- c构造函数和析构函数_C ++构造函数和析构函数| 查找输出程序| 套装2
- 使用C++和LIBSVM实现机器学习+样本分类
- 职业方向网络词汇(不定时更新)
- Asp.net core 学习笔记 ( OData )
- LaTex安装及使用
- 汽车之家推荐系统排序算法迭代之路
- 腾讯,字节等大厂面试真题汇总,赶快收藏备战金九银十!
- 字节跳动,正在动摇互联网的根基!(转)
- 计算机更新并关机能关闭吗,win10关机不想更新并关机而是直接关机步骤设置
- 春眠不觉晓,二极管种类知多少?「TVS、整流、稳压、肖特基、快回复、续流、发光LED、变容」
- 财政部ppp数据库爬虫
- 金蝶服务器销售出库单无法审核,金蝶云IM-201805007-销售出库单审核失败
- 高合汽车引发行业“核裂变”,数字生命体高合HiPhi Z正式发布
- CenterNet 模型后处理 (C++和python代码)
- 实时监控TCP Reset信息的二进制hook手艺
热门文章
- “琐事”? “锁事”。
- Photoshop画笔工具应用—光斑与气泡效果
- 服务器返回数据为空是怎么回事,服务器端已经序列化对象了,为什么客户端读到的是空值?...
- OrCAD error Subcircuit xxx used by X_U1 is undefined
- 这四条价值百万的建议,我免费送给你
- 项目管理软件上云,到底靠不靠谱?
- SQL命令create table if not exist
- 【倩女幽魂xp主题】_8.5
- 常用的PHP语言的cms系统
- 最新kali之clang