JUC之基础回顾第二篇(总体第二篇)

上一篇已经学习到了集合类,Callable等基础回顾等信息,接下来复习一下我们的JVM相关互到JUC的基础回顾。

一、JVM

(一)JVM架构图

在这里插入图片描述

  • 记住一句话:栈管运行,堆管存储,程序寄存器管指针。
  • 橙色的占的内存比较多和线程共享,而灰色的比较少和线程私有。

(二)类装载器

  • 上一步的类装载子系统

1、类装载器的作用

在这里插入图片描述

  • 看看底层的关系【我们的小class,装载后转化为大Class,模板装配到了方法区】
    在这里插入图片描述
    在这里插入图片描述
    以后你实例化这个类对象,都是从方法区的模板刻出来的
  • 为什么你的xxx.class文件能被JVM识别呢【是因为我们编译后的文件都存在CAFEBABEI前缀】
    在这里插入图片描述

2、类装载器类型

在这里插入图片描述

(1)BootStrap

  • 看代码启动测试
    BootStrap:是启动类加载器【根加载器】
    在这里插入图片描述
    打出null,是因为我们底层用C++写的,你调用的时候是得不到的。
    在这里插入图片描述
    这个rt.jar里面就有我们的比如String.class,Object.class的压缩包等,启动的时候,这些就默认的加载了,所以我们能直接进去就调用。
    在这里插入图片描述

  • 启动了会加载哪些类呢
    最顶端的BootStrap就是进行java,javax,sun等开头的核心类库进行加载

(2)Extension

  • Extension它加载的类型时
    对我们的jre下的lib下的ext类进行加载
  • 有它的原因:其实刚开始java出来,设计的观念在现在看来都不行,会被替换,所以有扩展的加载器,专门去加载扩展。

(3)Application

  • Application:加载的是我们自定义的类型
    在这里插入图片描述
  • 它能加载的类型:系统类加载器我们自定义类的加载器

(4)自定义类加载器

  • 自定义类加载器:自己定义的【基本不会,除非很大公司,就是继承ClassLoader】

(5)类加载顺序说明

https://blog.csdn.net/weixin_46635575/article/details/122578677
双亲委派机制好处【不会破坏它原有的系统】
在这里插入图片描述

(三)本地接口

1、Native上面东西

在这里插入图片描述

  • 调用多线程启动方法多次(会报错)
    在这里插入图片描述
    在这里插入图片描述

  • 这个start0就是native定义的
    相当于你的程序要去调用操作系统底层的接口了(这个接口给你提供了功能:比如完成计算)

  • 这里的逻辑梳理一下:我们在我们的程序里面用native定义的方法,它会去调用底层的本地方法接口,它的定义的方法会放在本地方法栈里面。(我们非native定义的方法就放在方法栈里面)

  • 为什么有native呢
    其实java在95年诞生的时候,你java要发行,而C语言是老大呀,你就要跟我一点,所以它会有这样的方法,但是也有一点功能实现的目的(因为当时底层是系统用C写的)

  • 目的
    在这里插入图片描述

2、PC寄存器(Program Counter Register)

在这里插入图片描述

  • 不会发生内存溢出

3、小总结

  • 类被类加载器加载到内存里面(并不是所有的都会被加载,而且是按需加载,而且存在双亲委派机制(它的理由是沙箱安全机制))
  • Native讲的过程中(我们的新建new Thread()并不是线程马上启动,现在知道原因了吧,我们创建了后,它要调用底层,底层是未知的,要看CPU才知道,CPU说什么启动再启动)
  • PC寄存器(它就是一个指针,指向内存地址,指向马上要执行的地址(比如A方法的内部有个for循环内调用了B方法,执行到这个后,它的寄存器就会指向了B方法))

(四)方法区

1、是模板等

在这里插入图片描述

  • 存了:类的结构信息(我们new 某个类的对象,都是根据这个模板来生成的对象),比如构造方法,字段,方法数据等信息。
  • 方法区是一个规范:在不同的虚拟机是不一样的,最典型的是永久代(PermGen Space)和元空间(Metaspace)

2、元空间和永久代

具体可以看此文:https://blog.csdn.net/weixin_46635575/article/details/122740245

(五)栈(stack)

复习一句话:栈观运行,堆管存储

1、第一部分(大致介绍)

  • 看一下这里理解(只要报错,我们打印的是栈轨迹)
    在这里插入图片描述
    简单来说就是比较重要,好好学习。

  • 去复习一下队列(先进先出)和栈(后进先出)数据结构

2、第二部分(存储了哪些东西)

