JVM类加载,垃圾回收

目录
  • 类加载子系统
    • 双亲委派模型
  • 垃圾回收
    • 判断对象已死
    • JDK1.2之后的四种引用类型:
      • 1.强引用:
      • 2.软引用:
      • 3.弱引用:
      • 4.虚引用:
    • 常见的垃圾回收算法:
      • 1.标记–清除算法:(Mark–Sweep)
      • 2.标记–复制算法:
      • 3.标记–整理算法:
    • 常见的垃圾回收器:
    • 新时代、老年代
      • 为什么大对象会直接存在老年代?
  • 总结

类加载子系统

classLoader 只负责对字节码文件的加载,至于是否可以运行,还要看执行引擎。

  • 加载的类信息存放于方法区的内存空间,除了类信息之外,还会存放有运行时常量池的信息,还可能包含字符串字面量和数字常量。

loading加载:

通过一个类的全限定名获得定义此类的二进制字节流。也就是根据完整的路径找到对应类的二进制字节流。

将这个字节流所代表的静态的数据结构转化为方法区运行时的数据结构

  • 在内存中生成一个代表这个类的Java.lang.Class对象,作为方法区这个方法数据的入口。

链接:验证、准备、解析

验证:目的在于确保二进制字节流包含的信息符合虚拟机的要求,保证被加载类的正确性,不会危害虚拟机的安全。

验证文件格式、字节码、元数据、符号引用的验证

准备:为类变量分配内存并设置默认的初始值,即零值。

不包含被final修饰的类变量

解析:将常量池的符号转化为直接引用的过程。

初始化:JVM将程序的执行权交给程序。

双亲委派模型

双亲委派模型的原理:如果一个类加载器收到了类加载的请求的话,它首先不会自己去尝试加载这个类,而是把这个请求委派给自己的父类加载器去完成,每一层的加载器都是如此,因此所有的加载请求都会传送到最顶层的启动类加载器中,只有当父类加载器反馈自己无法加载这个请求的时候,即父类搜索不到这个类的时候,子类才会自己尝试去加载。

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        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 stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

使用双亲委派模型来组织类加载之间的关系,一个显而易见的好处就是Java中类随着它的类加载器一起具备了一种带有优先级的层级关系。因此Object类在程序中的各种类加载的环境中都能保证是同一个类。反之,如果没有双亲委派模型的话,都由各个的类加载器去加载的话,如果用户自定义了一个java.lang.Object的类,并放在ClassPath 中的话,就会出现多个Object类,Java中最基础的体系也就会无法保证。

破坏双亲委派模型:

1.双亲委派模型被破坏的第一次就是刚引入双亲委派模型的时候,是为了兼容JDK1.2之前的代码

2.双亲委派模型的第二次的破坏就是自生的缺陷导致的。但发生父类调用子类的时候。

3.第三次就是用户对于程序动态性的追求而导致的:代码热的替换、模块热的部署。

垃圾回收

什么是垃圾?

GC 中的垃圾就是特指在内存中的、不会在被使用的对象。

判断对象已死

引用计数器法:

每个对象添加一个引用计数器,每被引用一次,计数器加1,失去引用,计数器减1,当计数器在一段时间内保持为0时,该对象就认为是可以被回收得了。(在JDK1.2之前,使用的是该算法)

缺点:当两个对象A、B相互引用的时候,当其他所有的引用都消失之后,A和B还有一个相互引用,此时计数器各为1,而实际上这两个对象都已经没有额外的引用了,已经是垃圾了。但是却不会被回收,引用计数器法不能解决循环引用的问题。

JVM垃圾回收采用的是可达性分析算法:

从GC set中的GC roots 作为起点,从这些节点向下搜索,搜索的路径被称为引用链,如果一个对象不存在引用链的话,那么说明这个对象已死。就会被GC回收器回收。

GCroots 是:

1.来自于JVM栈中引用的对象。比如各个线程中被调用的方法堆栈中使用到的参数、局部变量、临时变量等。

2.方法区中静态属性引用的对象。比如Java中的引用类型静态变量。

3.方法区中常量引用的对象。比如字符串常量池(String Table)中的引用。

4.本地方法栈中引用的对象。

5.Java虚拟机内部的引用。比如基本数据类型对应的Class对象,一些常驻的异常类等,还有系统的类加载器。

6.所有被同步锁持有的对象。 …

并不是所有的引用对象一定就会被GC 的时候回收掉。

JDK1.2之后的四种引用类型:

1.强引用:

