一,Java的语言特点:
Java是兼顾编译和解释型的编程语言。
Java代码通过JavaC编译成Java字节码,JVM再把Java的字节码文件转化成机器可以读的指令,机器读一行,执行一行。
编译型编程语言:把代码编译成机器可以读懂的指令,编译不通过代码无法运行。例如:C语言
解释型编程语言:读一行,执行一行,没有读到错误代码,程序就可以接着运行。例如:Python、JavaScript
Java是静态强类型的编程语言。
静态类型指的是变量的类型在编译时确定,在运行时不可以修改。JVM可以将Java类编译成字节码文件,将符号引用转换为内存地址再转换为机器可以读的指令,在运行时的动态分派也是确定和解析的过程。
强类型:指的是数据类型不强转,就永远是一个类型。Java的强类型体现在有继承关系的对象,对象之间的强制转换,以及数据类型的强制转换,但是范围大的不可以转换成范围小的数据类型,避免数据丢失。
二,JVM和JVM编译器:
JVM将Java代码转换成JVM可以读的字节码文件,再转换成机器可以读的指令,JVM作为中间层,屏蔽掉不同的操作系统的硬件差异(CPU),在不同的操作系统上安装不同版本的JVM, Java就实现了跨平台。加一层要么屏蔽复杂,要么屏蔽差异。
JavaC编译器:源代码->字节码
GIT编译器:常用字节码->机器指令
AOT编译器:源代码->机器指令。
三,计算对象在堆内存占多少个字节:
1,对象头:marker标记位,记录对象的年龄、属性的符号引用、类似于空白的个人信息的表格,占8个字节。锁信息。
类的方法指向方法区的内存地址、JVM开启指针压缩,占4个字节。(也记录锁信息)
如果是数组对象,对象头还包含数组的长度数据占4个字节。
2,实例数据:属性的值占4个字节,引用对象的内存地址占4个字节,没有属性可以是0个字节,有属性就有初始化值。
如果是数组对象,数组对象里的实例数据,一个数据占1个字节。
3,padding: JVM要求Java对象的内存大小应是8个字节的倍数,不够,就自动补齐到8的倍数。
4,String是一个对象,里面的每一个字符并不是占一个字节。
四,类的加载过程:
一个类编译以后会形成Java的字节码文件,JVM在使用类时把类的字节码文件加载到方法区。
使用类指的是:创建类的实例、new一个类的实例时,创建子类对象会把继承链上的父类加载到内存当中,创建内部类的实例也会把外部类加载到内存当中。调用静态方法或静态属性,所属的类也会被加载到方法区,main所属的类会被最先调用,main方法最先入栈。 Java的反射机制也会触发类的加载。
1.类的加载:类全限定名获取类的二进制字节流,将字节流文件转换成方法区的数据结构,在内存区生成一个代表这个类的二进制对象,作为方法区这个类的各种数据的访问入口。(我听不懂)
2.验证:验证class文件是Java类的文件,而不是叫.class的其他伪造的文件。文件格式的验证:Java的字节码文件应以魔术值cafebaby开头。Java的版本号是在虚拟机处理的范围内。高版本的JDK中可以运行低版本的Java代码。
3.准备:为静态变量在方法区中分配内存、并设置初始化值,如果没有初始化值就是默认值0。
public final static int NUM;
可调用不可修改,又没有初始化值,会报错。
4.解析:虚拟机将常量池内的符号引用替换为直接引用的过程,动态分派也是解析的过程。
5.初始化:在类加载到内存中以后,静态变量会放到静态代码块中执行一次。static{};
五、new一个对象的过程中发生了什么?
1.确认类元信息是否存在,确认类有没有加载到方法区。类元信息:描述类的信息。
2.计算对象的内存大小,给对象分配内存,先在堆中分配空间,然后在内存中分配空间。
3.设定默认值,不同数据类型,基础类型和引用类型的默认值。
4.设定对象头,设定类的基本信息。
5.执行init方法,调用类的构造器,初始化成员变量,并给类的成员变量赋值,执行实例代码块,并把堆内对象的首地址赋给引用变量。
六、类加载器源码阅读:
类加载器是一个类ClassLoader,作用/功能是通过类的全限定名,把out目录下、编译后的类的字节码文件加载到内存区。
类编译后输出的目录可以修改,在project compiler output中修改。
加载这个类时会先找parent父类加载器ExtensionClassLoader加载lib/ext的jar包,没有再去找父类的父类的parent加载器BootStrapClassLoader加载lib目录的jar包,也没有,尝试自己加载,用ApplicationClassLoader加载自己写的Java类,双亲委派机制,避免Java的核心类被覆写,被加载过的类就不会再加载一遍。可以根据自身的业务特点重写findClass和loadClass重构相应的类加载机制。
七,运行时数据区:
系统态和用户态内核的切换,数据的保存和恢复。
方法区和堆、线程共有。
堆中不同的实例对象、可以调用方法区的同一个方法。
同一个方法可以加入不同的栈中,独立执行。
堆中的同一个实例对象,可以有不同的几个方法栈。
虚拟机栈、本地方法栈、程序计数器线程私有。
(1)虚拟机栈
每一个方法栈桢中包含:
局部变量表slot槽;
操作数栈:(i+j)把i压到栈顶、再把j压到栈顶,把j和i弹出来,加法器相加以后,把结果压到栈顶返回到要调用的变量中。
(++i):先自增,再把自增以后的值压到栈顶。
(i++):先把i压到栈顶、再自增;
动态链接:程序运行的过程中的符号引用转化成内存地址与常量池中的内存地址相同;
方法返回地址:正常退出,执行到return、ireturn、areturn退出。异常退出,回滚方法。
(2)本地方法栈
调用本地方法时创建的栈。本地方法没有方法体,但不是抽象方法,而是由native修饰的,具体实现细节在C++中的方法。
public native int hashCode(); protective native Object clone() throws CloneNotSupport Exception;
(3)程序计数器
打开编译后的Java字节码文件可以看偏移量offset。
系统内核控制CPU切换用户态内核,所以每个线程都有一个程序计数器,记录自己执行到哪儿了,切换回去的时候,自己可以接着执行。
(4)Java堆
存放对象实例,由线程共享,垃圾回收算法。
(5)方法区
存放Java的字节码文件,类、常量池、静态变量都在方法区。存放JIT即时编译后的常使用的代码数据。
八,回收算法
(1)引用计数法:
(2)可达性分析算法:根可达算法
从根空间顺着引用链、找堆内存中的对象,找到的对象做标记,没有被找的对象、就是在堆中飘零的对象,全部垃圾回收掉。
根:虚拟机栈的栈桢中的符号引用、局部变量;方法区中静态属性引用的对象;方法区中常量池引用的对象。
堆内存是有限的,内存垃圾回收时有内存碎片,对内存碎片做整合。
(3)垃圾回收算法:
标记-清除算法:与根可达类似,会产生很多的内存碎片,在分配较大的内存对象时,如果没有较大的连续的内存空间要重新垃圾回收一次,把所有的类拉出来再标记一遍。
标记-整理算法:标记删除不用的,把使用的对象位移成连续的使用的空间,节省空间。
复制算法:一种用空间换时间的算法,把堆内存分成两个部分,一部分使用并标记成垃圾,然后将没有标记成垃圾的对象,按顺序复制放入另一部分中,原来的部分就可以全部空出来,效率高,可以两部分空间来回倒腾。
(4)根据对象的生命周期产生的分代算法:
青年代:对象使用次数少,需要不断gc,使用复制算法效率高。
老年代:对象使用次数多或对象占内存空间多,使用标记整理算法,节省空间。
九,垃圾回收器(听不懂)响应时间和吞吐量
(1)三色标记(边运行边标记的并发标记):没有子类变黑,还有子类变灰,黑色代表根可达,垃圾回收时不管;灰色的继续标记叶子结点;而白色的是没有被标记的类,remarker重新标记时,业务线程全部停下来,垃圾回收已经标记为白色的,并发标记在remarker时可以节省时间。
(2)G1(Garbage-First):堆内存分区,每个区分代,哪个代内存不够了,可以堆内存整体兼顾所有的代、进行垃圾回收,清理出来的内存区按需求重新分代,不同代之间的引用在内存的位置没有规律导致算法比较复杂。
十,JVM的指令
jvm指令可以调整堆内存的大小也可以选择垃圾回收器,这些调整是会影响程序的执行效率的。想要程序频繁gc,就把堆的初始化的和最大的内存设小一点,在edit Configuration 中,选择modify options 选择Add vm options 然后Expand ,就可以输入JVM指令或打印gc日志。
-XMS20M:表示初始化的堆内存大小
-XMX20M:表示堆的最大内存
-XX:+PrintGCDetails:打印gc日志



