jvm学习笔记

1 Java语言

Java是一门面向对象的语言。

2 编程语言

编程语言是一种标准化的交流技巧,就是为了让人可以和计算机沟通。

2.1计算机体系机构

冯诺依曼计算机组成结构有输入、输出、存储器、控制器、运算器。
jvm学习笔记_第1张图片

2.2 计算机处理数据的过程

(1)提取过程:由输入设备将原始数据或信息输入给计算机并保存起来
(2)解码阶段:根据CPU的指令集将数值解释为指令
(3)执行阶段:由控制器将需要处理或计算的数据调入计算器
(4)最终阶段:用输出设备将最后结果输出
本质上就是CPU调取数据指令然后返回
CPU=存储器+控制器+运算器

机器语言

就是计算机可以识别的语言,就是01010的二进制码

汇编语言

低级语言,通过汇编器编译成机器语言
MOV、PUSH、ADD等
优点:对机器友好,执行效率高
缺点:可移植性差,需要专业的人员

高级语言

C、C++、Java、Python、Golang等

编译型和解释型

编译型语言

使用专门的编译器,针对特定的平台,将高级语言代码一次性编译成可被该平台硬件执行的机器码,并包装成该平台所能识别的可执行程序的格式
C、C++、GOLANG
编译型语言:执行速度快,执行效率高,依靠编译器,可移植性差,依靠编译器将做好的源程序一次性的全部编译成二进制代码的可执行程序,然后可以直接运行。

解释型语言

使用专门的解释器,将源码逐行解释成机器可以识别的特定平台的机器码并立即执行,是在代码执行时才一行行的被解释器动态翻译和执行,不是在执行之前就解释完成。
Python、JavaScript
执行速度慢、效率低;依靠解释器、跨平台性好。
把做好的源程序翻译一句,然后执行一句,直至结束。

Java

Java是编译型+解释型;
其实并不是因为有javac将Java源码编译成class文件,才说Java属于编译+解释语言,因为在这个编译器编译之后,生成的类文件不能直接在对应的平台上运行。
那为何又说Java是编译+解释语言呢?因为class文件最终是通过JVM来翻译才能在对应的平台上运行,而这个翻译大多数时候是解释的过程,但是也会有编译,称之为运行时编译,即JIT(Just In Time)。
综上所述,Java是一门编译型+解释型的高级语言。

JVM

Java Virtual Machine(Java虚拟机)

jvm包含在jre中,jre又被包含在jdk中;
JRE的安装文件中,包含了JVM还有lib文件夹,lib文件夹中是Java运行时需要的一些基础类、方法;

通过javac命令,可以将.java格式的文件编译成.class格式的文件,然后由类加载器将类文件加载到内存中,并对数据进行校验,转换解析和初始化形成可以虚拟机直接使用的Java类型,即java.lang.Class,
jvm学习笔记_第2张图片

  • 装载:通过查看一个类的class文件的全限定名(可以参照官网的方法查看16进制文件或通过javap指令来查看,class文件中包含了常量表、字段表集合、方法表集合)获取定义这个类的二进制字节流,然后将这个字节流锁代表的静态存储结构转化为方法区的运行时数据结构,然后在堆中生成一个这个类的对象(java.lang.Class),作为方法区中这些数据的访问入口。
    Class对象封装了类在方法区内的数据结构
    方法区中包含:类信息、常量、静态变量、即时编译器编译后的代码
  • 链接(Link)
    链接中分为:
    验证(verify):目的是为了保证类加载的正确性,包括文件格式、字节码、元数据、符号引用的验证。
  • 准备(prepare)
    给静态变量分配内存,并且将其初始化为默认值。
  • 解析(resolve)
    把类中的符号引用转换为直接引用
    在类的加载过程中的解析阶段,Java虚拟机会把类的二进制数据中的符号引用 替换为 直接引用,如Worker类中一个方法:
    public void gotoWork(){
         car.run(); //这段代码在Worker类中的二进制表示为符号引用        
    }
    
    在Worker类的二进制数据中,包含了一个对Car类的run()方法的符号引用,它由run()方法的全名 和 相关描述符组成。在解析阶段,Java虚拟机会把这个符号引用替换为一个指针,该指针指向Car类的run()方法在方法区的内存位置,这个指针就是直接引用。
    解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。
  • 初始化
    对类的静态变量,平台代码块进行初始化操作

类加载器(ClassLoader)

在装载阶段,通过类的全限定名获得其定义的二进制字节流,需要借助类加载器完成,就是用来加载class文件。
分类:

  • 1)Bootstrap ClassLoader 负责加载$JAVA_HOME中 jre/lib/rt.jar 里所有的class或Xbootclassoath选项指定的jar包。由C++实现,不是ClassLoader子类。
  • 2)Extension ClassLoader 负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。
  • 3)App ClassLoader 负责加载classpath中制定的jar包Djava.class.path 所指定目录下的类和jar包。
  • 4)Custom ClassLoader 通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。
    jvm学习笔记_第3张图片
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逐层尝试加载此类
jvm学习笔记_第4张图片