就是程序中一般使用的引用类型,即使发生OOM也不会回收的对象。

就是为刚被new出来的对象所加的引用,它的特点就是,永远不会被GC,除非显示的设置null,才会GC。

比如:

package Reference;
/**
 * user:ypc;
 * date:2021-06-19;
 * time: 9:40;
 */
public class Test {
    public static void main(String[] args) {
        StringBuilder stringBuilder = new StringBuilder("I am FinalReference");
        System.gc();
        System.out.println(stringBuilder);
        //触发GC
        byte[] bytes = new byte[1024 * 940 * 7];
        System.gc();
        System.out.println(stringBuilder);
        try {
            byte[] bytes2 = new byte[1024 * 1024 * 7];
        } catch (Exception e) {
        } finally {
            System.out.println("发生了OOM:");
            System.out.println(stringBuilder);
        }
    }
}

2.软引用:

就是用来描述一些还有用,但是非必须的对象,只被软引用关联着的对象,在系统将要发生OOM前会回收的对象。如果这次回收还没有足够的内存,才会抛出OOM的异常。

package Reference;
/**
 * user:ypc;
 * date:2021-06-19;
 * time: 9:46;
 */
public class SoftReference {
    public static class User {
        private int id;
        private String name;
        User(int id, String name) {
            this.id = id;
            this.name = name;
        }
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    public static void main(String[] args) {
        //user 是强引用
        User user = new User(1001, "小明");
        //softReference 是软引用
        java.lang.ref.SoftReference<User> softReference = new java.lang.ref.SoftReference<>(user);
        //  显示的将强引用置为null
        user = null;
        System.out.println(softReference.get());
        System.gc();
        System.out.println("After GC: ");
        System.out.println(softReference.get());
        //触发GC
        byte[] bytes = new byte[1024 * 940 * 7];
        System.gc();
        System.out.println(softReference.get());
    }
}

3.弱引用:

弱引用也是用来描述那些非必要的对象,它的强度比软引用还低一些。当垃圾回收器开始工作的时候,无论内存是否够用,都会回收掉只被弱引用关联着的对象。

    public static void main(String[] args) {
        User user = new User(1002,"小明");
        java.lang.ref.WeakReference<User> weakReference = new java.lang.ref.WeakReference<>(user);
        user = null;
        System.out.println(weakReference.get());
        System.gc();
        System.out.println("After GC");
        System.out.println(weakReference.get());
    }

4.虚引用:

它是最弱的一种引用关系。刚被创建就会被GC回收器回收。它的价值就是在GC 的时候触发一次方法的回调。

虚拟机中的并行与并发:

并行:

并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在同时的进行工作。

并发:

并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器的线程和用户线程之家同时在运行。

常见的垃圾回收算法:

1.标记–清除算法:(Mark–Sweep)

将死亡的对象标记,然后进行GC。

执行的效率不稳定。如果堆中有大量的对象,其中有大部分都是要被回收的话 ,那么必需要进行大量的标记–清除的步骤,导致执行效率的降低。

会造成内存碎片的问题,使空间的利用率降低。标记–清除之后会产生大量的不连续的内存碎片。

2.标记–复制算法:

将空间分为两部分,一部分始终是未使用的状态。当进行垃圾回收的时候

将存活的对象复制到未使用的空间上,然后将另一半的区域进行全GC。

但是标记–复制算法在对象存活率比较高的时候就要进行多次的复制操作,效率会降低。而且每次只有50%的内存空间被使用。

3.标记–整理算法:

将存活的对象进行移动,然后进行GC。

对象的移动需要STW

解决了垃圾碎片的问题

常见的垃圾回收器:

  • Serial/Serial Old收集器 是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。
  • ParNew收集器 是Serial收集器的多线程版本,使用多个线程进行垃圾收集。
  • Parallel Scavenge收集器 是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。
  • Parallel Old收集器 是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。
  • CMS(Concurrent Mark Sweep)收集器 是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法。
  • G1收集器 是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。

新时代、老年代

Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:Minor GC、FullGC ( 或称为 Major GC )。 Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法。新生代几乎是所有 Java 对象出生的地方,即 Java 对象申请的内存以及存放都是在这个地方。Java 中的大部分对象通常不需长久存活,具有朝生夕灭的性质。

当一个对象被判定为 “死亡” 的时候,GC就有责任来回收掉这部分对象的内存空间。新生代是GC收集垃圾的频繁区域。当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 也就是经历15次GC之后还存活的对象),这些对象就会被移动到老年代。

但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法。

另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。

