Java是一门面向对象的语言。
编程语言是一种标准化的交流技巧,就是为了让人可以和计算机沟通。
冯诺依曼计算机组成结构有输入、输出、存储器、控制器、运算器。
(1)提取过程:由输入设备将原始数据或信息输入给计算机并保存起来
(2)解码阶段:根据CPU的指令集将数值解释为指令
(3)执行阶段:由控制器将需要处理或计算的数据调入计算器
(4)最终阶段:用输出设备将最后结果输出
本质上就是CPU调取数据指令然后返回
CPU=存储器+控制器+运算器
就是计算机可以识别的语言,就是01010的二进制码
低级语言,通过汇编器编译成机器语言
MOV、PUSH、ADD等
优点:对机器友好,执行效率高
缺点:可移植性差,需要专业的人员
C、C++、Java、Python、Golang等
使用专门的编译器,针对特定的平台,将高级语言代码一次性编译成可被该平台硬件执行的机器码,并包装成该平台所能识别的可执行程序的格式
C、C++、GOLANG
编译型语言:执行速度快,执行效率高,依靠编译器,可移植性差,依靠编译器将做好的源程序一次性的全部编译成二进制代码的可执行程序,然后可以直接运行。
使用专门的解释器,将源码逐行解释成机器可以识别的特定平台的机器码并立即执行,是在代码执行时才一行行的被解释器动态翻译和执行,不是在执行之前就解释完成。
Python、JavaScript
执行速度慢、效率低;依靠解释器、跨平台性好。
把做好的源程序翻译一句,然后执行一句,直至结束。
Java是编译型+解释型;
其实并不是因为有javac将Java源码编译成class文件,才说Java属于编译+解释语言,因为在这个编译器编译之后,生成的类文件不能直接在对应的平台上运行。
那为何又说Java是编译+解释语言呢?因为class文件最终是通过JVM来翻译才能在对应的平台上运行,而这个翻译大多数时候是解释的过程,但是也会有编译,称之为运行时编译,即JIT(Just In Time)。
综上所述,Java是一门编译型+解释型的高级语言。
Java Virtual Machine(Java虚拟机)
jvm包含在jre中,jre又被包含在jdk中;
JRE的安装文件中,包含了JVM还有lib文件夹,lib文件夹中是Java运行时需要的一些基础类、方法;
通过javac命令,可以将.java格式的文件编译成.class格式的文件,然后由类加载器将类文件加载到内存中,并对数据进行校验,转换解析和初始化形成可以虚拟机直接使用的Java类型,即java.lang.Class,
public void gotoWork(){
car.run(); //这段代码在Worker类中的二进制表示为符号引用
}
在Worker类的二进制数据中,包含了一个对Car类的run()方法的符号引用,它由run()方法的全名 和 相关描述符组成。在解析阶段,Java虚拟机会把这个符号引用替换为一个指针,该指针指向Car类的run()方法在方法区的内存位置,这个指针就是直接引用。在装载阶段,通过类的全限定名获得其定义的二进制字节流,需要借助类加载器完成,就是用来加载class文件。
分类:
public class Demo3 {
public static void main(String[] args) {
// App ClassLoader
System.out.println(new Worker().getClass().getClassLoader());
// Ext ClassLoader
System.out.println(new
Worker().getClass().getClassLoader().getParent());
// Bootstrap ClassLoader
System.out.println(new
Worker().getClass().getClassLoader().getParent().getParent());
System.out.println(new String().getClass().getClassLoader());
}
}
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@3a71f4dd
null
null
因为extension和bootstrap不是通过Java实现,所以此处打印出两个空值
(1)检查某个类是否已经加载
自底向上,从CustomClassLoader到BootStrap ClassLoader逐层检查,只要某个ClassLoader已经加载了,就视为这个类已加载,来保证所有的类只会加载一次。
(2)加载顺序
自顶向下,也就是从bootstrap classloader逐层尝试加载此类
在装载阶段的第(2),(3)步可以发现有运行时数据,堆,方法区等名词
(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
(3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
说白了就是类文件被类装载器装载进来之后,类中的内容(比如变量,常量,方法,对象等这些数据得要有个去处,也就是要存储起来,存储的位置肯定是在JVM中有对应的空间)
JVM运行时数据区是一种规范,真正的实现
在JDK 8中就是Metaspace,在JDK6或7中就是Perm Space(永久代)
void a(){
b();
}
void b(){
c();
}
void c(){
}
栈帧:每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间。
每个栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向运行时常量池的引用(Areference to the run-time constant pool)、方法返回地址(Return Address)和附加信息。
局部变量表:方法中定义的局部变量以及方法的参数存放在这张表中,局部变量表中的变量不可直接使用,如需要使用的话,必须通过相关指令将其加载至操作数栈中作为操作数使用。
操作数栈:以压栈和出栈的方式存储操作数的
动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。
方法返回地址:当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇见异常,并且这个异常没有在方法体内得到处理。
如果当前执行的方法是native类型的,这些方法会在本地方法栈中执行。(就是调用一些本地的C语言的方法)
假设在栈帧中有一个饮用型的变量,如Object obj = new Object(),这时候就是典型的栈中元素指向堆中对象。
方法区中会存放静态变量,常量等数据。如果是下面这种情况,就是典型的方法区中元素指向堆中的对象。
private static Object obj=new Object();
一个对像想知道他是由哪个类创建出来的,怎么记录,就需要用到类的一些具体信息
jvm运行时数据区是一种规范,而jvm内存模型是对该规范的实现效果
一块是非堆区,一块是堆区
堆区分两块,新生代和老年代(young和old)
新生代分两块:eden和survivor(s0+s1)
一般情况下,信创建的对象都会被分配在eden区,较大的对象直接进入老年代,当不停的创建对象,Eden区满了,就触发一次GC,把剩余对象存到S0或S1中,当再次满了将Eden剩余的对象和刚才S0或S1中的对象存到另一个survivor中,默认情况下,当新生代剩余的对象经历过15次GC后,进入老年代
Minor GC:新生代
Major GC:老年代
Full GC:新生代+老年代
需要survivor区的原因是因为,如果没有survivor,那么Eden区每进行一次GC,存货对象就将要被送进老年代,这样老年代就会很快被填满,从而引发majorGC 继而引发fullGC。两个s区是为了解决内存碎片化的问题(标记-复制算法)
新生代中的可用内存:复制算法用来担保的内存为9:1
可用内存中Eden:S1区为8:1
即新生代中Eden:S1:S2 = 8:1:1
现代的商业虚拟机都采用这种收集算法来回收新生代,IBM公司的专门研究表明,新生代中的对象大概98%是
“朝生夕死”的
JVM默认为每个线程在Eden上开辟一个buffer区域,用来加速对象的分配,称之为TLAB,全称:Thread Local Allocation Buffer。对象优先会在TLAB上分配,但是TLAB空间通常会比较小,如果对象比较大,那么还是在共享区域分配。
垃圾回收,当内存中某一块(新生代或老年代)被占满的时候,会触发GC,将垃圾对象回收掉
判定一个对象是垃圾主要通过两种方式:
1)引用计数法,对于某个对象而言,只要应用中还有这个对象的引用,就说明不是垃圾,如果没有任何引用 就是垃圾对象,但是如果循环引用,将导致永远不能被回收掉
2)可达性分析
通过GC Root的对象,开始向下寻找,看某个对象是否可达
GC Root
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象。
GC是由JVM自动完成的,根据JVM系统环境而定,时机不是确定的。可以调用system.gc()方法进行一次垃圾回收,只是通知jvm来回收,时机还是不确定的 由jvm控制,不建议调用这个方法,因为GC消耗的资源比较大。
GC执行的过程,称为STW stop the world
GC时机:
1)标记-清除
标记
找出内存中需要回收的对象,将它们标记出来,但是这个时候会将所有对象都扫描一遍,耗时较长,且容易造成内存碎片化。
2) 标记-复制
将内存分为相同大小的两块区域,每次使用的时候只使用其中之一,当GC的时候将所有的存活对象一次性复制到另一块上,然后将当前使用的清除掉,就是空间换时间
3)标记-整理
标记过程与标记清除算法一样,但是后面不是直接清理,而是让所有的存活对象都向一端移动,然后直接清理掉边界以外的内存。
4)分代收集算法
就是在不同的代中使用不同的算法
新生代(young):复制算法
老年代(OLD):标记清除或标记整理
serial是最基本、发展历史最久的垃圾收集器,曾经在JDK1.31之前是唯一的垃圾收集器
它是一种单线程的收集器,不仅仅意味着它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是其在进行垃圾收集的时候需要暂停其他线程。
serial收集器的老年代版本,也是一个单线程收集器,但是适用的是标记整理算法,过程同serial收集器
serial收集器的多线程版本
是一个新生代收集器,也是使用复制算法,且是并行的收集器,类似parnew,但是更注重系统的吞吐量
吞吐量 =运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)
吞吐量越大,代表垃圾收集的时间短,用户可以充分利用CPU资源,尽快完成运算任务。
-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间,
-XX:GCRatio直接设置吞吐量的大小。
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法进行垃圾回收,也是更加关注系统的吞吐量。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
采用的是标记清除算法:
在Java7出现,在Java8建议使用
(1)初始标记 CMS initial mark 标记GC Roots直接关联对象,不用Tracing,速度很快
(2)并发标记 CMS concurrent mark 进行GC Roots Tracing
(3)重新标记 CMS remark 修改并发标记因用户程序变动的内容
(4)并发清除 CMS concurrent sweep 清除不可达对象回收空间,同时有新垃圾产生,留着下次清理称为浮动垃圾
由于整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行的。
使用G1收集器时,Java堆的内存布局与就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。每个Region大小都是一样的,可以是1M到32M之间的数值,但是必须保证是2的n次幂;
如果对象太大,一个Region放不下[超过Region大小的50%],那么就会直接放到H中;
设置Region大小:-XX:G1HeapRegionSize=M
所谓Garbage-Frist,其实就是优先回收垃圾最多的Region区域
(1)分代收集(仍然保留了分代的概念)
(2)空间整合(整体上属于“标记-整理”算法,不会导致空间碎片)
(3)可预测的停顿(比CMS更先进的地方在于能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒)
初始标记(Initial Marking) 标记以下GC Roots能够关联的对象,并且修改TAMS的值,需要暂停用户线程
并发标记(Concurrent Marking) 从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行
最终标记(Final Marking) 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
筛选回收(Live Data Counting and Evacuation) 对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划
JDK11新引入的ZGC收集器,不管是物理上还是逻辑上,ZGC中已经不存在新老年代的概念了
会分为一个个page,当进行GC操作时会对page进行压缩,因此没有碎片问题
只能在64位的linux上使用,目前用得还比较少
Serial和Serial Old
只能有一个垃圾回收线程执行,用户线程暂停。
适用于内存比较小的嵌入式设备。
Parallel Scanvenge、Parallel Old
多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
适用于科学计算、后台处理等若交互场景。
CMS、G1
用户线程和垃圾收集线程同时执行(但并不一定是并行的,可能是交替执行的),垃圾收集线程在执行的时候不会停顿用户线程的运行。
适用于相对时间有要求的场景,比如Web 。