(1)有关介绍

  • 栈更多的装载方法的

  • java栈
    在这里插入图片描述
    在这里插入图片描述

    • 首先基本类型都是存储在栈里面
    • 第二所有的引用类型也是(执行new了一个对象在堆里面的对象)都是存在栈里面
    • 实例方法也是存在这里面
    • 方法输入和输出参数和方法内的参数都是
    • 记录入栈和出栈的操作
  • 分析一下执行流程
    在这里插入图片描述
    上面的停着,下面的也不能动。
    在这里插入图片描述

    (2)栈运行原理

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

3、第三部分(测试递归调用:这里有一个错误)

  • StackOverflowError:它是一个错误,
    在这里插入图片描述

  • 错误和异常关系
    在这里插入图片描述
    错误和异常都继承于这个类
    而我们的StackOverflowError就是继承于Error的


  • 在这里插入图片描述

(六)堆+栈+方法区

在这里插入图片描述

  • 再次复习:我们的栈里面存了基本数据类型的变量,和引用对象,方法(栈帧),入栈和出栈的操作。

(七)堆(Heap)

1、概念

1)名称

  • java7
    在这里插入图片描述
    逻辑上分为:新生,养老,永生代
    物理上分为:新生代和老年代

2)GC发生时间

  • 一直new对象:
    我们new了对象,它是伊甸园里面存在,当你死循环在无限的new后,那么它会发生GC,此时发生过后,我们的Eden里面的对象几乎都死完了。

  • 对于上面new的对象,经过GC后,它会被移动到幸存者0区或1区(全部都是移动到一块地区),移动到的幸存者区的这个区此时叫做from区,对应的另外一个幸存者区叫做to区。

  • 经过一次GC后,活下来的对象都被移动到了某一个幸存者区里面,另外一个幸存者区是空着的(因为我们采用的复制引用算法),请记住,我们的幸存者0或1区是不会触发youngGc的,仅仅是当伊甸园区的不能再装了,才会触发,当Eden第二次触发YoungGC后,我们的幸存者区此时也会被同时进行垃圾回收,会把当前的from区里面能活下来的对象移动到to区,此时对象的年龄值就会被增加1,当如此循环,对象的年龄到了15后,就会被移动到永生代里面去了。

  • 如果移动过程中,我们的养老区间,也满了,会触发Full GC = FGC。如果把养老区都整理了,也装满了(就是经过Full GC也还是不能装下的情况下),就会报OOM了。

3)问题分析

(1)第一种情况输出什么呢

在这里插入图片描述
答案是输出20,为什么呢,我们的基本数据类型,是传的复印件(就是没有引用)

首先肯定是我们的main方法入栈,接着去调用changeValue方法(它也会入栈),它执行完后,出栈

(2)第二种情况输出什么呢

在这里插入图片描述
引用类型时传的引用,传一个引用给他它就会修改值。
在这里插入图片描述

(3)第三种情况输出什么呢

在这里插入图片描述
它为什么特殊的输出了hello呢
其实就是我们的常量池在做鬼。

  • 当我们的String str = "xxx"的格式中,它会去常量池汇总找这么一个xxx的字符串,如果找到了那就传递这引用给他,如果没有找到,哪就创建一个。
  • 而我们的String str = new String(“xxx”)的时候,会在堆内存中new和常量池中new
    这里两个的区别可以看这篇文章:https://blog.csdn.net/weixin_46635575/article/details/122782447

2、对象生命周期和GC

在这里插入图片描述

3、永久代

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
永久代几乎不会发生垃圾回收
在这里插入图片描述

4、堆参数的设置

在这里插入图片描述

(1)理论

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

(2)测试

  • 怎么查看CPU的参数呢

    • 获取CPU核数
      在这里插入图片描述
    • 其实这里可以补充一点为什么用Runtime类,它可以理解为运行时数据区的抽象(Runtime DataArea)
      在这里插入图片描述
  • 看内存

public class test {

    public static void main(String[] args) {
        System.out.println(Runtime.getRuntime().availableProcessors());//获取CPU核数
        long maxMemory = Runtime.getRuntime().maxMemory();//返回java虚拟机试图使用的最大内存
        long totalMemory = Runtime.getRuntime().totalMemory();//返回java虚拟机中的内存总量
        System.out.println("试图使用内存大小 = " + maxMemory + "字节" + "=" + (maxMemory / (double)1024/1024) + "MB");
        System.out.println("虚拟机的内存总量" + totalMemory);
    }
}

在这里插入图片描述
大概就是我们的4分之一和上线内存也差不多的是64分支一

  • 那我们可以修改的参数方式
    在这里插入图片描述
    对于Xms和Xmx在工作开发中必须一样,测试环境随便改(避免GC和应用程序争抢内存,理论峰值忽高忽低)
    在这里插入图片描述
    在这里插入图片描述

  • 证明我们的堆内存是分三个区呢
    在这里插入图片描述
    而且我们的PSYoungGen + ParOldGen刚好等于我们的479.5M,证明Metaspace在我们的直接内存里面。

  • 测试OOM(OutOfMemoryError)
    在这里插入图片描述
    首先出现了YoungGC,后来又出现了FullGC了,最后出现了OutOfMemoryError:java heap space(堆报错了)
    还有另外一种情况
    在这里插入图片描述

