深入理解JVM
这,仅是我学习过程中记录的笔记。确定了一个待研究的主题,对这个主题进行全方面的剖析。笔记是用来方便我回顾与学习的,欢迎大家与我进行交流沟通,共同成长。不止是技术。
2020年02月06日22:43:09 - 记录学习过程
终于开始了。在学习这个之前,看了zhanglong老师的 java 8 和springboot
迫不及待了。先开始吧。
写在前边
论方法论
听说之前还有netty 和 kotlin 。学习风格就是,每一门课程之前,前两节课不进入主题,讲方法论。
从他人身上学习优点。加强自己的学习。从人去学习,从事去学习。我们只有亲身经历一件事情,才会产生自己的想法。从事情学习付出的成本会相对的高一点。只有一件事,你失败了,才会发现你存在什么问题。从过程中吸收一点经验,指导着你未来学习前进的方向。从人去学习来说,不是你自己亲身经历的,要学习辨别能力。为什么大家在看书的时候,看书的印象不如你自己操作的印象深刻呢?这些都是值得去思考的。更为高效的方式,还是看别人的故事,揣摩自己的人生。将别人拥有的技能转换成自己的技能,这样才是高效的学习。
学习的过程中,一定要做到两点。
- 输入。输入是必要的。
- 有输入,必定要有输出。记笔记,写博客,给别人去讲。
能给别人讲明白的前提是自己要明白。自己明白了,就一定能给别人讲明白了吗?你自己明白,给别人讲完之后,你自己也不明白了(值得反思一下,之前学习消耗的时间是否是高效的)。
每个人都值得去自己思考这个问题。为什么当时学习的时候学习的非常透彻,过一段时间跟没学过一样?产生这种问题的根源是什么呢?为什么会产生这样的问题?《后会无期》电影。它是没有标准答案的。之所以看了很多书,看了很多视频,看了很多资料,为什么没有产生持久化的效果?什么是持久化,他要最终落实到一个存储上边。在学习技术的时候,不断的在脑海中去过滤。在当时看的时候会认识的非常时刻。只有输入,没有输出,肯定保存的时间不会太长。什么是输出呢?在学习的过程中,一定要把输入的东西给吐出来,选择一种最适合你的输出方式。就像人呼吸,如果光吸气,不吐气,人早就憋死了。所以输入和输出一定是同时存在的。通过输出,把当时学习到的知识点,以文字,图表,思维导图的方式给呈现出来。做到这一点,你会很难忘掉你学过的重要的内容。做项目的过程也是很重要的输出过程,不经意间,就记住了。刻意练习,针对某一门技术有意为之。刻意输出,当你忘记的时候,回顾一下,会很快的回忆起你之前的学习过程。
即便你在多大的公司,你了解到的技术,用到的技术只占冰山一角。不要把个人绑死在一个上面。当你脱离当前工作时,你还有竞争力才是有用的。不能说项目用什么学什么。不用什么不学什么。那简直就不是程序员。学习技术,百里无一害。就算你现在用不到,你觉得重要的东西一定要去学,去输出。可以做一个实验。半天时间各学一个框架,一个记笔记,一个不记笔记。可以看一下实验结果。学习视频是一种很好的方式,不建议多次去重复的看视频。把视频中的技术要点吸收成你自己的东西才是重要的。视频不能用来检索。
印象笔记,有道云笔记,小小的工具,可能给你生活带来很大的改变。甚至改变你的一生。讲课是一件非常辛苦的事情。平时的工作中就深有体会。
论学习曲线
如果去学习JVM,每一个来学习JVM的人,都渴望成功。每一个Java开发人员的终极目标都是在日常生活中深入理解JVM的运行原理。JVM和平时的应用框架明显的区别,应用框架学习之后,可以直接拿来写项目了,就可以运行起来看到helloworld。然而对于JVM,是一个特别枯燥的事情。涵盖的内容太多了。本次根据java8来学习。必须要笔记。因为一扭头就会忘记。绝对没有速成的,突击的。有节奏,有计划的去学习。关于这门技术,范围太广,从通用的层面来进行学习。
推荐一些学习资料:一边学习视频,一边学习资料;
《深入理解Java虚拟机》 , 《深入Java虚拟机》 , R大
可能会遇到的问题:
- 学习了十几节课,感觉什么都没有学到。学到的东西在平时用不上。
- 学习完JVM之后,可能会感觉,学的越多,不知道的越多。大功已成。以前没听过的,现在听过了。
- 学习JVM,期间学习的文档全是英文的。阅读能力制约了你对JVM的学习。
- 耐心。任重而道远。看不到曙光的那种。没有案例让你来做。沉下心来,有的放矢的推进。
开发过程中遇到问题是常态。如果遇到了JVM崩溃,就算你拿到了日志,你也不能定位到问题是什么。对于原理,对于基础的学习,能够增强我们的自信心。
课程大纲
介绍:JVM是一个令人望而却步的领域,因为它博大精深,涉及到的内容与知识点非常之多。虽然Java开发者每天都在使用JVM,但对其有所研究并且研究深入的人却少之又少。然而,JVM的重要性却又是不言而喻的。基于JVM的各种动态与静态语言生态圈已经异常繁荣了,对JVM的运行机制有一定的了解不但可以提升我们的竞争力,还可以让我们在面对问题时能够沉着应对,加速问题的解决速度;同时还能够增强我们的自信心,让我们更加游刃有余。
- JVM介绍
- HotSpot虚拟机讲解
- 垃圾收集方式详解
- 垃圾收集算法详解
- 垃圾收集器详解
- 分代垃圾收集机制详解
- 新生代讲解
- 老年代讲解
- G1收集器分析与实例
- 常见且重要虚拟机参数示例
- 栈
- 方法区
- 线程共享内存区
- 根搜索算法
- Serial收集器
- ParNew收集器
- 类加载机制详解
- 类加载的双亲委托机制
- 字节码文件生成与分析
- 魔数
- 常量池与方法表
- 各种指令详解
- 锁详解
- 线程安全
- 偏向锁、自旋锁与轻量级锁
- JIT编译器
- GC日志生成与分析
- 虚拟机监控工具详解
- jConsole使用方式详解
- 何为逃逸与逃逸分析
- 方法内联
- 虚拟机内存模型详解
- = = = = = = = = = = = =
以前都不知道这些工具的存在:
jConsole
Jvusualvm
jmap
类加载
- 在Java代码中,类型的加载、连接与初始化过程都是在程序运行期间完成的。
(类型,并不代表类产生的对象,而是类本身。类型是在程序运行期间生成出来的,run time)
- 提供了更大的灵活性,增加了更多的可能性
(为有创意的开发者提供了很多的功能。)
类加载器深入剖析
- Java虚拟机与程序的生命周期
- 在如下几种情况下,Java虚拟机将结束生命周期
- 执行了System.exit()方法
- 程序正常执行结束
- 程序在执行过程中遇到了异常或者错误而异常终止
- 由于操作系统出现错误而导致Java虚拟机进行终止
类的加载、连接与初始化
- 加载: 查找并加载类的二进制数据
- 连接
- -验证:确保被加载的类的正确性
- -准备:为类的静态变量分配内存,并将其初始化为默认值
- -解析:把类中的符号引用转换为直接引用
- 初始化:为类的静态变量赋予正确的初始值
- 使用
- 卸载
图解:
- Java程序对类的使用方式可分为两种
- 主动使用(七种)
- 创建类的使用
- 访问某个类或者接口的静态变量,或者对该静态变量赋值
- 调动类的静态方法(助记符: getstatic putstatic invokestatic )
- 反射(如:Class.forName("com.test.Test"))
- 初始化一个类的子类
- Java虚拟机启动时被表明为启动类的类(Java Test)
- JDK1.7开始提供的动态语言支持
- 被动使用
- 除了以上七种主动使用的情况,其他使用Java类的方式都被看做是对类的被动使用,都不会导致类的初始化。
- 主动使用(七种)
- 所有Java虚拟机实现必须在每个类或者接口被Java程序“首次主动使用”时才初始化他们
类的加载
类的加载指的是将类的.class文件中二进制数据读入到内存中,将其放在运行时数据区内的方法去内,然后再内存中创建一个对象(规范并未说明Class对象谓语哪里,HotSpot虚拟机将其放在了方法去中)用来封装类在方法区内的数据结构
- 加载.class文件的方式
- 从本地系统中直接加载
- 通过网络下载.class文件
- 从zip,jar等归档文件中加载.class文件
- 从专有数据库中提取.class文件
- 将Java源文件动态编译为.class文件(动态代理,web开发jsp转成servlet)
查看类的加载信息,并打印出来。
jvm 参数介绍:
-XX:+
类加载基础概念
常量池的概念
反编译
反编译之后会有助记符。
助记符
- 表示将int、float或是String类型的常量值从常量池中推送至栈顶。
- 表示将单字节(-128~127)的常量值推送至栈顶。
- 表示将短整型(-32767~32768)的常量值推送至栈顶。
- 表示将int类型1推送至栈顶 (inconst_m1 ~inconst_5)。
- 表示创建一个引用类型(如类,接口,数组)的数组,并将其引用值推至栈顶。
- 表示创建一个指定的原始类型(如int,float,char等)的数组,并将其引用值推至栈顶。
类的初始化规则
接口的初始化规则
接口本身的成员变量 都是 public static final 的
类的初始化顺序
类加载的加载顺序

