jvm学习总结1

一、java类的加载机制

一、什么是类的加载

类的加载指的是将类的**.class文件中的二进制数据读入到内存中,将其放在运行时数据区方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 Class对象**, Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

二、类的生命周期

img

加载-》验证-》准备-》解析-》初始化-》使用-》-》卸载

类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。

加载

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

初始化

JVM初始化步骤

  • 1、假如这个类还没有被加载和连接,则程序先加载并连接该类
  • 2、假如该类的直接父类还没有被初始化,则先初始化其直接父类
  • 3、假如类中有初始化语句,则系统依次执行这些初始化语句

类的主动使用包括以下六种 :

  • 创建类的实例,也就是new的方式
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射(如 Class.forName(“com.shengsiyuan.Test”)
  • 初始化某个类的子类,则其父类也会被初始化
  • Java虚拟机启动时被标明为启动类的类JavaTest),直接使用 java.exe命令来运行某个主类

三、类加载器

  • BootstrapLoader(引导类加载器)是用C语言实现的

类加载器的层次关系

img

Bootstrap ClassLoader-》ExtclassLoader-》AppClassLoader-》User ClassLoader

启动类加载器-》扩展类加载器-》应用类加载器-》自定义类加载器

注意:父类加载器并不是通过继承关系来实现的,而是采用组合实现的

  • Java虚拟机的角度来讲,只存在两种不同的类加载器 :

    • 启动类加载器
    • 所有其它的类加载器
  • 启动类加载器 : 负责加载存放在 JDK\jre\lib

  • 扩展类加载器 : 负责加载 JDK\jre\lib\ext

  • 应用程序类加载器 : 负责加载用户类路径(ClassPath)所指定的类

JVM类加载机制

  • 全盘负责
  • 父类委托
  • 缓存机制

代码聚焦:

​ getContextClassLoader()

​ loader.getParent()

  1. ClassLoader loader = Thread.currentThread().getContextClassLoader();
  2. System.out.println(loader);
  3. System.out.println(loader.getParent());
  4. System.out.println(loader.getParent().getParent());

四、类的加载

类加载有三种方式:

1、命令行启动应用时候由JVM初始化加载

  • 2、通过**Class.forName()**方法动态加载

    • 使用**Class.forName()**来加载类,默认会执行初始化块
    • 使用**Class.forName()**来加载类,并指定ClassLoader,初始化时不执行静态块
  • 3、通过**ClassLoader.loadClass()**方法动态加载

    使用ClassLoader.loadClass()来加载类,不会执行初始化块