(3)GC日志分析

[GC (Allocation Failure) [PSYoungGen: 105038K->17264K(149504K)] 105038K->50056K(491008K), 0.0118032 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 118092K->792K(149504K)] 347588K->230288K(491008K), 0.0009803 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 792K->760K(149504K)] 230288K->230256K(491008K), 0.0008719 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 760K->0K(149504K)] [ParOldGen: 229496K->131800K(341504K)] 230256K->131800K(491008K), [Metaspace: 3248K->3248K(1056768K)], 0.0315704 secs] [Times: user=0.14 sys=0.03, real=0.03 secs] 
[GC (Allocation Failure) [PSYoungGen: 2570K->32K(149504K)] 265506K->262968K(491008K), 0.0007913 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 32K->32K(149504K)] 262968K->262968K(491008K), 0.0006594 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 32K->0K(149504K)] [ParOldGen: 262936K->197368K(341504K)] 262968K->197368K(491008K), [Metaspace: 3248K->3248K(1056768K)], 0.0284284 secs] [Times: user=0.11 sys=0.00, real=0.03 secs] 
[GC (Allocation Failure) [PSYoungGen: 2508K->32K(154112K)] 331013K->328536K(495616K), 0.0016326 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 32K->0K(154112K)] [ParOldGen: 328504K->131800K(341504K)] 328536K->131800K(495616K), [Metaspace: 3248K->3248K(1056768K)], 0.0295789 secs] [Times: user=0.19 sys=0.00, real=0.03 secs] 
[GC (Allocation Failure) --[PSYoungGen: 131136K->131136K(154112K)] 262936K->394072K(495616K), 0.0311449 secs] [Times: user=0.09 sys=0.00, real=0.03 secs] 
[Full GC (Ergonomics) [PSYoungGen: 131136K->0K(154112K)] [ParOldGen: 262936K->262936K(341504K)] 394072K->262936K(495616K), [Metaspace: 3248K->3248K(1056768K)], 0.0305174 secs] [Times: user=0.17 sys=0.01, real=0.03 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(153088K)] 262936K->262936K(494592K), 0.0008972 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(153088K)] [ParOldGen: 262936K->262918K(341504K)] 262936K->262918K(494592K), [Metaspace: 3248K->3248K(1056768K)], 0.0676979 secs] [Times: user=0.24 sys=0.02, real=0.07 secs] 
Heap
 PSYoungGen      total 153088K, used 5403K [0x00000000f5980000, 0x0000000100000000, 0x0000000100000000)
  eden space 136704K, 3% used [0x00000000f5980000,0x00000000f5ec6d40,0x00000000fdf00000)
  from space 16384K, 0% used [0x00000000fdf00000,0x00000000fdf00000,0x00000000fef00000)
  to   space 14848K, 0% used [0x00000000ff180000,0x00000000ff180000,0x0000000100000000)
 ParOldGen       total 341504K, used 262918K [0x00000000e0c00000, 0x00000000f5980000, 0x00000000f5980000)
  object space 341504K, 76% used [0x00000000e0c00000,0x00000000f0cc1858,0x00000000f5980000)
 Metaspace       used 3279K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 354K, capacity 388K, committed 512K, reserved 1048576K

在这里插入图片描述

  • 我们的FullGC执行结果:要么OK,要么就OOM了
    在这里插入图片描述

  • 这里还要多一嘴,我们的Young GC会在Eden满了后触发,而我们的OldGC说的是当你Young收拾了,发现还是无法存了,此时会触发OldGC,但是我们的Full GC都是混合使用的,触发了OldGC,一般就是触发FUllGC,对我们的老年代,新生代都进行回收。

(九)GC常用算法

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

1、引用计数算法

(1)复习概念

此方法只需要了解,不用掌握,因为我们的java不采用这种方法(Python是采用的这种)
在这里插入图片描述
这种例子比如我们的链表,几个数据相互连接,没有任何外界对它引用了,而他们是互联的,此时就会是非死对象
在这里插入图片描述

(2)看main方法

此时补充看我们的:public static void main(String[] args)方法的理解(自行脑补):我们后台启动两个线程main线程和GC线程

(3)补充我们的Thread.start()和System.gc()方法

记得当我们的System.gc()启动垃圾回收和我们的Thread.start()一样的,我们启动了,并不是即可启动,而且是会由于操作系统来说(因为他们的底层都是Native处理,这些都是未知的)
在这里插入图片描述

