从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——从源码编译到类执行与内存管理全流程梳理相关推荐

  1. fork的黑科技,它到底做了个啥,源码级分析linux内核的内存管理

    最近一直在学习linux内核源码,总结一下 https://github.com/xiaozhang8tuo/linux-kernel-0.11 一份带注释的源码,学习用. fork的黑科技,它到底做 ...

  2. xv6源码分析(四):内存管理

    xv6通过页表机制实现了对内存空间的控制.页表使得 xv6 能够让不同进程各自的地址空间映射到相同的物理内存上,还能够为不同进程的内存提供保护. 除此之外,我们还能够通过使用页表来间接地实现一些特殊功 ...

  3. linux yum安装分区工具,搭建本地和网络yum源、源码编译安装软件及磁盘分区管理...

    1.自建yum仓库,分别为网络源和本地源 1)挂载光盘镜像 [root@centos7 ~]#mount /dev/sr0 /mnt/ [root@centos7 ~]# df -h Filesyst ...

  4. linux 内存管理slab源码,Linux内核源代码情景分析-内存管理之slab-回收

    图 1 我们看到空闲slab块占用的若干页面,不会自己释放:我们是通过kmem_cache_reap和kmem_cache_shrink来回收的.他们的区别是: 1.我们先看kmem_cache_sh ...

  5. Gradle教程 Spring源码编译

    目录: gradle 安装配置 grovvy 语法介绍 gradle 仓库配置 gradle 配置文件讲解 gradle 案例:springboot + gradle打war包 gradle 多项目案 ...

  6. Android源码编译(基于Ubuntu18.0.4)

    文章目录 一.环境搭建 硬件要求 软件要求 操作系统和 JDK 主要软件包 软件安装 Git安装 repo工具安装 安装 openJDK 8 其他依赖安装 二.源码下载 建立源码文件夹 初始化仓库 源 ...

  7. Android源码定制(1)——Android6.0源码编译

    一.前言 最近在研究Xposed框架定制,恰好又看到看雪上两个大佬关于源码定制和Xposed源码定制的帖子,所以尝试基于Android6.0版本,详细记录一下从源码下载到Xposed框架定制的全过程. ...

  8. Flink 源码解析 —— 源码编译运行

    更新一篇知识星球里面的源码分析文章,去年写的,周末自己录了个视频,大家看下效果好吗?如果好的话,后面补录发在知识星球里面的其他源码解析文章. 前言 之前自己本地 clone 了 Flink 的源码,编 ...

  9. make无法执行——源码编译、安装

    在进行软件源码编译.安装时 ,出现make无法执行的情况下,是缺少基础开发包 执行命令即可: apt-get  install    build-essential

最新文章

  1. SQLiteOpenHelper类
  2. 沈向洋,被微软“耽搁”的独角兽催化大师
  3. Golang 随机获取本机可用端口
  4. 面向对象的多态性(1)
  5. 在HTML文档内引入CSS
  6. c构造函数和析构函数_C ++构造函数和析构函数| 查找输出程序| 套装2
  7. 使用C++和LIBSVM实现机器学习+样本分类
  8. 职业方向网络词汇(不定时更新)
  9. Asp.net core 学习笔记 ( OData )
  10. LaTex安装及使用
  11. 汽车之家推荐系统排序算法迭代之路
  12. 腾讯,字节等大厂面试真题汇总,赶快收藏备战金九银十!
  13. 字节跳动,正在动摇互联网的根基!(转)
  14. 计算机更新并关机能关闭吗,win10关机不想更新并关机而是直接关机步骤设置
  15. 春眠不觉晓,二极管种类知多少?「TVS、整流、稳压、肖特基、快回复、续流、发光LED、变容」
  16. 财政部ppp数据库爬虫
  17. 金蝶服务器销售出库单无法审核,金蝶云IM-201805007-销售出库单审核失败
  18. 高合汽车引发行业“核裂变”,数字生命体高合HiPhi Z正式发布
  19. CenterNet 模型后处理 (C++和python代码)
  20. 实时监控TCP Reset信息的二进制hook手艺

热门文章

  1. “琐事”? “锁事”。
  2. Photoshop画笔工具应用—光斑与气泡效果
  3. 服务器返回数据为空是怎么回事,服务器端已经序列化对象了,为什么客户端读到的是空值?...
  4. OrCAD error Subcircuit xxx used by X_U1 is undefined
  5. 这四条价值百万的建议,我免费送给你
  6. 项目管理软件上云,到底靠不靠谱?
  7. SQL命令create table if not exist
  8. 【倩女幽魂xp主题】_8.5
  9. 常用的PHP语言的cms系统
  10. 最新kali之clang