为什么大对象会直接存在老年代?

大对象的创建和销毁随需要消耗的时间比较多,所以性能也比较满,如果存到新生代的话,那么有可能频繁的创建和销毁大对象,导致JVM对的运行的效率变低,所以直接存放在老年代。

新生代的各个区域的占比分别是:8:1:1 新生代与老年代的占比是:1:2

总结

本篇文章就到里了,希望能帮到你,也希望您能多多关注我们的更多内容!

(0)

相关推荐

  • JVM的7种垃圾回收器(小结)

    垃圾回收算法和垃圾回收器 对于JVM的垃圾回收算法有复制算法.标记清除.标记整理. 用阳哥的话就是:这些算法只是天上飞的理念,是一种方法论,但是真正的垃圾回收还需要有落地实现,所以垃圾回收器应运而生. JVM回收的区域包括方法区和堆,jvm对于不同区域不同的特点采用分代收集算法,比如因为所有的对象都是在Eden区进行分配,并且大部分对象的存活时间都不长,都是"朝生夕死"的,每次新生代存活的对象都不多,所以新采取复制算法:而jvm默认是新生代的对象熬过15次GC才能进入老年代,所以老年代

  • Java虚拟机JVM类加载机制(从类文件到虚拟机)

    一.类加载机制简介 什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构.类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口. 类加载机制:所谓的类加载机制就是虚拟机将class文件加载到内存,并对数据进行验证,转换解析和初始化,形成虚拟机可以直接使用的jav

  • JVM的类加载过程详细说明

    一.基础知识 我们平时写的Java写代码一般都是.java文件,编译成为.class字节码文件,然后类加载器把.class文件加载到JVM内存中,接下来JVM就执行我们的字节码文件,整个过程就是这样. 画个图方便大家好理解: 类加载过程其实非常琐碎且复杂,但是我们只要把握其中的核心工作原理即可 一个类从加载到使用会经历以下步骤: 加载-〉验证-〉准备-〉解析-〉初始化-〉使用-〉卸载 以以下ClassLoadDemo类代码举例: /** * @author god-jiang * @date 2

  • JVM入门之类加载与字节码技术(类加载与类的加载器)

    1. 类加载阶段 1.1 加载阶段 将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有: _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴 露给 java 使用 _super 即父类 _fields 即成员变量 _methods 即方法 _constants 即常量池 _class_loader 即类加载器 _vtable 虚方法表 _ita

  • 浅析JVM垃圾回收的过程

    JVM垃圾回收的算法很多,但是不管是哪种算法,在进行GC时大致的流程都是差不多的,主要有以下3个过程: 1. 枚举根节点 这个过程主要是找到所有的GC Roots对象,这些对象一般发生在JVM虚拟机栈栈帧.常量池中的静态对象.方法区中静态类属性引用.本地方法栈中引用的对象.这个过程会发生STW,所有的线程均运行到安全区域(Safe Region)才开始执行. 通常有两种算法: 引用计数法:每个对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就+1:当引用失效时,计数器值就-1:任何时刻

  • JVM类加载,垃圾回收

    目录 类加载子系统 双亲委派模型 垃圾回收 判断对象已死 JDK1.2之后的四种引用类型: 1.强引用: 2.软引用: 3.弱引用: 4.虚引用: 常见的垃圾回收算法: 1.标记–清除算法:(Mark–Sweep) 2.标记–复制算法: 3.标记–整理算法: 常见的垃圾回收器: 新时代.老年代 为什么大对象会直接存在老年代? 总结 类加载子系统 classLoader 只负责对字节码文件的加载,至于是否可以运行,还要看执行引擎. 加载的类信息存放于方法区的内存空间,除了类信息之外,还会存放有运行

  • JVM的垃圾回收机制你了解吗

    目录 一:回收堆内存 1.如何判定对象已死(可达性分析算法) 2.对象的引用级别 3.对象的死亡过程 二:垃圾回收算法 1.标记清除算法 2.标记复制算法 3.标记整理算法 三:垃圾收集器 1.G1(GarbageFirst) 总结 一:回收堆内存 1.如何判定对象已死(可达性分析算法) 当前主流的商用程序语言的内存管理子系统,都是通过可达性分析算法来判定对象是否存活的.这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程

  • JVM的垃圾回收机制详解和调优

    文章来源:matrix.org.cn 作者:ginger547 1.JVM的gc概述 gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集操作. 在充分理解了垃圾收集算法和执行过程后,才能有效的优化它的性能.有些垃圾收集专用于特殊的应用程序.比如,实时应用程序主要是为了避免垃圾收集中断,而大多数OLTP应用程序则注重整体效率.理解了应用程序的工作负荷

  • JVM的垃圾回收算法工作原理详解

    怎么判断对象是否可以被回收? 共有2种方法,引用计数法和可达性分析 1.引用计数法 所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一.当一个对象的引用计数器为零时,说明此对象没有被引用,也就是"死对象",将会被垃圾回收. 引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象A引用对象B,对象B又引用者对象A,那么此时A,B对象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法

  • JVM的垃圾回收机制真是通俗易懂

    目录 堆内存的划分 划分区域的目的 一.新生区的垃圾回收机制 二.什么时候进入老年区呢? 1经历15次GC后进入老年区 2动态对象年龄判断 3大对象直接进入老年代 4MinorGC后存活的对象太多无法放入Survivor区了 三.老年区空间分配担保原则 四.老年区垃圾回收算法 五.垃圾回收器 总结 堆内存的划分 分为三个部分(以下名词表示同一个区): 新生区.新生代.年轻代 养老区.老年区.老年代 永久区.永久代 划分区域的目的 唯一目的就是优化GC性能. 如果没有分代,我们所有的对象都放在一块

  • JVM的垃圾回收算法一起来看看

    目录 垃圾回收算法 概念 1.标记算法 1.1引用计数法(ReferenceCounting) 1.2可达性分析算法(ReachableAnalysis) 2.回收算法 2.1标记清除算法(MarkSweep) 2.2复制算法(Copying) 2.3标记压缩算法(Mark-Compact) 2.4分代回收算法 总结 垃圾回收算法 概念 垃圾回收(Garbage Collection,GC).程序的运行需要资源,无效的对象如果不及时清理就会一直占用资源,所以对内存资源管理就变得十分重要.而Jav

  • 单例模式垃圾回收_动力节点Java学院整理

    讨论命题:当一个单例的对象长久不用时,会不会被jvm的垃圾收集机制回收. 首先说一下为什么会产生这一疑问,笔者本人再此之前从来没有考虑过垃圾回收对单例模式的影响,直到去年读了一本书,<设计模式之禅>秦小波著.在书中提到在j2ee应用中,jvm垃圾回收机制会把长久不用的单例类对象当作垃圾,并在cpu空闲的时候对其进行回收.之前读过的几本设计模式的书,包括<Java与模式>,书中都没有提到jvm垃圾回收机制对单例的影响.并且在工作过程中,也没有过单例对象被回收的经历,加上工作中很多前辈

  • 浅谈Java垃圾回收的实现过程

    本教程是为了理解基本的Java垃圾回收以及它是如何工作的.这是垃圾回收教程系列的第二部分.希望你已经读过了第一部分:<简单介绍Java垃圾回收机制>. Java垃圾回收是一项自动化的过程,用来管理程序所使用的运行时内存.通过这一自动化过程,JVM解除了程序员在程序中分配和释放内存资源的开销. 启动Java垃圾回收 作为一个自动的过程,程序员不需要在代码中显示地启动垃圾回收过程.System.gc()和Runtime.gc()用来请求JVM启动垃圾回收. 虽然这个请求机制提供给程序员一个启动GC

  • 深入理解JVM垃圾回收算法

    目录 一.垃圾标记阶段 1.1.引用计数法 (java没有采用) 1.2.可达性分析算法 二.对象的finalization机制 2.1.对象是否"死亡" 三.使用(MAT与JProfiler)工具分析GCRoots 3.1.获取dump文件 3.2.GC Roots分析 四.垃圾清除阶段 4.1.标记-清除算法 4.2.复制算法 4.3.标记-压缩(整理,Mark-Compact)算法 4.4.以上三种垃圾回收算法对比 4.5.分代收集算法 4.6.增量收集算法 4.7.分区算法G1

  • JVM的基本介绍以及垃圾回收

    目录 JVM java虚拟机 JVM jvm主要组成部分及其作用 JVM Stack: jvm栈 堆: Jvm heap内存空间划分 Full GC 一.OOM含义: 二.监控GC命令 总结 JVM java虚拟机 JVM java虚拟机是一个可执行java字节码的虚拟机进程.Java虚拟机本质上就是一个程序,java源文件被编译成能被java虚拟机执行的字节码文件,当它在命令行上启动的时候,就开始执行保存在某字节码文件中的指令.Java语言的可移植性正是建立在Java虚拟机的基础上.任何平台只

随机推荐