image-20200722221722548

第一章-类加载器和类加载过程

尚硅谷JVM周阳视频(简介).https://www.bilibili.com/video/BV1vE411D7KE?from=search&seid=4436711570232529843

尚硅谷JVM宋红康(详情):https://www.bilibili.com/video/BV1PJ411n7xZ?p=49

别人笔记:https://www.cnblogs.com/yanl55555/p/12610952.html

1类的加载过程

  • 1 加载 类的加载阶段是将class文件中的二进制数据读取到内存中,然后将该字节流所代表的静态存储结构转化为方法区中运行时的数据结构,并且在堆内存中生成一个该类的java.lang.class对象,作为方法区数据结构的入口。

  • 2链接

    • 1验证
      确保class文件的字节流所含信息符合当前虚拟机要求,保证被加载类的正确性。主要包括,文件格式,元数据,字节码验证,符合引用验证。
    • 2准备 为类变量分配内存,并且设置类变量的初始默认值,即零值。 不包含final修饰的static,因为final在编译时就会分配内存,准备阶段会直接赋值。 类变量分配在方法区,实例变量会随着对象分配到Java堆中
    • 3解析 把符号引用转化为直接引用
  • 3初始化 就是执行类构造器方法()的过程。此方法不需定义,是javac编译 器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。 构造器方法中指令按语句在源文件中出现的顺序执行。 ()不同于类的构造器。(关联: 构造器是虚拟机视角下的()) 若该类具有父类,JVM会 保证子类的()执行前,父类的 () 已经执行完毕。 虚拟机必须保证一个 类的 ()方法在多线程下被同步加锁。

    image-20200730223639890

image-20200722222219415

1加载

类的加载阶段是将class文件中的二进制数据读取到内存中,然后将该字节流所代表的静态存储结构转化为方法区中运行时的数据结构,并且在堆内存中生成一个该类的java.lang.class对象,作为方法区数据结构的入口。

2链接

  • 1验证
    确保class文件的字节流所含信息符合当前虚拟机要求,保证被加载类的正确性。主要包括,文件格式,元数据,字节码验证,符合引用验证。
  • 2准备 为类变量分配内存,并且设置类变量的初始默认值,即零值。 不包含final修饰的static,因为final在编译时就会分配内存,准备阶段会直接赋值。 类变量分配在方法区,实例变量会随着对象分配到Java堆中
  • 3解析 把符号引用转化为直接引用

3初始化

就是执行类构造器方法()的过程。此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。(必须要有静态代码和静态代码块) 构造器方法中指令按语句在源文件中出现的顺序执行。 ()不同于类的构造器。(关联: 构造器是虚拟机视角下的()) 若该类具有父类,JVM会 保证子类的()执行前,父类的 () 已经执行完毕。 虚拟机必须保证一个 类的 ()方法在多线程下被同步加锁。


idea插件 double shift收缩plugins 然后下载jClasslib。

运行/build后,进入到class文件下image-20200729222814530

两个线程同时加载一个类,是线程安全的。

image-20200729223119220

1类加载器

jvm类加载https://blog.csdn.net/qq_15237993/article/details/72916868

负责加载class文件,class文件在 文件开头有特定的文件标示, 将class文件字节码内容加载到内存中,并将这些内容转换成==方法区==中的 ==运行时数据结构==,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。

1.1类加载器的类型

  1. 启动类加载器(Bootstrap ClassLoader)

    作用:负责加载以下类。

    存放在\lib目录中的类(rt.jar下的com.java.util等等,所以一开始就可以用Object)

    -Xbootclasspath参数所指定路径中、并且是被虚拟机识别的类库

    使用C++编写,加载Object等,但通过new Object().getClass().getClassLoader()获得的是Null,因为其是C++编写,不是Java对象。

  2. 扩展类加载器(Extension ClassLoader)

    作用:负责加载以下类:

    1. \lib\ext目录中的类库(ext下的*.jar)
    2. java.ext.dirs系统变量所指定的路径中的所有类库

    特别注意

    1. sum.misc.Launcher$ExtClassLoader类实现
    2. 开发者可以直接使用扩展类加载器
  3. 应用程序类加载器(Application ClassLoader)

    作用:负责加载 用户类路径(ClassPath)上所指定的类库

    特别注意

    1. 也称为系统类加载器,因为该类加载器是ClassLoader中的getSystemClassLoader()方法的返回值
    2. sum.misc.Launcher$AppClassLoader类实现
    3. 开发者可以直接使用该类加载器
    4. 若开发者 没 自定义类加载器,程序默认使用该类加载器

    ==以上为Java虚拟机自带的类加载器==

    4.用户自定义加载器 Java. lang. ClassLoader的子类,用户可以定制类的加载方式。

    使用自定义类加载器的原因:隔离加载类、修改类加载的方式、扩展加载源、防止源码泄露。

1.2双亲委派机制

