JVM(一) GC垃圾回收,类加载器,java生命周期,强引用,软引用,弱引用,虚引用

一、GC垃圾回收
1.1 、判断对象是否“已死”的方法

1.引用计数器算法
       对象中添加一个引用计数器,如果引用计数器为0,则表明已死, 如果有地方引用它就加1,引用失效就减1。但是这会导致一个致命的问题,对象循环引用,比如A引用B,B引用A,但是实际上他们俩的存在都没有意义了。

2.可达性分析算法:
       这种算法整个对象实例以一个树呈现,根节点是一个称为“GC Root”的对象,从这个对象开始向下搜索并做标记, 遍历完这棵树之后,未被标记的对象就会被认为“已死”,即可被回收的对象。

1.2 、常见GC回收算法

1.标记-清除算法:
       如果再被标记之后直接清除会带来另一个问题–内存碎片化,如果下次有比较大的对象需要在堆上分配比较大的内存空间的时候,可能会出现无法找到连续的空间从而导致另一次垃圾回收。

2.复制算法(Java堆中新生代的垃圾回收算法):
       首先还是先标记待回收和不用回收的内存,下一步将不用回收的内存复制到新的内存区域,这样旧的内存区域就可以全部回收,而新的内存区域就是连续的,这会损失掉部分内存空间用来复制。这种算法适用于对象存活率比较低的场合,经常用于新生代的垃圾回收。
       新生代内存会分为一个较大的Eden区和两个较小的Survivor区(比例8:1:1),当回收时,将Eden区和一个Survivor区的存活对象复制到另一个Survivor区,最后清理掉Eden区和刚才用过的Survivor区。如果几次清理都有对象反复存活,那就将该对象移至老年代。

3.标记-整理算法
       首先从根节点开始进行标记算法,然后将所有被标记对象移至内存一端,然后将边界以外的内存直接清理掉,改垃圾回收算法适用于对象存活率高的场合(老年代)。

4.分代收集算法
       不同的对象的生命周期是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内不同区域采用不同的回收策略是分代收集算法。
       对新生代采用复制算法,对老年代采用标记-清理或者标记-整理算法。

1.3、Minor GC和Full GC 的触发条件

Minor GC触发条件:当Eden区满的时候,触发Minor GC

Full GC触发条件:
(1)调用System.GC()时,系统建议Full GC,但不一定执行
(2)老年代空间不足时
(3)方法区空间不足时
(4)通过Minor GC之后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

二、类加载器
2.1、类加载器

       类加载器用来加载java类到JVM中,java虚拟机使用个Java类的方式如下:

java源程序(.java文件)经过javac编译为Java字节码文件(.class文件),java类加载器是
jre的一部分,java类加载器负责动态加载java类到java虚拟机的内存空间里。
类通常是按需加载,即第一次使用该类时才加载。

Java中的类加载器可以分为两类,一类是系统提供的,另一类开发人员可以自己编写。
系统提供的一般有下面几种:

  • 引导(Bootstrap)类加载器:不继承自java.lang.Classloader,负责加载核心java库,存储在<JAVA_HOME>/jre/lib目录中、
  • 扩展(Extension)类加载器:用来在<JAVA_HOME>/jre/lib/ext或java.ext.dirs指明的目录中加载java的扩展库。Java虚拟机的实现会提供一个扩展库目录,
    该类加载器可以在目录里面查找并加载Java类。
  • Apps类加载器(也称为系统类加载器):根据java应用程序的类路径(java.class.path或者CLASSPATH环境变量)来加载类,一般来说,java应用的类都是由它来完成加载的。

除了系统提供的类加载器之外,开发人员可以通过继承java.lang.ClassLoader的方式实现自己的类加载器。

2.2、双亲委派模型

如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器,每个加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(ClassNotFountException),子加载器才会去尝试加载。

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 {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 如果父加载器和Bootstrap加载器都没有找到的话
                    // 调用当前类加载器的findClass方法来完成类加载。
                    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;
        }
    }