破坏双亲委派

(1)Tomcat
jvm学习笔记_第5张图片(2)SPI机制
(3)OSGi

运行时数据区

在装载阶段的第(2),(3)步可以发现有运行时数据,堆,方法区等名词
(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
(3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
说白了就是类文件被类装载器装载进来之后,类中的内容(比如变量,常量,方法,对象等这些数据得要有个去处,也就是要存储起来,存储的位置肯定是在JVM中有对应的空间)

jvm学习笔记_第6张图片

  • 方法区
    方法区是各个线程共享的内存区域,在虚拟机启动的时候创建,生命周期同虚拟机,虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它又有一个别名叫Non-Heap,目的就是为了和Java的堆区分开,主要用来存储已经被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码等数据,当方法区无法满足内存分配需求的时候,抛出OutOfMemory异常;
    jvm学习笔记_第7张图片

JVM运行时数据区是一种规范,真正的实现
在JDK 8中就是Metaspace,在JDK6或7中就是Perm Space(永久代)


  • Java堆是Java虚拟机种最大的一块,在虚拟机启动的时候创建,被所有线程共享,Java对象实例以及数组都在堆上分配。

jvm学习笔记_第8张图片

  • 虚拟机栈
    虚拟机栈是一个线程执行的区域,保存着一个线程中方法调用的状态,也就是说,一个Java线程的运行状态,由一个虚拟机栈保存,所以虚拟机栈是线程私有的,随着线程的创建而创建。
    每一个被线程执行的方法,被称为虚拟机栈的一个栈帧,也就是每一个方法对应一个栈帧。每调用一个方法,就会像栈帧中压入一个栈帧,一个方法完成,就会将这个栈帧从栈中取出。
void a(){
b();
}
void b(){
c();
}
void c(){
}

jvm学习笔记_第9张图片栈帧:每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间。
每个栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向运行时常量池的引用(Areference to the run-time constant pool)、方法返回地址(Return Address)和附加信息。


局部变量表:方法中定义的局部变量以及方法的参数存放在这张表中,局部变量表中的变量不可直接使用,如需要使用的话,必须通过相关指令将其加载至操作数栈中作为操作数使用。


操作数栈:以压栈和出栈的方式存储操作数的


动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。


方法返回地址:当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇见异常,并且这个异常没有在方法体内得到处理。


jvm学习笔记_第10张图片jvm学习笔记_第11张图片


  • 程序计数器
    JVM中有多个线程在执行,而线程中内容是否有权执行,要通过CPU的调度来实现。
    假如此时线程A执行到了某个点,突然失去了CPU的执行权,切换到了线程B了,然后当线程A在此获得执行权的时候,怎么继续执行,就需要在线程汇总维护一个变量,记录线程执行到的位置。

    如果线程正在执行Java方法,则计数器记录的就是正在执行的虚拟机字节码指令的地址,如果正在执行的是native方法,则这个计数器为空。
  • 本地方法栈

如果当前执行的方法是native类型的,这些方法会在本地方法栈中执行。(就是调用一些本地的C语言的方法)
jvm学习笔记_第12张图片

栈指向堆

jvm学习笔记_第13张图片假设在栈帧中有一个饮用型的变量,如Object obj = new Object(),这时候就是典型的栈中元素指向堆中对象。

方法区指向堆

方法区中会存放静态变量,常量等数据。如果是下面这种情况,就是典型的方法区中元素指向堆中的对象。

private static Object obj=new Object();

jvm学习笔记_第14张图片

堆指向方法区

一个对像想知道他是由哪个类创建出来的,怎么记录,就需要用到类的一些具体信息

Java对象内存模型

jvm学习笔记_第15张图片一个对象在内存中包括三个部分,对象头、实例数据、对齐填充。

jvm内存模型


jvm运行时数据区是一种规范,而jvm内存模型是对该规范的实现效果


jvm学习笔记_第16张图片

一块是非堆区,一块是堆区
堆区分两块,新生代和老年代(young和old)
新生代分两块:eden和survivor(s0+s1)

对象的创建过程

一般情况下,信创建的对象都会被分配在eden区,较大的对象直接进入老年代,当不停的创建对象,Eden区满了,就触发一次GC,把剩余对象存到S0或S1中,当再次满了将Eden剩余的对象和刚才S0或S1中的对象存到另一个survivor中,默认情况下,当新生代剩余的对象经历过15次GC后,进入老年代
jvm学习笔记_第17张图片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

垃圾回收,当内存中某一块(新生代或老年代)被占满的时候,会触发GC,将垃圾对象回收掉
判定一个对象是垃圾主要通过两种方式:
1)引用计数法,对于某个对象而言,只要应用中还有这个对象的引用,就说明不是垃圾,如果没有任何引用 就是垃圾对象,但是如果循环引用,将导致永远不能被回收掉
2)可达性分析
通过GC Root的对象,开始向下寻找,看某个对象是否可达
jvm学习笔记_第18张图片GC Root
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象。

