栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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();
    }
}

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/847513.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号