Class.forName()和ClassLoader.loadClass()区别

  • Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
  • ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
  • Class.forName(name,initialize,loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。

代码聚焦:

ClassLoader loader = HelloWorld.class.getClassLoader();

类名.class.getClassLoader()

Class.forName(类名)

loader.loadClass(类名)

ClassLoader源码分析


双亲委派模型

模型解释

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

双亲委派机制:

  • 1、当 AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
  • 2、当 ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader```去完成。
  • 3、如果 BootStrapClassLoader加载失败(例如在 $JAVA_HOME/jre/lib里未查找到该class),会使用 ExtClassLoader来尝试加载;
  • 4、若ExtClassLoader也加载失败,则会使用 AppClassLoader来加载,如果 AppClassLoader也加载失败,则会报出异常 ClassNotFoundException

二、JVM内存结构

一、JVM内存结构分布

jvm学习总结1_第1张图片

JVM内存结构主要有三大块:堆内存方法区

堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间From Survivor空间To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配

方法区存储类信息常量静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);

又分为java虚拟机栈本地方法栈主要用于方法的执行

二、如何通过参数来控制各区域的内存大小

  • -Xms设置堆的最小空间大小。
  • -Xmx设置堆的最大空间大小。
  • -XX:NewSize设置新生代最小空间大小。
  • -XX:MaxNewSize设置新生代最大空间大小。
  • -XX:PermSize设置永久代最小空间大小。
  • -XX:MaxPermSize设置永久代最大空间大小。
  • -Xss设置每个线程的堆栈大小。

没有直接设置老年代的参数,但是可以设置堆空间大小新生代空间大小两个参数来间接控制。

老年代空间大小=堆空间大小-年轻代大空间大小

三、JVM和系统调用分布

jvm学习总结1_第2张图片

方法区和堆是所有线程共享的内存区域;

而java栈、本地方法栈和程序计数器是运行是线程私有的内存区域。

四、Java堆(Heap)/GC堆

  • 是Java虚拟机所管理的内存中最大的一块。

  • Java堆是被所有线程共享的一块内存区域

  • 唯一目的就是存放对象实例

  • 几乎所有的对象实例都在这里分配内存

  • 收集器基本都是采用的分代收集算法

  • 所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。

  • 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常

五、方法区(Method Area)

  • 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域
  • 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
  • 方法区有时被称为**持久代(**PermGen)
  • 所有的对象在实例化后的整个运行周期内,都被存放在堆内存中。
  • 方法的执行都是伴随着线程的。原始类型/基本数据类型的本地变量以及引用都存放在线程栈中。而引用关联的对象比如String,都存在在中。

六、程序计数器(Program Counter Register)

  • 线程私有的

  • 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

  • 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)。

七、JVM栈(JVM Stacks)

  • 与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的
  • 它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型
  • 每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
  • 局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。

八、本地方法栈(Native Method Stacks)

三、GC算法 垃圾收集器

我们的内存垃圾回收主要集中于 java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的.

一、对象存活判断

判断对象是否存活一般有两种方式:

引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象。

GC Roots包括:

  • 虚拟机栈中引用的对象。
  • 方法区中类静态属性实体引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI引用的对象。

二、垃圾收集算法

1、标记 -清除算法

算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其缺点进行改进而得到的。

主要缺点有两个:

一个是效率问题,标记和清除过程的效率都不高;

另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

2、复制算法

它将可用内存按容量划分大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

3、标记-压缩算法

复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

4、分代收集算法

把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。

而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收

三、垃圾收集器

收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现

1.Serial收集器

串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)

ParNew收集器 ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩

参数控制:

-XX:+UseParNewGC ParNew收集器
-XX:ParallelGCThreads 限制线程数量

2.Parallel收集器

Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩

参数控制: -XX:+UseParallelGC 使用Parallel收集器+ 老年代串行

3.Parallel Old 收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供

参数控制: -XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行

4.CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。

从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为4个步骤,包括:

  • 初始标记(CMS initial mark)
  • 并发标记(CMS concurrent mark)
  • 重新标记(CMS remark)
  • 并发清除(CMS concurrent sweep)

参数控制:

-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)

5.G1收集器

常用的收集器组合

新生代GC策略 老年老代GC策略 说明
组合1 Serial Serial Old Serial和Serial Old都是单线程进行GC,特点就是GC时暂停所有应用线程。
组合2 Serial CMS+Serial Old CMS(Concurrent Mark Sweep)是并发GC,实现GC线程和应用线程并发工作,不需要暂停所有应用线程。另外,当CMS进行GC失败时,会自动使用Serial Old策略进行GC。
组合3 ParNew CMS 使用 -XX:+UseParNewGC选项来开启。ParNew是Serial的并行版本,可以指定GC线程数,默认GC线程数为CPU的数量。可以使用-XX:ParallelGCThreads选项指定GC的线程数。如果指定了选项 -XX:+UseConcMarkSweepGC选项,则新生代默认使用ParNew GC策略。
组合4 ParNew Serial Old 使用 -XX:+UseParNewGC选项来开启。新生代使用ParNew GC策略,年老代默认使用Serial Old GC策略。
组合5 Parallel Scavenge Serial Old Parallel Scavenge策略主要是关注一个可控的吞吐量:应用程序运行时间 / (应用程序运行时间 + GC时间),可见这会使得CPU的利用率尽可能的高,适用于后台持久运行的应用程序,而不适用于交互较多的应用程序。
组合6 Parallel Scavenge Parallel Old Parallel Old是Serial Old的并行版本
组合7 G1GC G1GC -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC #开启; -XX:MaxGCPauseMillis=50 #暂停时间目标; -XX:GCPauseIntervalMillis=200 #暂停间隔目标; -XX:+G1YoungGenSize=512m #年轻代大小; -XX:SurvivorRatio=6 #幸存区比例

后续继续学习网址

lel Old是Serial Old的并行版本 |
| 组合7 | G1GC | G1GC | -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC #开启; -XX:MaxGCPauseMillis=50 #暂停时间目标; -XX:GCPauseIntervalMillis=200 #暂停间隔目标; -XX:+G1YoungGenSize=512m #年轻代大小; -XX:SurvivorRatio=6 #幸存区比例 |

后续继续学习网址

https://zhuanlan.zhihu.com/p/34426768

你可能感兴趣的:(后端-java)