【JVM】尚硅谷宋红康JVM系列1:内存与垃圾回收篇


b站视频地址:
https://www.bilibili.com/video/BV1PJ411n7xZ/?p=2

评论区大佬笔记:
https://www.yuque.com/mo_ming/gl7b70/rfot9k

https://www.cnblogs.com/yanl55555/category/1686360.html


一、JVM与Java体系结构

1.前言

2.面向人群及参考书目

老师在这里提出了几个问题:

  • “栈管运行、堆管存储 ”这句话一定对吗?
  • Java中的堆一定是多线程共享的吗?
  • Java中的对象一定要创建在堆上吗?
  • 方法区中永久带、元空间到底是什么关系?
  • Java为什么叫“半解释型、半编译型”语言?

3.Java及JVM简介

4.Java发展重大事件

5.虚拟机与Java虚拟机

6.JVM整体结构

如下图,其中方法区和堆是多线程共享的,Java栈、本地方法栈、程序计数器是每个线程独有一份的。
执行引擎相当于把字节码文件翻译成机器语言的引擎,使程序可以在操作系统上运行

7.Java代码的执行流程

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

8.JVM架构模型

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

在这里插入图片描述

9.JVM生命周期

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

10.JVM发展历程

在这里插入图片描述

理解执行引擎
解释器的逐行解释特点使得它响应很快,
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

二、内存结构概述

在这里插入图片描述

1.概述类加载器及类加载过程

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

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

1.1 类加载过程一:Loading

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

1.2 类加载过程二:Linking

在准备阶段:
变量是这个阶段分配值得,但是被final 修饰的static 算是常量了,在编译期就已经分配值了
在这里插入图片描述
在这里插入图片描述

1.3 类加载过程三:Initialization

ps。下图,解释,类变量就是有static修饰的成员变量,所以如果Java程序中没有类变量的显式赋值动作和静态代码块,也没有调用该类的实例的情况,clinit方法是不会出现的
在这里插入图片描述
老师的补充:
clinit 方法相当于类的构造器函数(与类中的静态变量赋值和静态代码块有关)

  • clinit 方法只需要加载一次,加载完以后的类信息就放在方法区(在JDK8的时候叫元空间的一个区域,元空间使用的也就是本地内存,也就是说类加载到内存后给缓存起来了,所以后续使用调用这个类时,加载的都是缓存中的那个类本身,因此clinit也就只需要加载一次就OK了)

init 方法相当于构造器函数。任何一个类在声明以后,内部至少会存在一个类的构造器,(这个构造器可能是你显示声明的,也可能是我们系统默认提供的),它总是会存在的。

在这里插入图片描述

如下图,可以这样把静态变量的声明写在静态代码块的后面,这是因为在“Linking”阶段的“prepare”阶段,默认初始化变量为零值,然后在“Initialization”中顺序执行<clinit>方法中的静态东东,先是执行静态代码块中给number赋值为20,之后再静态变量赋值时number又变成20
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

举个栗子:两个线程加载同一个类

package com.atguigu.java;

/**
 * @author shkstart
 * @create 2020 上午 11:23
 */
public class DeadThreadTest {
    public static void main(String[] args) {
        Runnable r = () -> {
            System.out.println(Thread.currentThread().getName() + "开始");
            DeadThread dead = new DeadThread();
            System.out.println(Thread.currentThread().getName() + "结束");
        };

        Thread t1 = new Thread(r,"线程1");
        Thread t2 = new Thread(r,"线程2");

        t1.start();
        t2.start();
    }
}

class DeadThread{
    static{
        if(true){
            System.out.println(Thread.currentThread().getName() + "初始化当前类");
            while(true){

            }
        }
    }
}

分析上面的代码:

  • 验证了一个类只会被加载一次
  • DeadThread类一旦类初始化,执行 clinit 方法,就会进入死循环。并且这个类加载初始化只会加载一次的,所以一旦有一个线程去加载该DeadThread类,就出不来了,之后其他线程再也无法加载这个类了。(会处于一种加锁的状态)
  • 上面的代码,线程一与线程二只会有一个加载到DeadThread类,打印出static中的语句

执行结果:
在这里插入图片描述

2. 类加载器的分类

前面讲解了,类加载的过程,这节讲述一下有哪几种类加载。

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

在这里插入图片描述

public class ClassLoaderTest {
    public static void main(String[] args) {

        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //获取其上层:扩展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d

        //获取其上层:获取不到引导类加载器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);//null

        //对于用户自定义类来说:默认使用系统类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);//null


    }
}

2.1 引导类加载器Bootstrap ClassLoader