什么时候出发 GC

GC是由JVM自动完成的,根据JVM系统环境而定,时机不是确定的。可以调用system.gc()方法进行一次垃圾回收,只是通知jvm来回收,时机还是不确定的 由jvm控制,不建议调用这个方法,因为GC消耗的资源比较大。
GC执行的过程,称为STW stop the world
GC时机:

  • (1)当Eden区或者S区不够用了
  • (2)老年代空间不够用了
  • (3)方法区空间不够用了
  • (4)System.gc()

垃圾回收算法

  • 1)标记-清除
    标记
    找出内存中需要回收的对象,将它们标记出来,但是这个时候会将所有对象都扫描一遍,耗时较长,且容易造成内存碎片化。
    jvm学习笔记_第19张图片

  • 2) 标记-复制
    将内存分为相同大小的两块区域,每次使用的时候只使用其中之一,当GC的时候将所有的存活对象一次性复制到另一块上,然后将当前使用的清除掉,就是空间换时间
    jvm学习笔记_第20张图片jvm学习笔记_第21张图片

  • 3)标记-整理
    标记过程与标记清除算法一样,但是后面不是直接清理,而是让所有的存活对象都向一端移动,然后直接清理掉边界以外的内存。

  • 4)分代收集算法
    就是在不同的代中使用不同的算法
    新生代(young):复制算法
    老年代(OLD):标记清除或标记整理


垃圾收集器

jvm学习笔记_第22张图片

serial

serial是最基本、发展历史最久的垃圾收集器,曾经在JDK1.31之前是唯一的垃圾收集器
它是一种单线程的收集器,不仅仅意味着它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是其在进行垃圾收集的时候需要暂停其他线程。

  • 优点:简单高效,单线程收集效率高
  • 缺点:收集过程要stw
  • 算法:复制算法
  • 适用范围:新生代
  • 应用:Client模式下的默认新生代收集器
    jvm学习笔记_第23张图片

serial old

serial收集器的老年代版本,也是一个单线程收集器,但是适用的是标记整理算法,过程同serial收集器
jvm学习笔记_第24张图片

parNew

serial收集器的多线程版本

  • 优点:多核CPU情况下,比serial效率高;
  • 缺点: 还是要在回收时stw,单核情况下不如serial
  • 算法:复制算法
  • 适用范围:新生代
  • 应用:运行在server模式下的虚拟机中首选的新生代收集器
    jvm学习笔记_第25张图片

Parallel Scavenge

是一个新生代收集器,也是使用复制算法,且是并行的收集器,类似parnew,但是更注重系统的吞吐量
吞吐量 =运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)
吞吐量越大,代表垃圾收集的时间短,用户可以充分利用CPU资源,尽快完成运算任务。

-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间,
-XX:GCRatio直接设置吞吐量的大小。

Parallel Old

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法进行垃圾回收,也是更加关注系统的吞吐量。

CMS

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收集器的内存回收过程是与用户线程一起并发地执行的。

jvm学习笔记_第26张图片

  • 优点:并发收集,低停顿
  • 缺点:产生大量空间碎片,并发阶段会降低吞吐量。

G1

使用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毫秒)

jvm学习笔记_第27张图片

初始标记(Initial Marking) 标记以下GC Roots能够关联的对象,并且修改TAMS的值,需要暂停用户线程
并发标记(Concurrent Marking) 从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行
最终标记(Final Marking) 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
筛选回收(Live Data Counting and Evacuation) 对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划

jvm学习笔记_第28张图片

ZGC

JDK11新引入的ZGC收集器,不管是物理上还是逻辑上,ZGC中已经不存在新老年代的概念了
会分为一个个page,当进行GC操作时会对page进行压缩,因此没有碎片问题
只能在64位的linux上使用,目前用得还比较少
  • (1)可以达到10ms以内的停顿时间要求
  • (2)支持TB级别的内存
  • (3)堆内存变大后停顿时间还是在10ms以内

垃圾收集器分类


  • 串行收集器

Serial和Serial Old
只能有一个垃圾回收线程执行,用户线程暂停。
适用于内存比较小的嵌入式设备。

  • 并行收集器(吞吐量优先)

Parallel Scanvenge、Parallel Old
多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
适用于科学计算、后台处理等若交互场景。

  • 并发收集器(停顿时间优先)

CMS、G1
用户线程和垃圾收集线程同时执行(但并不一定是并行的,可能是交替执行的),垃圾收集线程在执行的时候不会停顿用户线程的运行。
适用于相对时间有要求的场景,比如Web 。

jvm学习笔记_第29张图片

你可能感兴趣的:(Java学习)