步骤总结:若一个类加载器收到了类加载请求

  1. 把 该类加载请求 委派给 父类加载器去完成,而不会自己去加载该类

每层的类加载器都是如此,因此所有的加载请求最终都应传送到顶层的启动类加载器中。

  1. 只有当 父类加载器 反馈 自己无法完成该加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会自己去加载

优点:

Java类随着它的类加载器一起具备了一种带优先级的层次关系

  1. 如:类 java.lang.Object(存放在rt.jar中)在加载过程中,无论哪一个类加载器要加载这个类,最终需委派给模型顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。
  2. 若没有使用双亲委派模型(即由各个类加载器自行去加载)、用户编写了一个java.lang.Object的类(放在ClassPath中),那系统中将出现多个不同的Object类,Java体系中最基础的行为就无法保证

保证java类的安全

大家所熟知的Object类,直接告诉大家,Object默认情况下是启动类加载器进行加载的。假设我也自定义一个Object,并且制定加载器为自定义加载器。现在你会发现自定义的Object可以正常编译,但是永远无法被加载运行。

这是因为申请自定义Object加载时,总是启动类加载器,而不是自定义加载器,也不会是其他的加载器。

1.3沙箱安全机制

沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

1.4补充

1.在jvm中表示两个class对象是否为同一个类存在的两个必要条件

类的完整类名必须一致,包括包名

即使类的完整类名一致,同时要求加载这个类的ClassLoader(指ClassLoader实例对象)必须相同;是引导类加载器、还是定义类加载器

第二章-运行时数据区

image-20200730223731276

image-20200611220712194

==方法区和堆是进程(虚拟机)共享的==

栈、程序计数器、本地方法栈是线程共享的

1程序计数器

程序计数寄存器(Program Counter Register)中,Register的命名源于CPU的寄存器

==它是当前线程所执行的字节码的行号指示器。==

PC寄存器是用来存储指向下一条指令的地址,也即将将要执行的指令代码。由执行引擎读取下一条指令。

1.它是一块很小的内存空间,几乎可以忽略不计。也是运行速度最快的存储区域

2.在jvm规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致

3.任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的java方法的JVM指令地址;或者,如果是在执行native方法,则是未指定值(undefined),因为程序计数器不负责本地方法栈。

4.它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成

5.字节码解释器工作时就是通过改变这个计数器的值来选取吓一跳需要执行的字节码指令

6.它是唯一一个在java虚拟机规范中没有规定任何OOM(Out Of Memery)情况的区域,而且没有垃圾回收

image-20200801112940734

利用javap -verbose xxx.class查看编译后的字节码

image-20200801161518608

1.使用PC寄存器存储字节码指令地址有什么用呢(为什么使用PC寄存器记录当前线程的执行地址呢)

(1)多线程宏观上是并行(多个事件在同一时刻同时发生)的,但实际上是并发交替执行的

(2)因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行 (3)JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令

所以,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。 2.PC寄存器为什么会设定为线程私有? (1)我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停滴做任务切换,这样必然会导致经常中断或恢复,如何保证分毫无差呢?

(2)为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是**为每一个线程都分配一个PC寄存器,**这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。

2java栈

栈是由多个栈帧组成,一个栈帧中存在:局部变量表、操作数栈、动态链接、方法返回值,一个栈帧对应一个方法。

作用:主管java程序的运行,它保存方法的局部变量、8种基本数据类型、对象的引用地址、部分结果,并参与方法的调用和返回。

  • 局部变量:相较于成员变量(成员变量或称属性)

  • 基本数据变量:8种基本数据类型

  • 引用类型变量:类,数组,接口

可以设置栈内存大小 -Xss

我们可以使用参数-Xss选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度。 (IDEA设置方法:Run-EditConfigurations-VM options 填入指定栈的大小-Xss256k)


** 栈帧内部结构**

每个栈帧中存储着

1.局部变量表(Local Variables)

2.操作数栈(Operand Stack)(或表达式栈)

3.动态链接(Dynamic Linking)(或执行"运行时常量池"的方法引用)—-深入理解Java多态特性必读!!

4.方法返回地址(Return Adress)(或方法正常退出或者异常退出的定义)

5.一些附加信息

image-20200801184725160

—-分割—-

第一章-JVM

0JVM内存模型

image-20200611220712194

本地方法库:非java语言编写的方法。 本地方法栈:java中native关键字修饰的方法,表示这个方法不是用java编写的。 执行引擎:负责具体的代码调用及执行过程

程序计数器:它是当前线程所执行的字节码的行号指示器。

栈stack

栈数据线程独有,生命周期随着线程的死亡而结束。

它用来存放,8种基本类型,对象的引用变量,实例方法,都是在栈内存中分配。

java方法=栈帧。栈帧保存3类数据, 本地变量:输入,输出参数。 栈操作:记录出栈、入栈的操作 栈帧数据:包括类文件,方法等。

SatckOverFlowError栈内存溢出,出现原因,方法中调用本身,导致栈内存不足。


