本人已参与新人创作礼活动,一起开启掘金创作之路
想卷入java这条道的兄弟姐妹们,肯定也了解过,“JVM”、“多线程与高并发”这些犀利词句在找工作面试中的重要性,以及为找到工作之后个人今后的发展打一个坚实基础中的重要性等等。可能大家和我刚开始一样,那我该啥时候学呢?
- 刚开始学java基础的时候吗?
- 变量命名:小驼峰、大驼峰、java关键字巴拉巴拉.....
- 方法的定义、方法重载、方法重写....
- 面向对象封装、继承、多态......
- ......
- 学完基础(有的时候会先学一些javaweb相关知识,servlet或者jsp,有可能有的路线啥的会夹杂着数据库等等)?
- 学框架时Spring、SpringMVC、Mybatis、SpringBoot......
- 面试前三四个月......
突然,感觉有点慌了,咋这么多要学呢..... 别慌喽,还有数据结构与算法、计算机网络(TCP\IP你不学、分层协议你不学)、OS(进程线程你不学)? 其实呀,我个人感觉,关于什么时候学什么---------最合适的答案就是有的东西可能一遍学不会 所以呀,刚开始有个印象,我要学这和那,然后慢慢看书看博客看视频等等,然后形成自己的关于这个知识的相对完整的知识体系,一步一步慢慢奏大奏强,不断扩展。 所以我自己的这篇博客,就是和大家分享一下自己的JVM的知识体系,当然啦,不能说把我的笔记拍个照片扔在这里人就跑了,这肯定是不行的,我会尽量用自己的话把自己学到的知识点讲出来,巩固自己的同时希望只言片语能够为大家带来一丝曙光。好了,叨叨叨了半天,正题开始。
JVM_Part1 (贯彻自己一贯风格,用自己的话讲自己的知识框图给你听) 可以先给大家看一下咱要讲啥:
详细的图又需要的,评论区留言,后面发给你
图中JVM大体上包含两个子系统和两个组件:
- 两个子系统:
- ClassLoader(类加载):根据给定的全限定名类名(包名)来装载存储字节码的class文件到Runtime data area中的方法区
- Execution engine(执行引擎):执行classes中的指令。因为字节码文件只是JVM的一套指令集规范,并不能被OS直接执行,所以需要特定的命令解析器执行引擎来将字节码翻译为底层OS指令,再交给CPU去执行,而这个过程中需要调用其他语言的本地库接口来实现。
- 两个组件为:
- Runtime data area(运行时数据区):JVM的内存
- Native Interface(本地接口):通过本地方法库用来与其他语言进行交互的接口
咱们玩java,不管咱夹杂设计模式,设计原则,还是用各种各样炫酷的框架美化自己的代码,总会逃脱不了一个事:在此之前,先瞅瞅Java的一次编写到处运行,到底是什么意思
1.建一个Xxx.java(也就是建一个java类么)
- 这里之前咱们学的时候肯定都是先记事本写,再黑窗口上面写
- 或者直接上eclipse,IDEA上面编
2.建好一个java类,对吧,然后干嘛。---更具体的看这篇
- 实例化出这个类的对象,然后调用这个类中的属性和方法实现咱们的功能。
- 后来学了知道,有个叫单例模式的玩意,也可以不通过new来造出一个对象。这其实没啥,大家知道,咱们能够通过new关键字实例化出一个类的对象,就是因为构造器(这个构造器,相当于一个方法,只不过没有返回值,也不写void,而且这个方法的名字和类名一模一样)是公开的(也就是他的权限修饰符为public)。如果把这个public改成private,那就不能new去实例化了,不过说到底还是调用一个方法,让这个方法返回一个对象给咱们用:就是在叫外卖。喂,给我送一个对象过来。(如果大家实在想钻研单例模式,head first设计模式,好好啃啃吧)
- Java对象创建过程
- JVM遇到一条新建对象的指令时首先去检查这个指令的参数是否能在常量池中定义到一个类的符号引用。然后加载这个类
- 为对象分配内存。一种办法“指针碰撞”、一种办法“空闲列表”,最终常用的办法“本地线程缓冲分配(TLAB)”
- 将除对象头外的对象内存空间初始化为0
- 对对象头进行必要设置
3.咱们写好的Xxx.java文件会被编译器编译为Xxx.class字节码文件(其实是:存储字节码的Class文件)---更具体的见这篇,存储字节码的Class文件的前世今生
- 从上面的一次编译到处运行中的语言无关性能看得出来,不止Java,
只要你哪种语言敢通过自己对应的编译器把自己编译成为Class文件,我JVM就敢在不同平台上执行你 - Java编译器并非一个一个的编译Java文件,而是将所有编译单元的语法树顶级节点输入到待处理列表后再进行编译,因此各个文件之间能够互相提供符号信息,无需使用处理器
- 另外说明一点,Class文件应当是一串二进制的字节流
到这里可以拓展一点:
- Javac编译器本身就是一个由Java语言编写的程序,如上图,就是来把.java文件转变为.class文件的过程**。之所以把Javac这类将Java代码转变为字节码的编译器叫做前端编译器是因为他只完成了从程序到抽象语法树或中间字节码的生成。在此之后还有一组内置于虚拟机内部的后端编译器完成了从字节码到本地机器码的过程
- 编译过程大致可分为3个过程:
- 解析与填充符号表过程:解析步骤由parseFiles()方法完成,解析步骤包括经典程序编译原理中的
词法分析和语法分析两个过程- 词法分析:词法分析是将源代码的字符流转变为Token标记集合(单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素)。比如 int a = b + 2这句代码中包含了6个标记,分别是int、a、=、b、+、2
- 语法分析是根据Token标记序列构造抽象语法树的过程(抽象语法树Abstract Syntax Tree,AST,是一种用来描述程序代码语法结构的树形表示方式,**语法树的每一个节点都代表着程序代码中的一个语法结构,**比如包、类型、修饰符、运算符、接口、返回值等。语法树能表示一个结构正确的源程序的抽象但无法保证源程序是符合逻辑的)
- 填充符号表:符号表是由一组符号地址和符号信息构成的表格,其实和哈希表中的K-V差球不多
- 词法分析:词法分析是将源代码的字符流转变为Token标记集合(单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素)。比如 int a = b + 2这句代码中包含了6个标记,分别是int、a、=、b、+、2
- 插入式注解处理器的注解处理过程
- 注解是在运行期间发挥作用的
- 分析与字节码生成过程
- 字节码生成是Javac编译过程的最后一个阶段,字节码生成阶段不仅仅是把前面各个步骤生成的信息(语法树、符号表)转化成为字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作
- 语义分析相当于给抽象语法树打补丁擦屁股,语法分析的主要任务是对结构上正确的源程序进行上下文有关性质的审查,比如进行类型审查
- 编译器实现了用来提升效率的一些小把戏~语法糖,见这篇
- 字节码生成是Javac编译过程的最后一个阶段,字节码生成阶段不仅仅是把前面各个步骤生成的信息(语法树、符号表)转化成为字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作
- 解析与填充符号表过程:解析步骤由parseFiles()方法完成,解析步骤包括经典程序编译原理中的
上面除了稍微介绍的前端编译器和后端编译器,还有个JIT编译器
- JIT编译器:当虚拟机发现某个方法或者代码块运行特别频繁时就会把这部分代码认定为热点代码。为了提高热点代码的执行效率在运行时虚拟机将会把这些代码编译成为与本地平台有关的机器码并进行各种层次的优化。完成这个任务的编译器叫做即时编译器,也就是JIT编译器
4.然后Xxx.class字节码文件会被放入类加载器中进行加载(加载到内存中,也就是放在运行时数据区域的方法区中)并初始化(也就是说**在Class文件中存储的各种信息最终都需要加载到虚拟机中才能运行和使用**),这里就得深究一下加载器了,
- Java代码在进行Javac编译时是在虚拟机加载Class文件时进行动态连接
- 也就是说Class文件中不会保存各个方法、字段的最终内存布局信息(因为这些字段、方法不经过运行期转换的话无法得到真正的内存入口地址,也就无法被虚拟机使用)
5.类加载完成后,接着会在Java堆中划分一块内存分配给对象。内存分配根据Java堆是否规整,有两种方式:
- 指针碰撞:
- 如果Java堆的内存是规整,即所有用过的内存放在一边,而空闲的的放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样便完成分配内存工作。
- 空闲列表:
- 如果Java堆的内存不是规整的,则需要由虚拟机维护一个列表来记录那些内存是可用的,这样在分配的时候可以从列表中查询到足够大的内存分配给对象,并在分配后更新列表记录。
另外,
- 分配内存空间的动作进行同步处理(采用 CAS + 失败重试来保障更新操作的原子性);
- 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 时,才需要同步锁。通过-XX:+/-UserTLAB参数来设定虚拟机是否使用TLAB。
除了上面有关加载器的分类作用(什么扩展,根等等等)这个问题,还有下面几个问题大家在一块学加载器时要自己心里有数:
- 听说加载器这个有个啥双亲委派机制,啥玩意讲讲?
- 我之前听过有个classloader,这体现在程序中咋得到呢,打印出来我看看?
- 类加载器和字节码文件有什么关系,你把对象、类、类加载器三个能不能用Java程序给我打印个啥出来我看看,怎么互相之间有联系?
- 类加载完了后到哪里了呢,这个可以提前给大家剧透一下,下来可以说说JVM里面所谓的内存了,栈在哪?堆在哪?方法区在哪?本地方法栈在哪?
- 在哪后你不知道他里面放的是啥,他是用来干啥的能行?
- 后面就知道你里面放啥,有个东西里面放对象,你放对象放着放着,有些对象就成垃圾了,可不得清理?
- 清理谁里面的垃圾?用啥清理?怎么清理?
- 这不就垃圾回收喽吧啦吧啦
今天开个小头,明天,咱们同一时间再见
赠品:
- 深拷贝和浅拷贝:
- 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存
- 使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
- 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址
- 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存
- 深复制和浅复制
- 浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
- 深复制:在计算机中开辟一块新的内存地址用于存放复制的对象
巨人的肩膀: 深入理解Java虚拟机 师兄斌子哥的面经