一、解释还是编译
解释:解释表示代码在运行的过程不存在编译的过程,而是读一行执行一行。
编译:编译指的是代码会被编译成机器指令,再由机器去执行。
Java是一名解释与编译都有的一门语言。
存在编译的过程:会把Java源代码编译成class文件,也就是字节码
解释的过程:JVM会识别字节码,通过解释的方式读一行,执行一行的方式运行。
Java的编译器:
前端编译器:Javac指令,把Java源代码编译位字节码。
JIT编译器:及时编译器,会把热点class文件编译成机器代码。
AOT编译器:能将Java源代码直接编译成机器指令。
二、动态类型还是静态类型
动态类型:变量在声明的时候不确定类型。
变量在编译的时候不确定类型
变量在运行的时候确定类型,在运行的时候可以更改
静态类型:变量在声明的时候就确定了类型。
变量在编译的时候就确定了类型
变量在运行的时候不能更改类型
强类型语言
变量如果被指定了数据类型,那么他只能通过强制类型转换改变数据类型
弱类型语言1
变量的数据类型可以在运行过程中随意更改
Java是一名静态类型的强类型语言
三、实例对象在内存的分配
普通对象和数组对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GVqYGABt-1671110439350)(C:\Users\playclear\AppData\Roaming\Typora\typora-user-images\image-20221204172715140.png)]
举例:
1.Object对象:12(对象头) + 0(数据区域) + 4(对齐) = 16
2.Object数组对象:12(对象头) + 0(数据区域) + 4(数组长度) + 0(对齐)= 16
public class Test {
private byte[] bytes;
private int age;
public Test(byte[] bytes, int age) {
this.bytes = bytes;
this.age = age;
}
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static void main(String[] args) {
Test test = new Test(new byte[12],12);
System.out.println(ObjectSizeCalculator.getObjectSize(test));
}
}
Test对象占多大空间:
分两部分计算
1.Test对象:12(对象头) + 4(bytes的引用) + 4(age)+ 4(对齐) = 24
2.bytes: 12(对象头) + 4(长度) + 12(数据大小) + 4(对齐) = 32
all: 24 + 32 = 56
四、类的加载
加载-连接-初始化-使用-卸载
1.加载:把class文件以二进制流的形式加载到方法区中,并生成Java.lang.Class对象。
2.连接:
验证:对class文件的字节流进行安全性校验(文件格式、元数据、字节码等)
准备:位静态变量赋予初始值,并在方法区为其分配空间
解析:将常量池的符号引用替换为直接引用
3.初始化:收集静态变量的赋值动作和静态代码块然后初始化
五、类加载器
Bootstrap Classloader 启动类加载器(加载JVM需要的类,主要是 <JAVA_HOME>/lib 下的)
Extension Classloader 扩展类加载器 (主要是 <JAVA_HOME>/lib/ext 下的,开发者也可以)
Application Classloader 系统应用类加载器(加载class Path下的)
使用双亲委派模式(这种委派给上一个类加载器的模式是通过代码逻辑实现的,在Application Classloader类加载器中维护了一个parent成员变量,类型是Class loader。基本逻辑是判断有没有父亲,有就交给父亲加载,如果父亲没有加载成功,在由自己去加载)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cB0unrN3-1671110439350)(C:\Users\playclear\AppData\Roaming\Typora\typora-user-images\image-20221205141656210.png)]
六、运行时数据区
共有:
方法区:存放JVM加载类的信息,是一个存放元数据的区域。
堆:是存放对象实例的区域,从方法区元数据实例后的对象
私有:
虚拟机栈:线程需要执行每个方法需要的存储结构
本地方法栈:存放被native修饰的方法,是由C++编写的
程序计数器:用于记录发生线程切换记录操作进行到了哪步,在解析后的字节码文件中就有序号
七、垃圾回收
1.如何标记?
引用计数法:为对象添加引用计数器,被引用一次+1;引用失效-1,为0则回收。存在循环依赖的问题
根可达算法: 以GC Root为起点,向下开始搜索,被搜索到的就标记。未被搜索到的就会被清除。
GC Root有哪些?
1.虚拟机栈中的对象
2.方法区中静态属性引用的对象
3.方法区中常量引用的对象
2.如何删除(垃圾回收算法)?
1.标记-清除
简单粗暴,标记的留下,未标记的删除。
缺点:1.会产生内存碎片。
2.效率低
2.复制算法
开辟两片相同的空间A和B, 先在A空间标记不清除的空间,然后将标记的空间复制到B空间,在清除A空间,如此反复。
缺点:空间换时间
3.标记-整理算法
第一步标记 不清理的空间,第二步将标记的空间向前挪动,第三部清理标记好的边界以外的空间。
缺点:整理耗时
4.分代年龄算法(2,3的结合)
对对象进行年龄分代,分为年轻代和老年代。
年轻代有自己存放的区域,叫做伊甸园和幸存区。在幸存区采用复制算法进行垃圾回收。
老年代存放在老年代中,采用标记整理-算法。
具体流程:
第一步:一个崭新的0岁的对象被创建准备放入伊甸园,伊甸园没满则成功放入,伊甸园满了就第二步。
第二步: 伊甸园满了就会进行一次MinorGC,幸存的对象放入幸存区的左区域(图示的左边的区域)中,当左区域满了,开始进行复制算法。
当满足以下条件的一个就会被放入老年代区域:
1.每执行一次GC操作,对象的年龄就会+1(该数据存放在对象头中),当年龄等于15的时候(默认15)就会被放入老年代;
2.大对象直接放入老年代
3.如果同一个年龄的对象所占的空间大于幸存区的一半的时候,就会把这个年龄以及大于他们年龄的对象放入老年代。
第三步:当老年代的空间满了之后,就会触发MajorGC,使用的算法是标记-整理算法。
空间分配担保:
在每次MinorGC之前都会进行一次检查,当老年代的剩余可用空间大于所有新生代的对象大小,只有这样才能保证MinorGC是安全的;
原因:如果所有的新生代的对象都将会被放入老年代中,上面的条件不成立就会触发OOM堆内存溢出。
八、垃圾回收器
待更新。。。