• 请你谈谈你对JVM的理解?Java8虚拟机和之间的变化更新?
  • JVM类加载器是怎么样的?有几种?
  • 什么是OOM,什么是StackOverFlowError? 怎么分析?
  • JVM常用调优参数有哪写?
  • GC有几种算法?分别是怎么执行的?
  • 内存快照如何抓取,怎么分析Dump文件?

目录

  • 一、简介
    • 1. 什么是JVM
    • 2. JVM的位置
    • 3. JVM、JRE、JDK 的关系
    • 4. 三种JVM
  • 二、JVM体系结构
  • 三、类加载器
    • 1. 作用
    • 2. 回顾new对象的过程
    • 3. 类加载器的等级
      • 1. 启动类(根)加载器
      • 2. 拓展类加载器
      • 3. 应用程序加载器
      • 4. 用户自定义类加载器
    • 4. 类加载机制
      • 1. 什么是双亲委派机制
      • 2. 为什么要设计这种机制
      • 3. 双亲委派机制的作用
    • 拓展:沙箱安全机制
      • 什么是沙箱?
      • Java中的安全模型演进
      • 组成沙箱的基本组件
  • 四、本地方法接口
  • 五、PC寄存器
  • 六、方法区
    • 1. 方法区中有啥
    • 2. 创建对象内存分析
  • 七、栈
    • 1、栈中存放啥?
    • 2、栈运行原理
    • 3、堆栈溢出StackOverflowError
  • 八、堆
    • 1. 堆的分区图
    • 2. 堆内存详解
      • Young 年轻代
      • Tenured 老年代
      • Perm 元空间
    • 3. GC垃圾回收
    • 4. 堆内存调优
      • 1. 查看并设置JVM堆内存
      • 2. 什么是OOM
      • 3. 怎么排除OOM错误?
        • 1. 尝试扩大堆内存看结果
        • 2. 利用内存快照工具JProfiler
        • 3. 什么是Dump文件?如何分析?
      • 附:安装Jprofiler教程
        • 1. idea中安装插件
        • 2. 下载客户端
        • 3. 安装客户端
        • 4. IDEA中设置
  • 九、GC算法
    • 1. 引用计数算法(很少使用)
    • 2. 复制算法
    • 3. 标记–清除算法
    • 4. 标记–压缩算法
    • 总结

一、简介

1. 什么是JVM

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

百度的解释云里雾里,对于我们Java程序员,说白了就是:

  • JVM本质上是一个程序/软件,它能识别.class 字节码文件(里面存放的是我们对.java编译后产生的二进制代码),并且能够解析它的指令,最终调用操作系统上的函数,完成我们想要的操作!
  • 关于Java语言的跨平台性,就是因为JVM,我们可以将其想象为一个抽象层,只要这个抽象层JVM正确执行了.class文件,就能运行在各种操作系统之上了!这就是一次编译,多次运行

2. JVM的位置

JVM是运行在操作系统之上的,与硬件没有直接的交互


3. JVM、JRE、JDK 的关系

JDK(Java Development Kit):Java开发工具包

JRE(Java Runtime Environment):Java运行环境

JDK = JRE + javac/java/jar 等指令工具

JRE = JVM + Java基本类库

4. 三种JVM

  • Sun公司 HotSpot(我们都用的这个)
  • BEA公司 JRockit
  • IBM公司 J9 VM

二、JVM体系结构

Java虚拟机主要分为五大模块:

  • 类装载器子系统
  • 运行时数据区
  • 执行引擎
  • 本地方法接口
  • 垃圾收集模块

  • 方法区是一种特殊的堆
  • 栈里面不会有垃圾,用完就弹出了,否则阻塞了main方法
  • 垃圾几乎都在堆里,所以JVM性能调优%99都针对于堆和方法区,主要是堆

详细图


三、类加载器

1. 作用

Class Loader 类加载器:加载.class文件


  1. 加载:加载.Class字节码文件加载到内存中,创建代表这个类的java.lang.Class对象
  2. 链接:对类进行链接,将类的二进制代码合并到Java运行时环境JRE
  3. 初始化:然后交给JVM对类进行初始化

详细过程图示