类加载器
类加载的概念回顾
- 类的加载
- 类的加载的最终产品是位于内存中的Class对象
- Class对象封装了类在方法去内的数据结构并且向Java程序员提供了访问方法区内的数据结构的接口。
- 有两种类型的类加载器
- Java虚拟机自带的加载器
- 根类加载器(BootStrap)(BootClassLoader)
- 扩展类加载器(Extension)(ExtClassLoader)
- 系统(应用)类加载器(System)(AppClassLoader)
- 用户自定义的类加载器
- Java.long.ClassLoader的子类
- 用户可以定制类的加载方式
- 类加载器并不需要等到某个类被“首次使用”时再加载它。
- jvm规范允许类加载器在预料某个类将要被使用时就预先加载他,如果在预先加载的过程中遇到了.class文件缺失或者存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)
- 如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误
依次执行初始化语句:按照CLass类文件中的顺序加载静态方法和静态代码块。

重点介绍一下上图的概念。举例说明。
- 在初始化一个接口时,并不会先初始化它的父接口。
调用CLassLoader类的loadCLass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
类加载器深度剖析
下图:不是继承关系,是包含关系。
线程上下文类加载器 作用:打破双亲委托机制。加载SPI提供的类。
类加载器的双亲委托机制
一层一层的 让父类去加载,最顶层父类不能加载往下数,依次类推。
能够成功加载自定义类加载器的系统类加载器 被称为。
所有能够返回Class对象引用的类加载器都被称为。
对于final的理解深入
类的初始化顺序举例练习
2020年02月09日09:41:01 继续吧。--- 今天是周末。原来说的要加班。不加了。学习
类加载器的层次关系
获得ClassLoader的途径
- 获得当前类的CLassLoader
clazz.getClassLoader();
- 获得当前线程上下文的ClassLoader
Thread.currnetThread().getContextClassLoader();
- 获得系统的ClassLoader
CLassLoader.getSystemClassLoader();
- 获得调用者的ClassLoader
DriberManager.getCallerClassLoader();
ClassLoader类源码剖析
自定义一个类加载器
类加载器重要方法详解
启动类加载器
扩展类加载器
应用类加载器
自定义类加载器
拒绝低效率的学习
案例结果如下:
类加载器的命名空间
- 每个类加载器都有 自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成
- 子加载器所加载的类能够访问父加载器所加载的类
- 父加载器加载的类无法访问子加载器所加载的类
- 在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
- 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类
输出结果一样,说明loader1 和loader2成为了父子关系,在同一命名空间中。
不同类加载器的命名空间关系
类的卸载
类的卸载举例:
也可以通过一个小工具,来查看 jvisualvm
自定义类加载器在复杂类加载情况下的运行分析
2020年02月10日21:36:38
对于类加载器,具体是从哪个地方加载的?
系统类加载器:sun.boot.class.path (加载系统的包,包含jdk核心库里的类)
扩展类加载器:java.ext.dirs(加载扩展jar包中的类)
应用类加载器:java.class.path(加载你编写的类,编译后的类)
可以打印出各个加载器加载的目录: System.getProperty("上述的地址");
类加载器命名空间总结
类加载器的双亲委托模型的好处:
- 可以确保Java核心库的类型安全:(jdk自带的rt.jar等)所有的Java应用都至少会引用Java.lang.Object类,也就是说,在运行期,这个类会被加载器到Java虚拟机中;如果这个加载过程是由Java应用自己提供的类加载器完成的,那么就很可能在jvm中存在多个Java.lang.Object类的版本,而且这些类之间还是不兼容的,相互不可见的(正是命名空间在发挥着作用)。借助于双亲委托机制,Java核心类库中的类的加载工作都是由启动类(根类)加载器来统一完成。从而确保了Java应用使用的都是同一个版本的Java核心类库,他们之间是相互兼容的。
- 可以确保Java核心类库所提供的类,不会被自定义的类所替代。
- 不同的类加载器可以被相同名称(binary name)的类创建额外的命名空间;相同名称的类可以并存在Java虚拟机中,只需要用不同的类加载器加载他们即可。不同类加载器所加载的类之间是不兼容的,这就相当于在Java虚拟机中创建了一个又一个相互隔离的Java类空间,这类技术在多框架中都得到了实际应用。
扩展类加载器类特性介绍
关于命名空间的补充:
在运行期,一个Java类是由该类的完全限定名(binary name,二进制名)和用于加载该类的定义类加载器共同确定的。
如果同样的名字(即相同的完全限定名)的类是由两个不同的加载器所加载,那么这些类就是不同的。即便.class文件的字节码完全一样,并且从相同的位置加载亦如此。
在Oracle的Hotspot实现中,系统属性sun.boot.class.path如果修改错了,则运行时会出错,信息下图:
类加载器的特例,数组。数组不是由类加载器的加载的,是由JVM加载的。
先有鸡还是先有蛋?
类加载器本身也是一个Java类,他们是怎么加载到JVM当中的呢?
他们是由启动类加载器加载的。BootStrapClassLoader加载的。他不是由Java代码写的,由C++代码编写的。启动的时候回自动加载出启动类加载器的实例。
- 内建与JVM中的启动类加载器会加载java.lang.ClassLoader以及其他的Java类加载器。
- 当JVM启动时,一块特殊的机器码会执行,它会加载类加载器与系统类加载器,这块特殊的机器码叫做启动类加载器(BootStarp)
- 启动类加载器并不是Java类,而其他的加载器则都是Java类
- 启动类加载器是特定于平台的机器指令,它负责开启整个加载过程
- 所有类加载器(除了启动类加载器)都被时限为Java类。不过,中国要有一个组件来加载第一个Java类加载器,从而让整个过程能够顺利的进行下去,加载第一个纯Java类加载器就是启动类加载器的职责。
- 启动类加载器还会负责加载供JRE正常运行所需要的基本组件,这包括java.util与java.lang包中的内容。
自定义系统类加载器
在自定义类加载器中加一个父类的构造方法,就可以正常的运行了。
用Java命令行的话,和idea里边执行的结果有时候类加载器的是不一样的。
因为idea也重新定义了类加载器的路径。
getSystemClassLoader();
grepcode.com 在线查看源代码的网站。
为什么扩展类加载的路径是java.ext.dirs 源代码里边写的。
Launcher();
Class.forName();
第一次使用这个方法的时候,是在学习JDBC的时候获取数据库连接。那个时候只用到了一个参数的构造方法
线程上下文类加载器
当前类加载器(Current ClassLadert):加载当前类的加载器。
每一个类都会尝试使用自身的类加载器(即加载自身的类加载器)来加载其他的类(指的是所依赖的类)。
如: 如果CLassx引用了ClassY,那么ClassX的类加载器就会去加载CLassY(前提是ClassY尚未被加载)
线程上下文类加载器(Context CLassloader)
线程上下文类加载器是从jdk1.2开始引入的,类Thread中的getContextClassLoader();与setContextClassLoadert();分别用来获取和设置上下文类加载器。
如果没有通过setContextCLassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器。Java应用运行时的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过该类加载器来加载类与资源。
线程上下文类加载器的重要性:
SPI(Service Proider Interface) 服务提供厂商;
父ClassLoader可以使用当前线程Thread.currentThread().getContextClassLoader()所指定的ClassLoader加载的类,这就改变了父ClassLoader不能使用子ClassLoader或者是其他没有直接父子关系的ClassLoader加载的类的情况,即这就改变了双亲委托模型。
线程上下文类加载器就是当前线程的Current ClassLoader。
在双亲委托模型下,类加载是由下至上的,即下层的类加载器会委托上层进行加载。但是对于SPI来说,有些类是由Java核心库提供的。而Java核心库是由启动类加载器来加载的,而这些接口的实现却来源于不同的jar包(不同的厂商提供),Java的启动类加载器是不会加载其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求,而通过给当前线程设置上下文类加载器,就可以由上下文类加载器来实现对于接口实现类的加载。
数据库厂商肯定会满足jdbc规范的接口。
Tomcat和spring的类加载机制和jdk的不同。
线程上下文类加载器的一般使用模式(获取-使用-还原)
至于为什么会输出两个jdbc的驱动;由下图所示。
听懂了,但是要不要记下来呢?? 源码里边都有,但是手速已经跟不上了。如果暂停下来去粘贴过来的话,会大量的浪费时间。所以这里就不粘贴过来了,认真的听吧。
通过jdbc驱动加载,深刻理解线程上下文类加载器的机制
分析一下 这两行代码底层是怎么实现的。已经都学习过了。
2020年02月11日23:12:09 。 睡觉。
2020年02月12日17:07:52
类加载器阶段性总结
总结回顾了一下之前学习的所有笔记中的内容。
论笔记的重要性。学习方式的升级。学习,遗忘,复习。笔记来做一个载体。
不要给后续留坑,前期欠的东西,以后总归是要还的。
技术学习与思维方式谈心
学习效率,学习方法,重要吗?
学习一个知识的时候太着急了。并不是一个视频学完了,这门技术就掌握了。
你学习的时候,目标是什么呢?把视频中讲解的内容给吸收理解了。
时间和效率两个因素来说,时间是固定的。我们只能从效率入手。
突击学习只会让你误以为你了解了这个技术。有节奏的学习。张龙老师的视频的准备和录制时间,是在工作之余准备和录制的。
再次复习一下之前学习的规程。
类加载器学习到此结束。开启一个新的阶段。2020年02月12日21:31:10
Java字节码
.class字节码文件是跨平台的。
Java字节码文件介绍
Java字节码文件的结构及组成部分的学习。
javap -c com.erwa.jvm.MyTest1
Javap -verbose com.erwa.jvm.MyTest1 -- 返回class文件的冗余内容
.class字节码文件中的内容都是什么?待学习 Hex_Fiend工具
完整的Java字节码结构
上下两个表相互对应。
Java字节码基础概念
常量池(constant pool)
- 使用java -verbose命令分析一个字节码文件时,将会分析该字节文件的魔数,版本号,常量池,类信息,类的构造方法,类中的信息,类变量与成员变量等信息。
- :所有的.class字节码文件的前4个字节都是魔数,魔数值为固定值:0xCAFEBABE。
- 魔数之后的4个字节为版本信息,前两个字节表示minor version(次版本号),major versio(主版本号)。这里的00 00 00 34 ,换算成十进制,表示次版本号为0,主版本号为52。所以,该文件的版本信息就是1.8.0。可以通过java -version 命令查看一下Java版本号。
- (constant pool): 紧接着主版本号之后的就是常量池入口。一个Java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是class文件的资源仓库,比如说Java类中的定义的方法与变量信息,都是存储在常量池中。常量池中主要存储两类常量:字面量与符号引用。
- 字面量:如文本字符串,Java中声明final的常量等。
- 符号引用:如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符等。
- 的总体结构:Java类所对应的常量池主要由常量池数量与常量池数组这两部分共同构成。常量池数量紧跟在主版本号后边,占据2个字节;而常量池数组紧跟在常量池数量之后。常量池数组与一般的数组不同的是,常量池数组中不同的元素的类型、结构都是不同的,长度当然也就不同。但是,每一种元素的第一个数据都是一个u1类型。该字节是一个标志位,占据1个字节。JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。值得注意的是,常量池数组中元素的个数=常量池数-1 (其中0暂时不使用)。目的是为了满足某些常量池索引值的数据在特定情况下需要表达「不引用任何一个常量池」的含义;其根本在于,索引0也是一个常量(保留常量),只不过他不位于常量表中,这个常量就对应null;所以常量池的索引是从1而非0开始的。
常量池中11中数据类型的结构总表:
- 在JVM规范中,每个变量/字段都有描述信息,描述信息主要的作用是描述字段的数据类型、方法的参数列表(包括数量,类型与顺序)与返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符来表示,对象类型则使用字符L加对象的全限定名称来表示。为了压缩字节码文件的体积,对于基本数据类型,JVM都使用一个大写字母来表示,如下所示:B - byte ; C - char ; D - double ; F - float;I - int ; J -long ; S - short ; Z - boolean ; V - void ;L -对象类型,如:Ljava/lang/String.
- 对于数组类型来说,每一个维度使用一个前置的[来表示,如int[]被记录为I[]。 String[] [] 被记录为[[Ljava/lang/String;
- 用描述符描述方法时,按照先参数列表,后返回值的顺序来描述。参数列表按照参数的严格顺序放在一组()之内,如方法:String getRealnameByIdAndNickname(int id,String name)的描述符为:(I,Ljava/lang/String;)Ljava/lang/String;
Java是一个单继承,多实现的语言。
访问标志(Access_Flag)
0x0002 private
字段表集合(field_info)
字段表结构:
方法表集合(method_info)
方法表结构:
方法表结构实例:
code结构(code attribute):每一个方法的结构内容
java核心基础584讲解
code_length : 之后的字节 会转换成 助记符
附加属性
LineNumberTable : 源代码和行号的对应关系。方便抛异常时,定义出错的位置。
在字节码文件中。 this 属性是默认当做第一个参数给传入进去的。
字节码查看工具 的GitHub地址
idea也有插件。 jclasslib
字节码反编译练习
加上synchronized关键字字节码
为什么除了访问权限多了synchronized之外,其他的地方都一样。
因为synchronized使用的方式有多种:
- 如果synchronized用在实例方法上,表示对此方法加了一个锁。
- 如果用在了对象上,则字节码会生成锁的入口和出口。
- synchronized用在类中的静态方法上, 其实是给当前的类的对象上的锁。
关于锁:能不用synchronized就不用。使用轻量级的 lock 。
构造方法和静态代码块字节码
- 如果没有设置构造方法,会默认生成一个无参构造。如果有静态方法,会初始化一个clinit的方法。
- 全局变量是在构造方法初始化的时候完成赋值的。
- 一个构造方法对应一个init方法。(全局变量的赋值,会在每个init方法中都重新执行一次)
- 所有的静态变量和静态代码块赋值都是在clinit中执行的。
字节码对this关键字和异常的处理
test()中的Code中三个参数的说明:
stack=3 ;"最大栈深度" (对于方法栈,压栈弹出。)
locals =4 ; "成员变量的个数" (局部变量有: this,is ,serverSocket , ex (同时只能有一个异常的参数))
agrs_size=1 "参数的个数“(第一个默认的都是this)
LineNumberTable 是 字节码和源代码对应的行号
LocalVariableTabel 是 对于局部变量的存储信息
StackMapTable 是针对于安全的。
ByteCode内容:
goto 语句是发生异常的时候进行跳转到catch的位置。
Exception table 信息:
最后的一个any来说,是处理所有上边不可能处理的异常。是字节码生成的时候帮助我们生成的。
字节码的运行机制
字节码基础概念
栈帧(stack frame)
- 栈帧是由栈和帧组合而成的概念。
栈帧一种用于帮助虚拟机执行方法调用与方法执行的数据结构。他是独立于线程的,一个线程有自己的一个栈帧。不存在并发的情况。
栈帧本是一种数据结构,封装了方法的局部变量表、动态链接信息、方法的返回地址以及操作数栈等信息。
- java中的引入的概念:符号引用:直接引用。
有些符号引用是在类加载阶段或是第一次使用时就会转换成直接引用,这种转换叫做静态解析;
另外一些符号引用则是在每次运行期转换为直接引用,这种转换叫做动态链接,这种体检为Java的多态性。
如: Animal a = new Cat();
a.sleep(); //实际上应该调用Cat()的sleep();
a = new Dog();
a.sleep(); //实际上应该调用Dog()的sleep();
a = new Tiger();
a.sleep(); //实际上应该调用Tiger()的sleep();
但是在程序编译的时候,字节码能看到的是 a调用的都是Animal的sleep();
应用了invokevirtual的概念。
入栈,出栈
结合上图,我用文字描述一下两个概念。看自己能否理解。
首先将2和3压入栈中。现在要做一个减法,3-2 。 需要先把2从栈中取出,3也从栈中取出,做完减法后,把1压入栈中。
局部变量表的概念(slot)
invoke 的概念(调用)
- invokeinterface : 调用接口中的方法,实际上是在运行期决定的,决定到底调用实现该接口的哪个对象的特定方法。
- invokestatic : 调用静态方法。
- invokespecial : 调用自己的私有方法、构造方法(
)以及父类的方法。 - invokevirtual : 调用虚方法、运行期动态查找的过程。
- Invokedynamic: 动态调用方法。
方法重载(方法的静态分派机制)
方法重写(方法的动态分派机制)
new 关键字的作用: 1、开辟一个内存空间,2、调用构造方法 3、赋值给局部变量
虚方法表与动态分派机制详解
针对于方法调用动态分派的过程,虚拟机会在类的方法区建立一个虚方法表的数据结构。(virtual method table ,简称 vtable),是在加载连接阶段完成的加载。
针对于invokeinterface指令来说,虚拟机会建立一个叫做接口范发表的数据结构。(interface method table , 简称itable)
输出结果:
animal str
Dog date
子类和父类的相同的方法 元素的索引号是一样的。这样会提高性能和效率。
基于栈的指令集与基于寄存器的指令集详细对比
透过字节码生成审视Java动态代理运作机制
java字节码总结
不要把源代码和字节码混为一谈,他们没有相关的对应关系。
2020年02月15日23:00:50 。 回顾完毕。晚安。
JVM内存
JVM内存空间划分与作用
引用指向的对象并不是目标本身,而是一个指针指向对象的实例数据,另一个指针指向方法区中的类型数据(元数据)。如图 所示。Oracle的JVM采用下方的引用方式(使用直接指针的方式)。
使用句柄的好处(引用永远指向他,引用不会跟着修改)
直接指针的好处(在压缩的时候效率比句柄的方式更高)
Java对象内存分配原理与布局
堆溢出、栈溢出、方法区溢出实例练习
堆溢出举例,实战jvisualvm
对应的JVM的运行信息:类,实例数等信息都可以在这里通过工具查看。
我这里jdk8带的jvisualvm打不开,没有跟着练习看一下。
线程栈溢出监控与Jconsole 工具使用介绍
Jvisualvm 监控栈溢出过程。
Jconsole 工具 监控。 也是Oracle自带的。
线程死锁检测与分析工具深度学习
使用Jconsole检测死锁的结果。
使用jvisualvm工具检测的结果:
方法区产生内存溢出错误
使用jconsole来进行检测:
使用jvisoualvm进行检测: 能够明确的看到元空间使用了多少M
元空间的概念
原文链接:https://www.infoq.cn/article/Java-PERMGEN-Removed/
适当监控工具的实践
存在两种工具。(图示化工具能看到的,命令行都可以得到。)
- 命令行工具 : jmap ; jstat ; jcmd ;jhat
- GUI工具: jconsole ; jvisoualvm ;jmc
jmap 工具介绍
jstat 工具介绍
上图中的MC的作用:当前的元空间的容量
MU:元空间已经被使用的容量
jcmd 命令介绍和 jps命令
jps 命令: 查看当前左右线程的pid .
jcmd 和 jps 配合使用,查看JVM的多种信息 。 PID等
jcmd (从jdk1.7开始新增的命令)命令介绍:
- jcmd pid VM.flags : 查看JVM的启动参数
- jcmd pid help : 列出当前运行的Java进程可以进行的操作。
- jcmd pid help JFR.dump: 查看当前命令的具体操作含义
- jcmd pid PerfCounter.pring: 查看JVM性能相关的参数
- jcmd pid VM.uptime: 查看 JVM的启动时长
- jcmd pid GC.class_histogram : 查看系统当中类的统计信息
- jcmd pid Thread.print : 查看当前线程的堆栈信息。
- jcmd pid GC.head_dump path : 查看当前堆的信息,并打印到path路径
- jcmd pid VM.system_properties : 查看JVM的属性信息。
- jcmd pid VM.version: 查看目标进行JVM版本的信息。
- jcmd pid VM.command_line : 命令行的参数
jstack pid : 查看或者导出当前线程的堆栈信息。
命令行:jps : 查看PID
jcmd 和 jps 配合使用,查看JVM的多种信息
jmc命令 - 可视化工具
jmc : JAVA Mission Conreol
jfr : java flight recodrding 飞行记录器
我本地不能打开。
jhat 对于堆转储的分析
功能和visualvm类似。涉及到了OQL 对象查询语言,随用随查即可。
来一个系统性的总结与回顾
- ClassLoader
- Java字节码
- JVM的内存空间
2020年02月17日20:58:02 复习结束。开始新的课程啦。
垃圾回收
垃圾回收基础概念介绍
垃圾回收最主要的是在堆中进行的。- - 了解了垃圾回收的原理,才能写出来更高效的代码。
JVM运行时内存数据区域
个人理解:
灰**域为线程共享的,白**域为线程隔离。
方法区:存放元数据信息。常量池空间就在这个里边。
本地方法栈:存放navtion的,非Java代表编写的本地方法。
Java虚拟机栈(栈帧):存放对象的引用
堆:创建的对象存储在这里边。
JVM垃圾回收(GC)模型
垃圾判断的算法
引用计数算法
什么是无法解决对象循环引用的问题。
已经没有外部的引用引用这两个对象了,是一个完全孤立的系统,但是这个两个对象又相互引用。所以这两个对象一直无法被回收。
根搜索算法
方法区
JVM常见GC算法
每一种算法都有自己的好处与缺点
新生代,一般使用复制算法。
老年代,一般使用标记算法。
(选取算法的根据是 1、生命周期 2、有一个老年代作为分配担保的后盾 )
标记-清除算法
上图中有引用的如下图所示,绿色的是垃圾回收时不会回收的,红色的是被回收的内容。
执行完一次垃圾回收之后,结果如下图所示。
复制收集算法
复制后的样子:
垃圾回收后的样子
标记-整理算法
示意图:
分代收集算法
2020年02月17日22:05:23 这个时候发生了一个有趣的问题,原本把笔记全部给记录到一块,现在地下的时候卡的要死,现在重新创建了一个文件,完全没有一点问题了。
永久代从jdk8之后就被替换成了元空间。用来存放元信息。
内存结构、分配与回收
内存结构
内存分配
内存回收
强引用 ;软引用 ;弱引用 ; 虚引用
scavenge : 打扫
Full : 完全的。(会导致FW的出现。占用业务线程。 要在开发中完全避免这种GC);
垃圾回收器
Serual收集器
serial: 连续的、
ParNew收集器
Parallel Scavenge 收集器
parallel : 平行的 、 scavenge : 打扫
Serial Old 收集器
Parallel Old 收集器
CMS(Concurrent Mark Sweep)收集器
GC垃圾收集器的JVM参数定义
JAVA内存泄漏的经典原因
对象定义在错误的范围
下图中的names的定义,定义为全局变量,但是只使用了一次。全局变量不会被立马被回收,但是局部变量会立马被回收的。
异常处理不当
集合数据管理不当
垃圾回收日志与算法深度解读
新生代与老年代垃圾收集器实现详解
JVM的默认参数 查看: java -XX:+PrintCommandLineFlags -version
UseParallelGC : 默认的是使用ParallelGC
阈值和垃圾收集器类型对于对象分配的影响实践分析
MaxtenuringThreshold与阈值的动态调整详解
CMS垃圾回收器(新版本不建议使用)
CMS 垃圾回收器,复杂性比较高。在jdk新版本中不建议被使用。
CMS(Concurrent Mark Sweep):并发标记清除
枚举根节点概念
安全点(Safepoint)概念
程序执行时,并非在所有地方都能停顿下来开始GC,只有在达到安全点时才能暂停。
安全点的选定基本上是以“是否具有让程序长时间执行的特征”为标准来进行选定的。
主动式中断:轮询标志的地方和安全点是重合的。
安全区域(Safe Region)概念
CMS垃圾收集器深入详解
CMS收集器,以获取为目标,多数应用于互联网站或者BS系统的服务器端上。
初始标记的时候,会Stop the world , 此时没有用户线程执行。
并发标记的时候,可以与用户线程同时执行。
重新标记的时候,会Stop the world , 此时也没有用户线程执行。
并发清除的时候,可以与用户线程同时执行。
停顿,停顿的是用户线程停顿的少。
CMS收集器收集步骤

- 初始标记
- 并发标记
- 并发预先清理阶段
- 并发可丢弃的阶段
- 最终的重新标记
- 并发清除
- 并发重置
出现Concurrent即代表GC线程可以和用户线程同步执行的阶段。
将标记拆分成了前5个阶段。
举例分析CMS垃圾收集器执行过程
G1(Gerbage First Collector)(目前被主流使用)
从JDK9开始,G1已经成为了默认的垃圾收集器。
物理内存中的新生代与老年代被消除。
系统吞吐量几个重要参数:QPS(TPS)、并发数、响应时间
QPS(TPS):每秒钟request/事务 数量
并发数: 系统同时处理的request/事务数
响应时间: 一般取平均响应时间
G1有很好的吞吐量和响应能力。
G1收集器的设计目标
强调: GC停顿时间更加可控。
G1可以回收部分老年代,CMS要回收全部的老年代。
所以说G1在停顿时间上更可控。
哇,竟然都学过了。图中的内容也都知道,但是刚拿到这个图的时候不敢去看他。
execution engine : 执行引擎
JIT compiler :即时编译器。
internal : 内部的 native : 本地的
G1收集器堆结构划分
整个堆空间,不做块的区分。就当成是一个堆空间。每一个空间的使用情况都可能是三种情况之一。
对每种分代内存的大小,可以动态变化。
G1 vs CMS
这些差异,源于G1对物理模型设计的变化。
G1重要概念
分区(Region)
G1 : 垃圾优先。 即首先收集垃圾最多的分区。
新生代满的时候,G1 对整个新生代进行回收。
收集集合(CSet)
CSet:一组将要被回收的空间的集合。
已记忆集合(RSet)
RSet 存在的原理及价值。如上图所示,记录了Region中的引用关系。使得垃圾收集器不需要扫描整个堆找谁引用了当前分区中的对象,只需要扫描RSet即可。
Points-into :代表谁指向了我;
Point-out: 我指向了谁
SATB(Snapshot-At-The-Beginning)起始快照
G1垃圾收集器官方文档解读
https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html
G1垃圾收集器深度理论讲解
G1 是将来Oracle HotSpot的未来。
又开始卡了,图片放的太多了。哈哈哈哈。快结束了,坚持住。
G1的适合场景
G1 GC模式
global concurrent marking 全局并行标记
G1只有 Young GC 和 Mixed GC
G1是不提供Full GC的
global concurrent marking
G1 在运行过程中的主要模式
并发阶段,指的是全局并发标记的阶段
混合模式,指的是 Mixed GC
Mixed GC的参数详解
什么时候发生Mixed GC
由一些参数控制,另外也控制着哪些老年代Region会被选入CSet当中
垃圾占比已经超过此参数设定的值
上图描述了RSet的记录情况,存放各个引用关系。
RSet其实是一个Hash Tabella,key 是引用它的起始地址,value是一个集合,元素是card table 的index。
混合GC, 不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区。
三色标记算法(黑白灰)
在进行并发标记时出现的标记错误的情况。而出来的算法
被标记,意思是可达的对象。被标记的对象都不应该被回收掉,都不是垃圾。
没有被标记的才是垃圾。
三色标记算法在并发情况下的漏标问题分析。
此时。C也不应该被垃圾回收。
SATB
灰色移除的目标标记为灰色。黑色引用新产生的对象标记为黑色
误标没什么关系,但是漏标的结果是致命的,影响线程的正确性。
针对于是上一个问题 的解决方案
G1的收集模式
G1的**实践
暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度,最终退化成Full GC
每天写业务代码,怎么能和别人拉开距离。
确定了一个待研究的主题,对这个主题进行全方面的剖析。高注意力的研究一个主题。
G1回收器日志内容详细分析
两个问题。
- 在Java当中,一个全局变量会被初始化几次。 (问的问题不对。Java当中承担全局变量的是静态变量)> 静态变量只会被初始化一次,在类加载器初始化的时候,静态变量会被首先初始化。
- Java当中的泛型是怎么实现的。
强引用,软引用,弱引用,虚引用等概念。
https://www.cnblogs.com/yngjian/p/12059428.html
基础的重要性。
谁都会忘,忘了去复习就好了。自己复习自己的笔记,能够瞬间勾起你的回忆。
学习,为什么不可以去看官方文档学习呢?看不懂英文的吗?
要知道,老师们讲的顺序都是按照官网的顺序来的。
总结,复习。有来有回。复盘。
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/18887.html