学习笔记系列开头惯例发布一些寻亲消息
链接:https://baobeihuijia.com/bbhj/contents/3/196593.html
JVM(Write Once,Run Anywhere)
- 以下是一些学习时有用到的资料,只学习了JVM的基础知识,对JVM整体进行了解
(5 封私信 / 80 条消息) Java JVM怎么学习啊?从哪方面入手? - 知乎
(zhihu.com)JVM入门教程开篇:为什么要学虚拟机? - 陈树义 - 博客园
(cnblogs.com)【精选】Java | JVM | 详细图解,坚持看完,带你真正搞懂Java虚拟机_java jvm
图-CSDN博客抽象语法树AST的全面解析(一)_语法树解析_嘿嘿帆的博客-CSDN博客
可移植性的两个层次
- 源代码级可移植性和二进制代码的可移植。 大部分的语言都支持源代码级的可移植性(编译器是平台有关的) JAVA语言不仅支持源码可移植性,其源代码编译之后生成的字节码同样具有可移植性。【理解:系统能够识别是编译后形成的二进制文件,操作系统有一套对应的机制能够识别出来,比如对应于0011,win操作系统认为是+,但是放到linux下可能就认为是非法操作,所以需要“编译”+“识别”要成对,而对于java代码,我们的编译和识别是不需要成对的,因为java不直接生成给系统看的二进制代码,而是先生成字节码再通过jvm匹配不同的系统。】
java到机器码流程
- java并不是一上来直接把代码编译为系统识别的机器码,而是编译为一种特定的语言规范——字节码,java虚拟机解析字节码内容,将其翻译为操作系统能理解的机器码
- javac: 将源代码翻译为字节码**(前端编译器)**——>16 进制数据流 / javap可以反编译
- 将源码的字符流转化为token流,构建抽象语法树(AST)
- 将定义的符号信息输入到符号表(符号地址和符号信息)
- 注解处理:对生成的语法树进行增删改查,还是要进行token生成和符号表新增,重复直到新增注解后语法树没有修改
- 语法分析
- 语义分析:使用前是否声明/路径的返回值/受查异常被正确处理
- 解语法糖:自动拆箱-还原简单的数据结构
- 生成字节码(将语法树和符号表转为字节码.class)
- 字节码到机器码(java解释器和JIT编译混用)
- java解释器:直接执行字节码,调用系统(启动快但是过程慢)
- JIT编译器:将字节码转化为本地机器码(启动慢过程很快)
- c1编译模式:进行简单、可靠的优化,如有必要将加入性能监控的逻辑
- c2编译模式:会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。
- AOT编译器:是一种由源代码直接到机器码的方法,但是对于java 的动态特性并不友好,所以是一种牺牲质量换取性能的策略,如动态类加载无法实现
- javac: 将源代码翻译为字节码**(前端编译器)**——>16 进制数据流 / javap可以反编译
对字节码进行深入理解
-
源代码
-
常量池
-
类索引:地址指向了常量池中的Demo类
-
父类索引:地址指向常量池的object类
-
接口索引:接口计数器+接口信息
-
字段表集合:类级变量、实例级变量(不包括类中的局部变量)/ 同样是字段计数器+属性数据
-
方法表索引:方法计数器+方法表
- public访问修饰符 + 方法名的索引,指向了常量池的 + 属性表计数器+属性表
- 属性表名code+属性长度+操作数栈深度的最大值+为局部变量分配的存储空间+字节码指令(通过查询字节码指令表可以知道,类似于汇编)+异常表长度+又来一个属性表放的是line_number_info表,其中包含了start_pc(字节码行号)+ line_number(java源码的行号)
- public访问修饰符 + 方法名的索引,指向了常量池的 + 属性表计数器+属性表
-
属性表(是类层次的属性,其中包含了该字节码文件的源文件即demo.java)
运行时数据区(java虚拟机的内存)【java类加载到java内存中,类信息存在方法区,创建对象存在堆中,调用方法就开启线程】
-
公有(所有线程共享)
- java堆
- 存储java实例对象的内存分配
- 方法区
- 存储java类字节码数据,存储每一个类的结构信息如运行时常量池、字段和方法数据
- 常量池也在里边
- java堆
-
私有(线程私有)
- PC寄存器:线程正在执行的字节码指令地址
- java虚拟机栈:存储局部变量,操作数栈
- 本地方法栈:。。。
类加载机制(即字节码如何翻译)【遇到static单独开出去内存,不会每次new 对象都新开内存】有几个例子要多看
-
加载:将字节码加载到内存,在方法区创建对应的class对象
-
校验:对字节码文件继续校验,是否规范
-
为类变量(static修饰的变量)分配内存,并按照
- 变量的话:java语言的数据零值来赋值【注意不是代码里边的值】
- 常量final的话:直接就是用户代码给出的值
-
解析:将常量池中的符号引用替换为直接的内存地址
-
初始化:new/读取或者设置一个类的static(被final修饰、或者在编译器把结果放入常量池的静态字段除外)/调用一个类的静态方法
-
从入口程序开始执行
-
执行结束后,jvm销毁创建的class对象,最后JVM也退出内存
重点,先类初始化,结束后就对象初始化,如果没有实例化,只是因为main入口或者用了其中的static或者因为子类初始化的时候父类还没有初始化,那么只会类初始化不会对象初始化,understand!!!类初始化只有一次
- 类初始化的细节(jvm看到方法区的字节码其实分不清构造函数啥的)【是先类初始化后对象初始化】
- 类初始化:类变量的赋值语句、静态代码块的执行
- 对象初始化(实例化之后才会执行):赋值语句,普通代码块,最后才是构造函数代码【按照我们平时构造函数中可以调用对象字段就可以推测出来】
垃圾回收机制
-
判断垃圾:GC Root Tracing 算法:从 GC Root 出发,所有可达的对象都是存活的对象,而所有不可达的对象都是垃圾。为什么不使用引用计数:是因为引用计数会存在循环引用的问题
-
三种垃圾回收机制
-
标记清除
-
复制算法
-
标记压缩
-
-
堆区的回收总的来说分为年轻代和老年代 1:3
-
【年轻:存活率低,复制算法;老年:存活率高,标记-压缩方法】
- 年轻代用复制:年轻代分为Eden(伊甸园),From Survivor 0(幸存0区),To Survivor 1(幸存1区)
- 分区比例8:1:1,这么分区是因为ibm公司统计98%对象都是短期的,不必单独浪费一半的空间来存储,只需要用10%即可保留下当前年轻代中的精华对象
-
老年代用标记-压缩方法
-
扫描的时候,如果没有引用且计数为0就直接回收,如果+1后够年龄就放入老年
-
一进来就进新生代Eden,Eden一满就会触发GC进行垃圾回收,新生代会频繁存入新的对象,所以触发很频繁
-
Survivor 0:上一代GC的时候保留下来的数据,但是还不够进入老年,和Eden作为本次的扫描区域
-
Survivor 0:我感觉像个temp,耶斯就是一个temp,理解的很棒~!
-
如果前边几次扫描发现某个对象都在,够一定年龄,就放到老年代,减少GC重复的判断
-
-
四大垃圾回收器
-
一些术语
- Minor GC:从年轻代回收内存叫做minor GC, Eden满了就会触发
- Major GC: 如果老年代的空间也不够了,就会触发,也可以认为major GC是由minor GC引起的
- Full GC:年轻代预感到有大量对象即将进入老年代,于是发起full gc将年轻和老年都整理
- stop the world