2. 回顾new对象的过程

public class Student {//私有属性private String name;//构造方法public Student(String name) {this.name = name;}
}
  • 类是模板、模板是抽象的;对象是具体的,是对抽象的实例化
//运行时,JVM将Test的信息放入方法区
public class Test{//main方法本身放入方法区public static void main(String[] args){//s1、s2、s3为不同对象Student s1 = new Student("zsr");  //引用放在栈里,具体的实例放在堆里Student s2 = new Student("gcc");Student s3 = new Student("BareTH");System.out.println(s1.hashCode());System.out.println(s2.hashCode());System.out.println(s3.hashCode());//class1、class2、class3为同一个对象Class<? extends Student> class1 = s1.getClass();Class<? extends Student> class2 = s2.getClass();Class<? extends Student> class3 = s3.getClass();System.out.println(class1.hashCode());System.out.println(class2.hashCode());System.out.println(class3.hashCode());}
}

根据结果,我们发现:

  • s1、s2、s3的hashcode是不同的,因为是三个不同的对象,对象是具体的
  • class1、class2、class3的hashcode是相同的,因为这是类模板,模板是抽象的

我们画图分析以下new一个对象的流程:

  1. 首先Class Loader读取字节码Student类编译生成的.class文件,加载初始化生成Student模板类也就是相应的java.lang.Class对象
  2. 然后通过该Student模板类创建出三个具体的实例对象s1、s2、s3,这三个对象的引用在中,具体的实例是在



3. 类加载器的等级

我们编写这样一个程序,输出当前的类以及其父类往上的加载器

根据返回结果,可以看到不同等级的加载器,接下来介绍这些不同等级的类加载器

1. 启动类(根)加载器

启动类/根加载器:BootstrapClassLoader

  • c++编写,用于加载 java核心库 java.*,构造 拓展类加载器应用程序加载器

  • 根加载器 加载 拓展类加载器,并且将 拓展类加载器 的父加载器设置为 根加载器

    然后再加载 应用程序加载器,应将 应用程序加载器 的父加载器设置为 拓展类加载器

  • 由于引导类加载器涉及到虚拟机本地实现细节,我们无法直接获取到启动类加载器的引用;这就是上面那个程序我们第三个结果为null的原因。

  • 加载文件存在位置rt.jar:Java基础类库,也就是Java doc里面看到的所有的类的class文件

2. 拓展类加载器

