Java程序的执行流程:
java文件(xxx.java) 编译-> 字节码文件(xxx.class) -> 运行在JVM -> 操作系统
1.字节码文件的结构
首先我们对一个.java文件使用javac命令将其编译,得到以下的结果
得到字节码
其中u代表字节数
用javap -v xxx.class命令可反编译class文件得到以上信息
可通过反编译来查看java语句更详细的执行步骤
javap更详细的使用见这篇博客:https://www.jianshu.com/p/6a8997560b05
JVM 虚拟机字节码指令表:https://www.jianshu.com/p/e057695f1184
2.JVM的内存结构
1.
以下两个空间在虚拟机启动时就被创建!
堆(线程共享):一般空间最大,存放对象,数组等
-Xms:初始堆大小
-Xmx:最大堆大小
方法区(线程共享)(Java8叫元空间):含一个运行时常量池(存放编译期生成的各种字面量和符号引用),存储已被虚拟机加载的类信息(含一个Class常量池)、常量、静态变量、即时编译器编译后的代码等数据。
常量池包括:字符串常量池、Class常量池和运行时常量池
Class常量池(方法区):Class常量池可以理解为是Class文件中的资源仓库。 Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。
运行时常量池(方法区):运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中。
字符串常量池(堆jdk1.8):存放字符串对象
https://blog.csdn.net/afdafvdaa/article/details/119802244见这篇博客
以下三个空间在线程创建时被创建!
程序计数器(线程私有):记录下一条需要执行指令的字节码指令地址偏移量,不会发生OutOfMemory(JVM向机器申请内存时机器内存不足)
为什么需要程序计数器?答:因为JVM是操作系统软件层面的东西,当时间片发生轮转的时候会被阻塞,所以就需要程序计数器来记录。
Java 虚拟机栈(线程私有):按一方法一栈帧为基础单位存放局部变量和自定义方法等,大小Xss默认1m
一栈帧包括:
1.局部变量表:存放局部变量
2.操作数栈:方法里面变量进行运算、操作
3.动态连接:符号引用和直接引用在运行时进行解析和链接的过程,叫动态链接。
4.返回地址:方法运行结束后根据程序计数器回到当前程序执行的位置
Java 本地方法机栈(线程私有):存放Java本地方法信息
JVM性能优化
编译优化:内联函数
栈的优化:栈帧之间数据共享
2.对象在堆中的结构:
tip:逃逸分析:分析对象的作用范围是否只在这一个函数内使用,若是就在栈上申请空间,缓解了GC压力。
不同Markword对应的含义:
3.GC垃圾回收
类会在什么时候回收?
1.所有实例都被回收 2.classloader被回收 3.该类的Class对象被回收 4.GC中没有禁止类可被回收
判断对象的存货:1.引用计数算法 2.可达性分析(GCroot)
1.垃圾回收算法
1.标记 - 清除算法:直接标记并清除(缺点:会产生内部碎片)
2.标记- 整理算法:标记将对活的对象整理到一块,然后删除其他对象(缺点:代价太大)
3.复制算法(新生代)(效率最高):把空间分成两块,每次只对其中一块进行垃圾回收。当这块内存使用完时,就将还存活的对象复制到另一块上,并清空该块空间。(缺点:内存利用率只有一半)
堆可被jvm分为三个区域:新生代(复制算法),老年代(标记 - 清除算法、标记-整理算法),永久代
GC过程参考:https://www.bilibili.com/video/BV1dt411u7wi/?spm_id_from=333.788.recommend_more_video.-1
垃圾收集器:
https://blog.csdn.net/afdafvdaa/article/details/119800179
https://github.com/sunwu51/notebook/blob/master/19.09/java_jvm%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8.md
G1:筛选回收最有价值的空间