在这里插入图片描述
在这里插入图片描述

2.3、类加载器的工作原理

委托,单一性和可见性。
委托机制
假如你要加载一个Test.class,首先加载这个类的请求由Apps类加载器委托给它的父类加载器Extension类加载器,然后再委托给Bootstrap类加载器,如果父类加载器都没有找到,再由Apps类加载器查找加载。

可见性机制
子类加载器可以看到父类加载器加载的类,反之则不行,比如Test.Class类已经被Apps类加载器加载过了,然后想要使用Extension类加载器加载这个类的话,就会抛出ClassNotFoundException异常。

单一性机制
父类加载过的类不能被子加载器加载第二次。

三、java类的生命周期

指一个class文件从加载到卸载的过程。一个java类完整的生命周期会经过加载、连接、初始化、使用和卸载五个过程。
类加载过程包括加载、连接、初始化几个过程,最终形成JVM可以直接使用的java类型的全过程。
在这里插入图片描述

  • 1.加载 : 查找并加载类的二进制数据
  • 2.连接
    验证:主要目的就是确保被加载类的正确性。比如class文件必须以0xCAFEBABE开头,另外版本号也要做验证,比如用java1.8编译后的class文件要在java1.6上运行,因为版本问题就不会通过。
    准备:准备过程就是分配内存,给类的一些字段设置初始值。比如 public int num = 1;在准备阶段num的值就会被设置为0,到后面初始化阶段才会赋值为1。但是对于static final(常量),在准备阶段就会被赋值。比如public static final int num= 1;在准备阶段num的值就是1。
    解析:将符号引用替换为直接引用(类所在的内存地址)。
  • 3.初始化:主要包括执行类构造方法,static变量赋值语句,static语句块。如果一个子类要初始化,必先初始化其父类。
四、强引用,软引用,弱引用,虚引用
4.1、强引用
Object o = new Object();

强引用的特点:

  • 1、可以直接访问目标对象
  • 2.、引用指向的对象在任何时候都不会被系统回收,JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象
  • 3、强引用可能导致内存泄露,为了避免内存泄露,在使用完之后可以把对象设置为null,如果是集合的话可以使用list.clear()
4.2、软引用

如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足时,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

String str = new String("abc");
SoftReference<String> softReference = new SoftReference<String>(str); // 软引用

软引用可以和一个引用队列(ReferenceQueue)联合使用。如果软引用所引用对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

 String str = new String("abc");
 SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);

当软引用中的内存不足时,JVM首先将软引用中的对象设置为null,然后通知垃圾回收器进行回收。

if(JVM内存不足) {
        // 将软引用中的对象引用置为null
        str = null;
        // 通知垃圾回收器进行回收
        System.gc();
    }

也就是说,垃圾回收线程会在抛出OutOfMemory之前回收软引用对象,而且虚拟机会尽可能优先回收长时间不用的软引用对象,对那些刚构建的软对象虚拟机会尽可能保留,
这就是ReferenceQueue的原因。

4.3、弱引用

弱引用与软引用的区别在于,弱引用具有更短暂的生命周期。在垃圾回收器进行扫描的时候,一旦发现了只有弱引用的对象,不管当前空间足够与否,都会回收它的内存。

String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str);
str = null;

JVM会首先将软引用中的对象设置为null,然后通知垃圾回收器进行回收。

str = null;
System.gc();

弱引用也可以和一个引用队列(ReferenceQueue)相关联。

4.4、虚引用

虚引用并不会决定对象的生命周期,如果一个对象只具有虚引用,那么它就和没有任何引用一样,在任何时候都可以被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。

创建虚引用,必须要与引用队列(ReferenceQueue)相关联。

String str = new String("abc");
ReferenceQueue queue = new ReferenceQueue();
// 创建虚引用,要求必须与一个引用队列关联
PhantomReference pr = new PhantomReference(str, queue);
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值