拓展类加载器:PlatformClassLoader

  • java编写,加载扩展库,开发者可以直接使用标准扩展类加载器。

  • java9之前为ExtClassloader,Java9以后改名为PlatformClassLoader

  • 加载文件存在位置:/jre/lib/ext/*.jar

3. 应用程序加载器

应用程序加载器:AppClassLoader

  • java编写,加载程序所在的目录下的类,也就是ClassPath路径下的类包
  • 是Java默认的类加载器

4. 用户自定义类加载器

用户自定义类加载器:CustomClassLoader

  • 开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求

4. 类加载机制

类加载器通过 双亲委派机制 交给其他加载器来加载指定的类

1. 什么是双亲委派机制

我们查看“java.lang”包下的ClassLoader类,找到其中的loadClass方法

public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);
}protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// // 首先,检查是否已经被类加载器加载过Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {// 存在父加载器,递归的交由父加载器加载if (parent != null) {c = parent.loadClass(name, false);} else {// 直到最上面的Bootstrap类加载器c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statsPerfCounter.getParentDelegationTime().addTime(t1 - t0);PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

这段源码很清晰的解释了双亲委派机制,下图就是该过程的描述

当一个.class文件要被加载时,不考虑我们自定义类加载器类,首先会在AppClassLoader检查是否加载过,如果有那就无需再加载;如果没有会交到父加载器,然后调用父加载器的loadClass方法。父加载器同样也会先检查自己是否已经加载过,如果没有再往上,直到到达BootstrapClassLoader之前,都是在检查是否加载过,并不会选择自己去加载。到了根加载器时,才会开始检查是否能够加载当前类,能加载就结束,使用当前的加载器;否则就通知子加载器进行加载;子加载器重复该步骤。如果到最底层还不能加载,就抛出异常ClassNotFoundException

总结:所有的加载请求都会传送到根加载器去加载,只有当父加载器无法加载时,子类加载器才会去加载

2. 为什么要设计这种机制

这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被BootstrapClassLoader加载过了,所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。

测试一下:我们重写java.lang包下的String类,然后实例化出一个String对象进行测试

发现报错了,这就是双亲委派机制作用,当我们实例化出一个String的对象时,类加载器收到String类加载的请求将这个请求向上委托给父类加载器去完成,从应用程序加载器向上委托到拓展类加载器,直到根加载器,发现根加载器中已经加载过String类(也就是rt.jar中的java.lang.String),于事就结束了,不会再向下加载,也就是这里的String不是当前应用我们写的类,就不会执行我们编写的这个String类,因此报错

3. 双亲委派机制的作用

1️⃣ 避免类的重复加载

通过不断委托父加载器直到根加载器进行加载,如果父加载器加载过了,子加载器就不用再加载

2️⃣ 保证Java核心库类型的安全

如保证了上述的java.lang.String类不能被篡改


拓展:沙箱安全机制

这里引用了这篇博文https://www.cnblogs.com/zhahu/p/12576351.html,了解即可

什么是沙箱?

Java安全模型的核心就是Java沙箱(sandbox)

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

所有的Java程序运行都可以指定沙箱,可以定制安全策略。

Java中的安全模型演进

​ 在Java中将执行程序分成本地代码远程代码两种

  • 本地代码可信任,可以访问一切本地资源。
  • 远程代码不可信任,在早期的Java实现中,安全依赖于沙箱 (Sandbox) 机制。

如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。

因此在后续的 Java1.1 版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。

Java1.2版本中,再次改进了安全机制,增加了代码签名

  • 不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。

当前最新的安全机制实现,则引入了域 (Domain) 的概念。

  • 虚拟机会把所有代码加载到不同的系统域应用域
  • 系统域部分专门负责与关键资源进行交互
  • 应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。
  • 虚拟机中不同的受保护域 (Protected Domain),对应不一样的权限 (Permission)。存在于不同域中的类文件就具有了当前域的全部权限下

组成沙箱的基本组件

1. 字节码校验器bytecode verifier

确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类(如上述java.lang.String)。

2. 类装载器class loader

类装载器在3个方面对Java沙箱起作用

  • 它防止恶意代码去干涉善意的代码;
  • 它守护了被信任的类库边界;
  • 它将代码归入保护域,确定了代码可以进行哪些操作。

虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。

类装载器采用的机制是双亲委派模式

  1. 从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
  2. 由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
  • 存取控制器(access controller):存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。
  • 安全管理器(security manager):是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
  • 安全软件包(security package):java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:
    1. 安全提供者
    2. 消息摘要
    3. 数字签名
    4. 加密
    5. 鉴别

四、本地方法接口

JNIJava Native Interface,本地方法接口

Java诞生的时候C/C++横行,为了立足,必须要能调用C/C++的程序;于是在内存区域中专门开辟了一块标记区域:Native Method Stack,登记所有的native本地方法(只是登记并不能执行);

什么是本地方法呢?

  • 本地方法是由其它语言编写的,编译成和处理器相关的机器代码。

  • 本地方法保存在动态链接库中,即.dll(windows系统)文件中,格式是各个平台专有的。

  • 通过本地方法,JAVA程序可以直接访问底层操作系统的资源,但是这样用,java程序就变成平台相关了,

因为本地方法的动态库是与平台相关的,此外使用本地方法还可能把程序变得和特定的JAVA平台实现相关。

为了调用这些本地方法,就出现了JNI,让Java可以在执行引擎执行的的时候通过JNI加载本地方法库的方法,实现了与操作系统的交互

在多线程学习的时候,我们启动一个线程调用start()方法本质上是调用了start0本地方法

private native void start0();

可以看到start0()是用native关键字修饰的,那么native关键字是什么意思呢?

凡是带native关键字的,说明java的作用范围达不到了,会去调用底层本地方法库!通过本地方法接口JNI调用本地方法,拓展Java的使用,融合不同的语言为Java所用

目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间通信很发达,比如可以使用 Socket通信,也可以使用 Web service等等,了解即可!


五、PC寄存器

程序计数器Program Counter Register

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计


六、方法区

方法区Method Area

  • 方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义
  • 简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间
  • 方法区与Java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据。
  • 虽然Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java 堆区分开来

1. 方法区中有啥

  1. 静态变量(static)
  2. 类的抽象模板Class信息(版本、字段、方法、接口等描述信息,也就是Class对象)
  3. 运行时常量池

注意:实例变量在堆内存中,与方法区无关

2. 创建对象内存分析

  • 创建一个对象时,方法区中会生成对应类的抽象模板;还有对应的常量池、静态变量、类信息、常量
  • 我们通过类模板去new对象的时候
    • 堆中存放实例对象
    • 栈中存放对象的引用,每个对象对应一个地址指向堆中相同地址的实例对象

例如这个例子中,生成了对应的Person模板类,name常量“zsr”放在常量池中,三个对象的引用放在栈中,该引用指向放在堆中的三个实例对象。这就是堆、栈、方法区的交互关系


七、栈

又称栈内存,主管程序的运行,生命周期和线程同步

  • 栈是线程级的:线程结束,栈内存就释放了,不存在垃圾回收
  • 先进后出

1、栈中存放啥?

  1. 8大基本类型
  2. 对象引用
  3. 实例的方法

2、栈运行原理

  • 栈表示Java方法执行的内存模型
  • 每调用一个方法就会为每个方法生成一个栈帧(Stack Frame),每个方法被调用和完成的过程,都对应一个栈帧从虚拟机栈上入栈和出栈的过程。
  • 程序正在执行的方法一定在栈的顶部

3、堆栈溢出StackOverflowError

举个例子:

public class Test {public static void main(String[] args) {new Test().a();}public void a() {b();}public void b() {a();}
}

最开始,main()方法压入栈中,然后执行a(),a()压入栈中;再调用b(),b()压入栈中;以此往复,a与b方法不断被压入栈中,最终导致栈溢出


八、堆

Heap,堆在JVM启动的时候创建,其空间大小也被创建,堆内存的大小是可以调节的;

不同于栈:

  • 一个JVM只有一个堆内存,而栈是线程级的
  • 垃圾几乎都在堆里,所以JVM性能调优%99都针对于堆和方法区,主要是堆

堆中存放什么:

  • 类的实例化对象

1. 堆的分区图

2. 堆内存详解


Young 年轻代

对象诞生、成长甚至死亡的区,分为 伊甸园区幸存区

  • Eden Space伊甸园区:所有的对象都是在此new出来的
  • Survivor Space幸存区
    • 幸存0区From Space
    • 幸存1区To Space
    • 这两个区是动态的,会互相交换

Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1


Tenured 老年代

年轻代 存活下来的 进入 老年代


Perm 元空间

存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收!关闭虚拟机就会释放这个区域内存!方法区在此

  • 这个区域常驻内存,用来存放JDK自身携带的Class对象、Interface元数据。

  • 什么情况永久区会崩?

    一个启动类加载了大量的第三方Jar包,Tomcat部署了过多应用,或者大量动态生成的反射类

    这些东西不断的被加载,直到内存满,就会出现OOM

  • 名称演变

    jdk1.6之前: `永久代`
    jdk1.7: `永久代`慢慢退化 ==> `去永久代`
    jdk1.8之后: `永久代`改名为`元空间`
    

注意:元空间在逻辑上存在,在物理上不存在

新生代 + 老年代的内存空间 = JVM分配的总内存


3. GC垃圾回收

GC垃圾回收,主要在年轻代和老年代

  • Minor GC:伊甸园区满时触发;从年轻代回收内存
  • Full GC:老年代满时触发;清理整个堆空间,包含年轻代和老年代

首先,对象出生再伊甸园区

  • 假设伊甸园区只能存一定数量的对象,则每当存满时就会触发一次 轻GC(Minor GC)
  • 轻GC 清理后,有的对象可能还存在引用,就活下来了,活下来的对象就进入幸存区;有的对象没用了,就被GC清理掉了;每次 轻GC 都会使得伊甸园区为空
  • 如果对象在年轻代经历了15次GC还没有死亡(可设置-XX:MaxTenuringThreshold=n新生代的对象最多经历n次GC, 就能晋升到老年代),则会进入老年代,如果老年代满了,就会触发一次 重GC(FullGC)年轻代+老年代的对象都会清理一次,活下的对象就进入老年代
  • 如果新生代老年代都满了,则OOM

何时进入老年代


4. 堆内存调优

1. 查看并设置JVM堆内存

查看我们jvm的堆内存

public class Test {public static void main(String[] args) {//返回jvm试图使用的最大内存long max = Runtime.getRuntime().maxMemory();//返回jvm的初始化内存总量long total = Runtime.getRuntime().totalMemory();//默认情况下:分配的总内存为电脑内存的1/4,初始化内存为电脑内存的1/64System.out.println("max=" + max / (double) 1024 / 1024 / 1024 + "G");System.out.println("total=" + total / (double) 1024 / 1024 / 1024 + "G");}
}


默认情况下

  • JVM最大分配内存为电脑内存的1/4
  • JVM初始化内存为电脑内存的1/64

我们可以手动调堆内存大小


VM options中可以指定jvm试图使用的最大内存jvm初始化内存大小

-Xms1024m -Xmx1024m -Xlog:gc*
  • -Xmx用来设置jvm试图使用的最大内存,默认为1/4
  • -Xms用来设置jvm初始化内存,默认为1/64
  • -Xlog:gc*用来打印GC垃圾回收信息

2. 什么是OOM

内存溢出:java.lang.OutOfMemoryError

产生原因

  1. 分配的太少
  2. 用的太多
  3. 用完没释放

我们可以写一个实例模拟堆内存溢出

package java.lang;import java.util.Random;public class Test{public static void main(String[] args){String str = "zsr";while(true){str += str + new Random().nextInt(88888888)+ new Random().nextInt(888888888);}}
}

3. 怎么排除OOM错误?

1. 尝试扩大堆内存看结果

利用上述方法指定jvm试图使用的最大内存jvm初始化内存大小

2. 利用内存快照工具JProfiler

例如:MAT(Eclipse)、JProfiler

作用:分析Dump内存文件,快速定位内存泄漏;获得堆中的文件;获得大的对象…

3. 什么是Dump文件?如何分析?

Dump文件是进程内存镜像,可以把程序的执行状态通过调试器保存到dump文件中

举个例子

import java.util.ArrayList;public class Test {byte[] array = new byte[1024 * 1024];//1Mpublic static void main(String[] args) {ArrayList<Test> list = new ArrayList<>();int count = 0;try {while (true) {list.add(new Test());count++;}} catch (Exception e) {System.out.println("count=" + count);e.printStackTrace();}}
}

运行该程序,报错OOM

接下来我们设置以下堆内存,并附加生成对应的dump文件的指令

-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
  • -XX:+HeapDumpOnOutOfMemoryError表示当JVM发生OOM时,自动生成DUMP文件。

再次点击运行,下载了对应的Dump文件

我们右键该类,点击Show in Explorer

一直点击上级目录,直到找到.hprof文件,与src同级目录下

我们双击打开,可以看到每块所占的大小,便于分析问题 ![image-20200803103451739](https://img-blog.csdnimg.cn/img_convert/7946baed860b658a5e33cf8c0a64e243.png) 点击`Thread Dump`,里面是所有的线程,点击对应的线程可以看到相应的错误,反馈到具体的行,便于排错 ![image-20200803103549701](https://img-blog.csdnimg.cn/img_convert/3bcd33d54cd1918d6d61763e0576a910.png)

每次打开Dump文件查看完后,建议删除,可以在idea中看到,打开文件后生成了很多内容,占内存,建议删除

附:安装Jprofiler教程

1. idea中安装插件

2. 下载客户端

下载地址:https://www.ej-technologies.com/download/jprofiler/files

3. 安装客户端

选择自定义安装,注意:路径不能有中文和空格

后续默认,安装成功即可

4. IDEA中设置

安装完成后,重启IDEA,可以看到我们的内存快照工具

打开IDEA的设置,找到Tools里面的JProfiler,没有设置位置则设置位置

此时则全部安装完成


九、GC算法

Garbage Collection:垃圾回收

我们已经对GC的流程进行了大概的讲解,这里做一些总结

  • JVM在进行GC时,并不是对年轻代老年代统一回收;大部分时候,回收都是在年轻代

  • GC分为两种

    • 轻GC(清理年轻代)
    • 重GC(清理年轻代+老年代)

1. 引用计数算法(很少使用)

  • 每个对象在创建的时候,就给这个对象绑定一个计数器
  • 每当有一个引用指向该对象时,计数器加一;每当有一个指向它的引用被删除时,计数器减一
  • 这样,当没有引用指向该对象时,该对象死亡,计数器为0,这时就应该对这个对象进行垃圾回收操作
  • 计数器本身也是一种消耗

2. 复制算法

复制算法主要发生在年轻代幸存0区幸存1区

  • 当Eden区满的时候,会触发轻GC,每触发一次,活的对象就被转移到幸存区,死的就被GC清理掉了,所以每触发轻GC时,Eden区就会清空;
  • 对象被转移到了幸存区,幸存区又分为From SpaceTo Space,这两块区域是动态交换的,谁是空的谁就是To Space,然后From Space就会把全部对象转移到To Space去;
  • 那如果两块区域都不为空呢?这就用到了复制算法,其中一个区域会将存活的对象转移到令一个区域去,然后将自己区域的内存空间清空,这样该区域为空,又成为了To Space
  • 所以每次触发轻GC后,Eden区清空,同时To区也清空了,所有的对象都在From区

这也就是幸存0区幸存1区总有一块为空的原因

好处:没有内存的碎片(内存集中在一块)

坏处

  1. 浪费了内存空间(浪费了幸存区一半空间)
  2. 对象存活率较高的场景下(比如老年代那样的环境),需要复制的东西太多,效率会下降。

最佳使用环境:对象存活度较低的时候,也就是年轻代

3. 标记–清除算法

为每个对象存储一个标记位,记录对象的生存状态

  1. 标记阶段:这个阶段内,为每个对象更新标记位,检查对象是否死亡;
  2. 清除阶段:该阶段对死亡的对象进行清除,执行 GC 操作。

缺点:两次扫描严重浪费时间,会产生内存碎片

优点:不需要额外的空间

4. 标记–压缩算法

标记-整理法标记-清除法 的一个改进版。

又叫做 标记-清除-压缩法

  1. 标记阶段,该算法也将所有对象标记为存活和死亡两种状态;
  2. 不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是再次扫描,向一段移动存活的对象,然后把剩下的所有对象全部清除。

缺点:多了一次移动成本

可以进一步优化,在内存碎片不太多的情况下,就继续标记清除,到达一定量的时候再压缩

总结

  • 内存(时间复杂度)效率:复制算法 > 标记清除算法 > 标记压缩算法
  • 内存整齐度:复制算法 = 标记压缩法 > 标记清除法
  • 内存利用率:标记压缩法 = 标记清除法 > 复制算法

思考:有没有最优的算法?

没有最优的算法,只有最合适的算法,所以 GC 也称为 分代收集算法,对于年轻代和老年代采用不同的算法

对于年轻代

  • 对象存活率低
  • 用复制算法

对于老年代

  • 区域大,对象存活率高
  • 标记清除+标记压缩混合实现

面试高频!JVM必备教程~相关推荐

  1. 秋招来袭,想学习Java进大厂的小伙伴,这7点武林秘籍一定要点开看看!(附一线互联网大厂MySQL面试高频考点)

    转眼间六月已经过了十天了.六月虽是盛夏,但秋招也不远了.提早做好准备才能在实战中游刃有余. 今天整理更新一篇Java学习分阶段路线攻略.希望能给大家一些启发,找到一条属于自己的学习线路. 本文为转载. ...

  2. 2020春招 / 2021秋招阿里、腾讯、字节、快手、美团 JAVA 开发岗面试高频问题总结

    2020春招 / 2021秋招阿里.腾讯.字节.快手.美团 JAVA 开发岗面试高频问题总结 2.1 进程和线程的区别? 2.2 进程的调度算法有哪些?(主要) 2.3 常用 IO 模型? 2.4 s ...

  3. 黑马程序员《JVM完整教程》笔记 - p001- p015

    黑马程序员<JVM完整教程>笔记 - p001- p015 引言 什么是JVM? 学习JVM有什么用? 内存结构 程序计数器 虚拟机栈 栈内存溢出 线程运行诊断 b站链接: 黑马程序员JV ...

  4. 面试高频题:Spring和SpringMvc父子容器你能说清楚吗

    引言 以前写了几篇关于SpringBoot的文章<面试高频题:springBoot自动装配的原理你能说出来吗>.<保姆级教程,手把手教你实现一个SpringBoot的starter& ...

  5. java求职攻略_2020年求职攻略 《Java面试连成诀》教程免费分享

    原标题:2020年求职攻略 <Java面试连成诀>教程免费分享 IT行业薪资高.就业好.发展前景广阔,而面试是我们打开事业大门的第一关.技术的革新以及IT从业者增多加大了市场竞争,面试中问 ...

  6. Java开发面试高频考点学习笔记(每日更新)

    Java开发面试高频考点学习笔记(每日更新) 1.深拷贝和浅拷贝 2.接口和抽象类的区别 3.java的内存是怎么分配的 4.java中的泛型是什么?类型擦除是什么? 5.Java中的反射是什么 6. ...

  7. 2020年Android面试高频知识点小结+9家公司面试真题

    作者:小羊子说 前言 正直2020金九银十,第一次换工作或是面试候选人,我们都会对面试知识做一次总结梳理,毕竟开发技术无边界,不同人对技术的解读不同.文章总结了最近大半年中的面试考查点V1.0,希望对 ...

  8. 2021秋招-面试高频2-BN、LN、WN相关

    面试高频2-BN.LN.WN相关 终极问题? 1. BN.LN.WN.IN.CN分别是怎样计算的? CV和NLP方面有什么区别? 参考2-知乎-如何区分并记住常见的几种 Normalization 算 ...

  9. Web开发:HTML5、CSS、JavaScript必备教程

    2019独角兽企业重金招聘Python工程师标准>>> Web开发:HTML5.CSS.JavaScript必备教程 想开启Web开发的智慧之门吗?想DIY炫酷的网页特效设计吗?想通 ...

最新文章

  1. traceback异常打印
  2. Vue.js not detected
  3. python实现快排算法_Python实现快速排序算法
  4. Magento 添加后台管理 addColumn
  5. 2.7、Spring Boot 异常处理体系
  6. 开发软件快捷键(持续更新中)
  7. 十、Python-模块
  8. css 立体管道图_高层住宅管道井内密集管线施工方法研究
  9. 好用靠谱的人事管理软件推荐?
  10. wincc报表日报表实例_wincc报表例程
  11. 2016苹果开发者账号注册申请流程链接
  12. 伴鱼使用教程!【原创】
  13. 河南省哪所技校学计算机软件工程,河南技校排名前十有哪些?都有什么专业
  14. 使用再生龙制作linux系统镜像及还原,再生龙软件备份和还原linux系统
  15. Oculus联合创始人Palmer Luckey:我不认为Facebook是VR的未来
  16. 解决打开管家婆软件报表慢的问题
  17. VMware设置共享文件夹之后/mnt/hgfs里面什么也没有
  18. 百度飞桨第一天学习笔记
  19. 在C#中根据HardwareID获取驱动程序信息
  20. php接口上传头像 app,php调用美图秀秀插件上传头像

热门文章

  1. 2021-10-27 我与地坛
  2. 2022-2028年中国汽车天线行业市场需求与投资规划分析报告
  3. 2022-2028年中国阻燃纤维行业市场研究及前瞻分析报告
  4. windows阿里云服务器配置
  5. 什么是高/低方差、高/低偏差、(推荐阅读)
  6. NLP进阶之(七)膨胀卷积神经网络
  7. NNVM Compiler,AI框架的开放式编译器
  8. 在NVIDIA A100 GPU上利用硬件JPEG解码器和NVIDIA nvJPEG库
  9. 嵌入式开发在过去20年中是如何演变的
  10. 可以控制到多低(功率)?