在这里插入图片描述

2.2 扩展类加载器Extension ClassLoader

在这里插入图片描述

2.3 系统类加载器 AppClassLoader

在这里插入图片描述

public class ClassLoaderTest1 {
    public static void main(String[] args) {
        System.out.println("**********启动类加载器**************");
        //获取BootstrapClassLoader能够加载的api的路径
        URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (URL element : urLs) {
            System.out.println(element.toExternalForm());
        }
        //从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器
        ClassLoader classLoader = Provider.class.getClassLoader();
        System.out.println(classLoader);

        System.out.println("***********扩展类加载器*************");
        String extDirs = System.getProperty("java.ext.dirs");
        for (String path : extDirs.split(";")) {
            System.out.println(path);
        }

        //从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器
        ClassLoader classLoader1 = CurveDB.class.getClassLoader();
        System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d

    }
}

2.4 例子:自定义一个类加载器

在这里插入图片描述

在这里插入图片描述

/**
 * 自定义用户类加载器
 * @author shkstart
 * @create 2019 下午 12:21
 */
public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        try {
            byte[] result = getClassFromCustomPath(name);
            if(result == null){
                throw new FileNotFoundException();
            }else{
                return defineClass(name,result,0,result.length);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        throw new ClassNotFoundException(name);
    }

    private byte[] getClassFromCustomPath(String name){
        //从自定义路径中加载指定类:细节略
        //如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。
        return null;
    }

    public static void main(String[] args) {
        CustomClassLoader customClassLoader = new CustomClassLoader();
        try {
            Class<?> clazz = Class.forName("One",true,customClassLoader);
            Object obj = clazz.newInstance();
            System.out.println(obj.getClass().getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.5 ClassLoader自定义类加载器的使用及方法

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

由上图可知,扩展类加载器、系统类加载器都是间接的继承自ClassLoader的

在这里插入图片描述

/**
 * 几种不同的方式获取类加载器
 */
public class ClassLoaderTest2 {
    public static void main(String[] args) {
        try {
            //1.
            ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
            System.out.println(classLoader);
            //2.
            ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
            System.out.println(classLoader1);

            //3.
            ClassLoader classLoader2 = ClassLoader.getSystemClassLoader().getParent();
            System.out.println(classLoader2);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

2.6 双亲委派机制的工作原理

在这里插入图片描述

在这里插入图片描述

解释:双亲委派机制
若自定义了一个类、它所在的包也是自定义的比如com.atguigu.code.Test 加载该类时使用的是AppClassLoader ,但是系统类加载器不会立即加载,而是向上级委托给 Extension ClassLoader,扩展类加载之前讲过是加载 java.etc.dirs 目录下的类,所以不会接受这个委托,于是扩展类加载器继续向上级委托给Bootstrap ClassLoader,引导类加载器是只会加载 java javax sun开头的目录,也不会接受这个委托。

上级都没有一个接受委托的,不帮忙加载这个自定义类,那么这个委托只好向下走最后还回到本身的AppClassLoader,使用系统类加载器加载该类

在这里插入图片描述

2.7 双亲委派机制举例

在这里插入图片描述

在这里插入图片描述

出于安全考虑,禁止自定义的类以java.lang包命名
在这里插入图片描述

2.8 沙箱安全机制

问题:
分析上面的代码和截图,为什么我们自定义shkStart类就会禁止访问java.lang包,而自定义String类就没有报错?
解释:
加载的String类并非我们自定义的String类,而是引导类加载器加载的核心库中的String类。
实质上我们还是不可以自己定义一个类放进java.lang包里面 企图用引导类加载器帮我们加载,这种操作是不安全的也是不允许的,这是出于对类加载器的一种保护机制-----------沙箱安全机制
在这里插入图片描述




最后得更新

宋红康老师讲解得太详细了,很多人在评论区要PPT,其实对我这样得菜鸟来说理解这些内存啊参数啊什么得枯燥得知识点真的是有难度得!!我都会在老师开始讲解之前按下暂停自己看一遍截图,自己体会一遍,然后再听康老师讲。
结果就是真的康老师讲的会让我更好得理解,比喻也非常得加深印象,让那么多密密麻麻得文字记起来没那么困难。所以一定要听老师去讲一遍,不要只看PPT截图啊!

PPT截图实在太多了,放在一个帖子里也太庞大了。我就花了好几天时间自己在本地做了笔记,导出PDF留着以后自己回顾。

在这里插入图片描述

B站评论区有很多人提供PPT截图,想要得去B站看吧。我到这里就不更新啦~
完结撒花~可用了我不少时间呀

  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_popo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值