2、复制算法(Coping)

(1)复习概念

其实就是将我们的from复制到to区(也就我们的minor GC采用的这种算法)

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

(2)原理

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

(3)劣势

  • 劣势:空间浪费
    在这里插入图片描述

3、标记清除(Mark-Sweep)

  • 概念很好理解:就是我们的标记,和清除
  • 老年代一般是用的标记清除或者标记整理和标记清除混合使用

(1)原理

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

(2)劣势

在这里插入图片描述

4、标记压缩(Mark-Compact)

  • 其实就是标记,清除,压缩。三步

(1)原理

在这里插入图片描述

(2)劣势

在这里插入图片描述
其实就印证了那句话,慢工出细活。

  • 其实有了另外的一种(但是这一种具体在工作中也没有很说明情况,等工作了再来回答)
    在这里插入图片描述

5、分代收集算法

(1)情况

  • 分代收集算法即没有最好的算法
    不同的代,用不同的算法使用于不同的状态的情况。

(2)总结

在这里插入图片描述

  • 看下面的另外一种情况(在java9默认垃圾回收器采用的G1)
    在这里插入图片描述
    在这里插入图片描述

二、JMM(java内存模型)

(一)概念理解

1、Volatile是java虚拟机提供的轻量级的同步机制

  • 三存(CPU > 内存 > 硬盘):我们之前数据在硬盘里面存起来的,后来感觉写在程序里面,慢了,就有了数据库来方便进行处理,后来发现数据库也慢了,就有了像Redis样的缓存结构的No-SQL数据库,后来感觉还是慢了,现在有了CPU的多级缓存。

  • 保证可见性,不保证原子性,禁止指令重排。

2、内存快照和JMM

再次提醒在我们的CPU和内存之间有了缓存。

  • 内存快照
    在这里插入图片描述
    内存快照就是比如我们的A线程和B线程通知想去修改公共数据age,此时不能直接进行修改,它们各自都要对数据进行复制,然后操作各自的数据,然后再提交给物理内存上值进行修改。

  • 我们的JMM
    在这里插入图片描述
    在这里插入图片描述

3、特征

  • 可见性(就比如我写的文章,那么都可以见;对于程序理解起来就是,A和B线程对我们的数据都是可见的):其实就是一种通知机制,我修改了,其他的就要知道,你就要通知它。
  • 原子性(这个和数据库的原则性一样的:和数据库的事务扯上关系(不可分割的部分))即不可分割的就是原子性。
  • 有序性:正常情况他们想的怎么写,就是按着你写的顺序去执行(但是不一定,就比如上面写到的native定义的方法,交给了操作系统来处理,我们java无法预测)

(二)代码实操

  • 看第一种情况
package cn.mldn.Juc.jvm;

class MyNumber {
    int age = 29;

    public void setAge() {
        age = 129;
    }
}

public class JVMHello {

    public static void main(String[] args) {

        MyNumber myNumber = new MyNumber();

        new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myNumber.setAge();//此时将29修改为129
            System.out.println(Thread.currentThread().getName() + myNumber.age);
        },"AAAA").start();

       

        while (myNumber.age == 29) {
            
        }
        System.out.println(Thread.currentThread().getName());
    }
}

此时情况是无法自己结束的,当我们AAAA线程修改后,我们的main线程一直在傻傻的等着,它也不知道个什么,看下面的对比版本更好理解

  • 第二个版本
package cn.mldn.Juc.jvm;

class MyNumber {
    volatile int age = 29;

    public void setAge() {
        age = 129;
    }
}

public class JVMHello {

    public static void main(String[] args) {

        MyNumber myNumber = new MyNumber();

        new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myNumber.setAge();//此时将29修改为129
            System.out.println(Thread.currentThread().getName() + myNumber.age);
        },"AAAA").start();



        while (myNumber.age == 29) {

        }
        System.out.println(Thread.currentThread().getName());
    }
}

就相当于我们的用volatile定义后,我A线程修改后,它就给其他线程说名情况。

三、代码加载顺序练习

class Code {
    public Code() {
        System.out.println("执行构造方法1111");
    }

    {
        System.out.println("执行代码块222");
    }

    static {
        System.out.println("执行静态代码块333");
    }

}

public class CodeTest {
    {
        System.out.println("执行test代码块444");
    }

    static {
        System.out.println("执行test静态代码块555");
    }

    public CodeTest() {
        System.out.println("执行构造方法666");
    }


    public static void main(String[] args) {
        System.out.println("-------------------------");
        new Code();
        System.out.println("----------------------------");
        new Code();
        System.out.println("----------------------------");
        new CodeTest();
    }
}

一定要记得静态先行(而且只加载一下),其次是构造代码块,其次是是构造方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值