线程共享

1方法区,2堆

1方法区

供各线程共享的运行时内存区域。它存储了每一个类的结构信息,例如运行时常量池( Runtime Constant Pool) 、字段和方法数据、构造函数和普通方法的字节码内容。上 面讲的是规范,在不同虚拟机里头实现是不一样的,最典型的就是永久代(PermGen space) 和元空间(Metaspace)。

class类加载

  • 通过类全限定名获取定义此类的二进制字节流;
  • 将字节流代表的静态存储结构转换为方法区的运行时数据结构;
  • 在内存中生成此类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

img

JVM规范并没有规定java.lang.Class类的实例要放到Java堆中,对于HotSpot虚拟机,是放到方法区里面的。这个class对象作为程序访问方法区中的这些类型数据的外部接口。

/

2堆heap

判断对象是否已经死亡的算法:引用计数算法,可达性分析算法; 四个垃圾收集算法:标记清除算法,复制算法,标记整理算法,分代收集算法;

image-20200619235729961

JDK1.8永久区变成了元空间

堆的比例大小。

image-20200620205028834

image-20200614150645309


2内存调优

在Java8中,永久代已经被移除,被一个称为元空间的区域所取代。元空间的本质和永久代类似。

元空间与永久代之间最大的区别在于:

永久带使用的JVM的堆内存,但是java8以后的元空间并不在虚拟机中而是使用本机物理内存

因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由MaxPermSize 控制, 而由系统的实际可用空间来控制。

1
2
3
4
5
long maxMemory = Runtime.getRuntime().maxMemory() ;//返回 Java 虚拟机试图使用的最大内存量。
long totalMemory = Runtime.getRuntime().totalMemory() ;//返回 Java 虚拟机中的内存总量。
System.out.println("MAX_MEMORY = " + maxMemory + "(字节)、" + (maxMemory / (double)1024 / 1024) + "MB");
System.out.println("TOTAL_MEMORY = " + totalMemory + "(字节)、" + (totalMemory / (double)1024 / 1024) + "MB");

发现默认的情况下分配的内存是总内存的“1 / 4”、而初始化的内存为“1 / 64”

VM参数: -Xms1024m -Xmx1024m -XX:+PrintGCDetails

生产环境必须:初始内存Xms和 最大Xmx一致,避免内存忽高忽低。(避免GC和应用程序争抢内存)

-XX:+PrintGCDetails打印GC信息。


GC日志解读:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
GC产生原因 分配失败**
[GC (Allocation Failure)
发生了YoungGen  **
第一个箭头左边指Gc前的新生代内存大小 、GC后的新生代内存大小、GC区域新生代的总大小。**
第二个箭头指的是发生了YoungGen前堆的大小,发生后的堆的大小。**
[PSYoungGen: 1519K->472K(1536K)] 6302K->6262K(8704K), 0.0010356 secs] 
YoungGC用户耗时,YoungGC系统耗时 YoungGC总耗时**
[Times: user=0.00 sys=0.00, real=0.00 secs] 



[Full GC (Ergonomics) [PSYoungGen: 472K->0K(1536K)] [ParOldGen: 5790K->1692K(7168K)] 6262K->1692K(8704K), [Metaspace: 3249K->3249K(1056768K)], 0.0064030 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 

3GC

GC是垃圾回收的意思,主要发生在堆内存中。

GC是什么(分代收集算法) 次数上频繁收集Young区 次数上较少收集Old区 基本不动元空间

判断对象是否已经死亡的算法:引用计数算法,可达性分析算法; 四个垃圾收集算法:标记清除算法,复制算法,标记整理算法,分代收集算法;

1判断死亡2个

引用计数算法,可达性分析算法;

2垃圾回收4个

1复制算法、2标记清除算法,3标记整理算法,4分代收集算法;

1复制算法

因为年轻代中的对象基本都是朝生夕死的(90%以上),所以在==年轻代的垃圾回收算法使用的是复制算法==,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片

但会浪费一半空间。

2标记清除算法

老年代一般是由标记清除或者是标记清除与标记整理的混合实现

image-20200620224454422

首先,它的缺点就是效率比较低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲。 其次,主要的缺点则是这种方式清理出来的空闲内存是不连续的

优点:不需要额外空间。

3标记整理算法

老年代一般是由标记清除或者是标记清除与标记整理的混合实现

image-20200620224826894

标记整理,不再做垃圾回收,而是让所有存活对象都向一端移动,然后直接清除边界以外的内存。

劣势:效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。

4JMM

java memory model Java内存模型

JMM是一种抽象的概念并不真实存在,它描述的是一组规则或者规范,通过这组规范定义了程序中各个变量(包含实例成员变量,静态字段,构成数组对象的元素)的访问方式。

特征:原子、可见、有序

CPU>内存>硬盘

类加载:

静态构造块>构造块>构造方法

静态代码块只加载一次