JVM深入

2 篇文章 0 订阅
类加载过程
类加载:类加载将class文件加载到虚拟机的内存。
1.加载:在磁盘中查找并通过IO读入字节码文件。
2.连接:执行校验、准备、解析步骤。
	1).校验:校验字节码文件正确性(-Xverifynone 关闭大部分的验证)
	2).准备:给类的静态变量分配内存,并赋予默认值
	3).解析:将符号引用替换为直接引用,该阶段会把一些静态方法替换为指向数据所存内存的指针或句柄等,这是所谓的  			静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的符合引用替换为直接引用。
4.初始化:对类的静态变量初始化为指定的值,执行静态代码块。
5.使用
6.卸载
类加载器
#启动类加载器:
	负责加载支撑JVM运行的位于JRE的lib目录下的核心类库。
#扩展类加载器:
	负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包。
#应用程序类加载器:
	负责加载ClassPath路径下的类包,主要是自己写的类。
#自定义加载器:
	负责加载用户自定义路径下的类包。
自定义加载器:
package cn.learn;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;

/**
 * @Author xxt
 * @DATE 2022/5/1 16:56
 */
public class MyClassLoader extends ClassLoader {

    private String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    private byte[] loadByte(String name) throws IOException {
        name = name.replaceAll("\\.", "/");
        FileInputStream fileInputStream = new FileInputStream(classPath + "/" + name + ".class");
        int available = fileInputStream.available();
        byte[] data = new byte[available];
        fileInputStream.read(data);
        fileInputStream.close();
        return data;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            return defineClass(name, data, 0, data.length);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader("d:/test");

        Class<?> clazz = classLoader.loadClass("cn.learn.Link");

        Object obj = clazz.newInstance();

        Method method = clazz.getDeclaredMethod("test", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}
双亲委派机制
加载某个类时会先委托父加载器寻找目标类,父加载器找不到再委托上层父加载器加载,如果所有的父加载器在自己的加载路径下都找不到目标类,则在自己的类加载路径中查找并加载目标类。

为什么要设计双亲委派机制

沙箱安全机制:Java核心类库的类不会被除了启动类加载器之外的其他加载器加载,这样可以防止核心api被随意篡改。
避免类重复加载:当父类加载器加载了目标类,子加载器就没有必要再重新加载,保证被加载类的唯一性。
内存模型

在这里插入图片描述

栈:

栈帧:每一个方法对应一个栈帧 栈帧z主要包含以下几部分:

  1. 局部变量表

    保存局部变量

  2. 操作数栈

  3. 动态链接

  4. 方法出口

方法区:

jdk 1.8 之后使用的是直接内存。主要存的是常量、静态变量、类元信息。

本地方法栈:

本地接口的作用是融合不同的编程语言为Java所用。

堆内存
在这里插入图片描述

VisualVM

VisualVM 是一个工具,它提供了一个可视界面,用于查看 Java 虚拟机 (Java Virtual Machine, JVM) 上运行的基于 Java 技术的应用程序(Java 应用程序)的详细信息。

启动命令:jvisualvm

默认是没有Visual GC 这个工具的,需要下载。

1.点击工具,然后点击插件。
在这里插入图片描述

2.之后选择可用插件。找到Visual GC 选中,点击安装即可。
在这里插入图片描述

例子:
在这里插入图片描述

对象逃逸分析

JVM 三种运行模式:

1.解释模式(Interpreted Mode):只使用解释器(-Xint 强制JVM使用解释模式),执行一行JVM字节码就编译一行为机器码。
2.编译模式(Compiled Mode):只使用编译器(-Xcomp JVM使用编译模式),先将所有的JVM字节码一次编译为机器码,然后一次性执行所有机器码。
3.混合模式(Mixed Mode):(-Xmixed 设置JVM使用混合模式)依然使用解释模式执行代码,但是对于一些“热点”代码采取编译器模式执行,这些热点代码对应的机器码会被缓存起来,下次执行无需再编译。

JVM一般采用混合模式执行代码。 这就是我们常见的JIT(Just In Time Compiler)即时编译技术。

在即时编译过程中JVM可能会对我们的代码做一些优化,比如对象逃逸分析等。

JVM运行模式优点适用场景
解释模式启动快只需要执行部分代码,且大多数代码只会执行一次的情况
编译模式启动慢,但是后期执行速度快,比较占用内存(因为机器码的数量至少是JVM字节码的十倍以上)适合代码可能会被反复执行的场景
混合模式一般JVM所默认的模式

什么是对象逃逸分析?

对象逃逸分析就是分析对象的动态作用域,当一个对象在一个方法中被定义之后,它很有可能被外部方法所引用,例如,作为调用参数传递到其他地方中。

public User test1(){
    User user=new user();
    user.setId(1)
    user.setName("张三")
    return user;
}
public void test2(){
    User user=new user();
    user.setId(1);
    user.setName("张三");
}

这里有两个方法,第一个方法把对象作为返回值返回,第二个方法只在方法内部定义了一个对象test1方法内的对象的作用域不确定,第二个方法对象我们可以认为这个方法结束后就变为无效对象了,那么第二个方法内的对象如果存放到堆内的话,只能靠GC来回收这个对象了,但这个对象是在我们这个方法结束后就没用了,是没有必要存到堆内的,JVM对于这种情况可以通过开启逃逸分析参数(-XX:+DoEscapeAnalysis)来优化对象内存分配位置,使其通过标量替换优先分配在栈上(栈上分配),JDK7之后默认开启逃逸分析,如果要关闭使用参数(-XX:-DoEscapeAnalysis)

关闭和开启逃逸分析

#开启
-XX:+DoEscapeAnalysis
#关闭
-XX:-DoEscapeAnalysis

JVM内存分配与回收策略

对象优先再Eden去分配

  • 大多数情况下,对象在新生代中Eden区分配,当Eden区中没有足够的空间,JVM将发起一次Minor GC。

Minor GC 和 Full GC 区别

  • Minor GC/Young Gc:指发生再新生代的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。
  • Major GC/Full GC:一般会回收老年代、年轻代、方法区的垃圾。Major GC 的速度一般会比Minor GC 的慢10倍以上。

对象进入老年代的四种情况:

1.存活对象达到年龄阈值(默认为15)
2.大对象直接进入老年代(超过了JVM中-XX:PretenureSizeThreshold参数的设置)
3.survivor 空间中年龄1 + 年龄2 +年龄3+ … + 年龄n (n>1)的这些对象大小的总和大于 survivor 空间的一半,	 年龄大于或等于该年龄 n 的对象就会直接进入到老年代。
4.Minor GC后,S区空间不能容纳全部存活对象,直接进入老年代。

老年代空间分配担保机制

  • 在每次年轻代Minor GC 之前JVM 都会计算下老年代剩余可用空间。

    • 如果这个空间小于年轻代现有的所有对象之和(包括垃圾对象),就会看一个-XX:-HandlePromotionFailure(JDK 1.8 默认设置)的参数是否设置了,如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每次minor后进入老年代的对象平均大小。

    • 如果上一次结果是小于或之前说的参数没有设置,那么就会触发一次Full GC 。

    • 如果回收完还是没有足够的空间存放新的对象,就会好生 OOM

判断对象是否可以被回收?

  1. 引用计数法

    给对象中添加一个引用计数器,每当有一个地方引用它,计数器就+1,当引用失效,计数器就-1。计数器为0 的对象就是不可能在被使用的。

  2. 可达性分析算法

    这个算法的基本思想是通过一系列成为GC RootS 的对象作为起始点,从这些节点向下搜索,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象。

    GC Roots根节点:线程栈的本地变量、静态属性、常量、本地方法栈的变量等。
    在这里插入图片描述

四种引用类型

  • 强引用

    普通的变量引用

  • 软引用

    将对象用SoftReference软引用类型的对象包裹,正常情况下不会被回收,但是GC之后发现释放不出空间存放新的对象,则会把这些软引用的对象回收掉,软引用可以用来实现对内存敏感度不高的高速缓存。

    public static SoftReference<User> user = new SoftReference<User>(new User());
    
  • 弱引用

    将对象用WeaKReference弱引用类型的对象包裹,弱引用跟没引用差不多,GC会直接回收掉,很少用。

    public static WeaKReference<User> user = new WeaKReference<User>(new User());
    
  • 虚引用

    虚引用也称为幽灵引用或幻影引用,它是最弱的一种引用关系,几乎不用。

finalize()方法最终判断对象是否存活

即使在可达性分析算法中不可达的对象,也并非是直接被回收,真正要宣告一个对象死亡,至少要经历再次标记过程。

标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。

1.第一次标记并进行一次筛选

对象没有覆盖finalize()方法,直接被回收

2.第二次标记

如果这个对象覆盖了finalize()方法,只需要重新与引用链上的任何一个对象建立关联即可。

怎么判断一个类是无用的?

类需要同时满足已下三种情况:

  1. 该类所有的实例都已经被回收,也就是堆中不存在该类的任何实例。
  2. 加载该类的ClassLoader已经被回收。
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
垃圾收集算法
查看当前 JDK 使用的垃圾收集器
java -XX:+PrintCommandLineFlags -version

GC常用的算法:

  • 标记-清除算法
  • 复制算法
  • 标记-整理算法
  • 分代收集算法

目前主流的JVM(HotSpot)采用的是分代收集算法。

标记-清除(Mark-Sweep)算法

算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。

  • 效率问题
  • 空间问题(标记清除后会产生大量不连续的碎片)
    在这里插入图片描述
复制(Copying)算法

复制算法是将内存分为大小相同的两块,每次只用其中的一块,当这一块内存用完了,就将还存活着的对象复制到另一块上面,然后再把已经使用过的内存空间一次性清理。
在这里插入图片描述

标记-整理(Mark-Compact)算法

根据老年代的特点,垃圾回收的过程与标记-清除算法一样,不过不是直接对可回收对象进行整理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
在这里插入图片描述

分代收集算法

​ 当前所有商用虚拟机都采用“分代收集“(Generational Collection)算法,这种算法根据对象存活周期的不同将内存划分成几块。一般是把Java堆划分为新生代和老年代,这样就可以根据各个代的特点选择最合适的收集算法。

​ 新生代:对象存活率低,适用”复制算法“。

​ 老年代:对象存活率高且没有额外的空间提供分配担保,适用”标记 - 清除算法“或”标记 - 整理算法“。

垃圾收集器

如果说垃圾收集算法是内存回收的方法论,垃圾收集器是内存回收的具体体现。
在这里插入图片描述

Serial 收集器

(-XX:+UseSerialGC -XX:+UseSerialOldGC)

Serial收集器是最基础、历史最悠久的垃圾收集器,通过名字可以联想到他是单线程工作的收集器。这个单线程指的是在他工作是要停止所有用户程序,“Stop The World”,直到它收集完成为止。
在这里插入图片描述

新生代采用复制算法,老年代采用标记-整理算法。

ParNew 收集器

(-XX:+UseParNewGC)

ParNew 收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为和Serial收集器完全一样,默认的收集线程数跟CPU核数相同,当然也可以使用参数(-XX:ParallelGCThreads)指定收集数,但是一般不推荐修改。
在这里插入图片描述

新生代采用复制算法,老年代采用标记-整理算法。

Parallel Scavenge收集器

(-XX:+UseParallelGC (年轻代)) (-XX:+UseParallelOldGC (老年代)) 指定在年轻代还是老年代使用

  • Parallel Scavenge 收集器类似于ParNew收集器,是Server模式(内存大于2G,2个CPU)下的默认收集器。

  • Parallel Scavenge 收集器关注点是吞吐量(高效利用CPU)。

    吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
    在这里插入图片描述

新生代采用复制算法,老年代采用标记-整理算法。

CMS 垃圾收集器(重点)

用于老年代。

CMS收集器(-XX:+UseConcMarkSweepGC (老年代))

CMS(Concurrent Mark Sweep)收集器是以最短回收停顿时间为目标的收集器,它非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作
在这里插入图片描述

优缺点:

#优点
1.并发收集、低停顿
#缺点
1.对CPU资源敏感,会与服务抢资源
2.无法处理浮动垃圾(在并发清理阶段,用户线程产生的垃圾,只能等到下一次GC清理)
3.它使用的是标记-清除算法会导致大量的空间碎片,可以通过-XX:+UseCMSCompactAtFullCollection JVM在执行垃圾清理后进行整理。
4.执行过程的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶段出现,一边回收,系统一边运行,也许还没回收完就再次触发GC,也就是“concurrent mode failure”,此时会进入Stop The Word,用Serial Old 垃圾收集器来回收。

相关参数

-XX:+UseConcMarkSweepGC:启用CMS. 
-XX:ConcGCThreads:并发的GC线程数
-XX:+UseCMSCompactAtFullCollection: FullGC之后做压缩整理(减少碎片)
-XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是O,代表每次FullGC后都会压缩一次
-XX:CMSInitiatingOccupancyFraction:当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比).
-XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阀值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM在第一次使用设定值,后续则会自动调整
-XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor GC 目的在于减少老年代对年轻代的引用降低CMS GC的标记阶段时的开销,一般CMS的GC耗时80%都在标记阶段

G1垃圾收集器

-XX:UseG1Gc

使用的算法是复制算法

G1(Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多个处理器及大容量内存的机器,以及高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。
在这里插入图片描述

Humongous用来存放大对象。

G1收集器一次GC的运作过程大致分为以下几个步骤:

  1. 初始阶段(initial mark,STW):暂停所有的其他线程,并记录下GC Roots 直接能引用的对象,速度很快。
  2. 并发标记(Conocurrent Marking):同CMS的并发标记。
  3. 最终标记(Remark,STW):同CMS的重新标记。
  4. 筛选回收(CleanUp,STW):筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间(默认是200ms,可以使用参数-XX:MaxGCpauseMills指定)来制定回收计划。
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值