• RV1-Java:面向对象、集合、线程、JVM内存、类加载、GC


    1.1 简介

    此文主要内容:

    • Java面向对象三大特征
    • ArrayList与LinkedList的区别
    • 集合中哪些是线程安全,哪些不是线程安全的
    • 如果对于员工入职时间进行排序,应该使用什么集合
    • HashMap的底层原理
    • 线程生命周期
    • JVM内存模型
    • 常量池
    • 类加载机制
    • GC机制

    1.2 内容

    1.2.1 三大特征
    1. 封装: 从狭义上来讲,封装即是属性私有化,并提供共有的数据访问函数,提高了程序的安全性。从广义上来讲,封装是集成与包装,例如:Mybatis集成JDBC、SpringBoot 集成 SpringMVC、SpringCloud集成SpringCloud、zlt-mp集成SpringCloud Alibaba。
    2. 继承: 在父类基础之上,创建子类,子类可以集成父类大部分的属性与方法,提高了代码的复用性,例如:equas是Object超类的方法,Java中的任何一个类都继承了此方法。
    3. 多态: 对于同一个行为,具有不同的表现形式或形态,提高了代码的可扩展性。例如,对于同一个接口,不同的实例调用同一个方法,会表现出不同的结果。
      image-20221109101249237

    注意: equals在Object中,就是 使用“==” 进行判断 的,而在String重写后,则是 先使用 “==”进行地址判断 ,然后使用instanceof进行 String类型判断 ,最后使用char[]进行挨个 字符判断


    1.2.2 ArrayList与LinkedList的区别
    • ArrayList的Array即是数组,所以ArrayList的底层是数组;
    • LinkedList的Linked即是链,所以LinkedList的底层是双向链表;
    • 由于底层是数组,所以ArrayList查询快(索引取值),增删慢(因为需要移动元素);
    • 由于底层是链表,LinkedList查询慢(遍历取值),增删快(改变节点引用)。

    注意: ArrayList默认长度为10,负载因子为0.75。


    1.2.3 集合中哪些是线程安全,哪些不是线程安全的
    • 单列集合中,Vector是线程安全的,而ArrayList与LinkedList都不是;
    • 双列集合中,HashTable是线程安全的,HashMap则不是。此外,ConcurrentHashMap内部则是使用了乐观锁(CAS)机制,减少了线程安全问题,但是并不能完全保证线程安全。

    数据库加乐观锁,是否能够完全做到线程安全?

    1.2.4 如果对于员工入职时间进行排序,应该使用什么集合

    答案: 使用TreeSet实现,根据其提供的Comparable或者Comparator来进行排序。

    Set集合的特点是无序且不可重复,但值得一提的是,无序指的是,存储顺序与插入顺序不同,但元素存储有指定规则的。例如HashSet是根据hash值进行的元素存储位置的计算,而TreeSet的可以指定,存储顺序规则,默认自然排序。


    1.2.5 HashMap的底层实现原理

      HashMap是双列集合,即键值对,其底层是数组+链表+红黑树。
      首先,当元素存入HashMap中,内部根据hash方法与key计算出元素在数组中的存储位置,数组默认长度为16。
      其次,由于不同的数据,也会计算出相同的哈希值,因此一个位置不能只放一个元素。所以最后决定,在位置中放入链表(Noded对象),当出现hash冲突时,则将其放入对应位置的链表中。值得一提的是,它并不会出作为尾节点出现,而是作为头节点出现,因为大佬们认为 后存多取
      最后,由于链表的查询十分缓慢,当数据较多时,不利于查询。因此,当某节点下元素个数达到8时,Node对象则会变成TreeNode,也就是链表变成红黑树。而进行删除之后,个数只有6时,则会由红黑树变成链表。
    在这里插入图片描述

    负载因子也是0.75,而默认长度则是16。负载因子过小容易造成空间浪费,负载因子过大,容易加入Hash冲突。


    1.2.6 线程生命周期
    1.2.6.1生命周期

    在这里插入图片描述

    1. 当new Thread执行时,则线程处于创建状态;
    2. 当执行start()时,线程则处于就绪状态,并没有执行;
    3. 当CPU给该线程分配时间片之后,则线程处于运行状态;
    4. 当线程执行,等待锁,或者调wait进行等待、或者调用sleep进行睡眠之后,线程则处于阻塞状态;
    5. 当获取到锁之后、或者被notify唤醒、或者睡眠时间已过之后,线程则转为就绪状态
    6. 当线程处于运行状态,而未在时间片中执行完成之后,则会转换成为就绪状态;而线程在时间片中执行完成,则会转成死亡状态

    1.2.6.2 创建线程的方式
    1. 继承Thread,实现run方法;
    public class MyThread extends Thread{
    	@Override
    	public void run(){
    		System.out.println("方式一");
    	}
    }
    //调用
    //MyThread th = new MyThread();
    //th.start(); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 实现Runnable接口
    public class MyThread implements Runnable{
    	@Override
    	public void run(){
    		System.out.println("方式二");
    	}
    }
    //调用
    //Thread th = new Thread(new MyThread());
    //th.start();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 实现Callable接口
    class MyThread<T> implements Callable {
    
        @Override
        public T call() throws Exception {
            return null;
        }
    }
    //调用
    //        MyThread th = new MyThread();
    //        FutureTask task = new FutureTask<>(th);
    //        Thread thread = new Thread(task);
    //        thread.start();
    //        Integer integer = task.get();
    //        System.out.println(integer);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    线程池创建线程的方法,归根结底应该还是三者之一。每一次线程时间片结束,而线程未完成时,下一次之所以又可以接着执行,是因为JVM中有一个程序计数器,它负责记录程序的执行位置,每一个线程都有一个私有的计数器。


    1.2.7 JVM内存模型

    在这里插入图片描述

    • 虚拟机栈: 每一次方法调用,都是一次入栈操作,会在虚拟机栈中创建一个栈帧。栈帧的内容主要是:局部变量表、操作栈(数据操作指令)、动态链接、方法返回地址等。由于栈的特点是先进后出,所以后调用的方法的栈帧,会在先调用的方法的栈帧的上面。而当方法调用完成,栈帧则会删除;加粗样式
    • 本地方法栈: 与虚拟机栈类似,只是存储的内容native修饰的方法的栈帧,也就是其他语言实现的方法,尤其是C与C++;
    • 堆: 程序运行过程中,所有对象的存储位置;
    • 元空间: JDK1.8以前,叫做方法区,只是那时放在堆中,并且只是一个逻辑概念。而1.8开始,元空间便不属于JVM内存中,独立于JVM。主要存放的内容是方法信息、静态变量、常量、常量池等。

    虚拟机栈与本地方法站都是私有的,而堆与元空间都是共享的。对于JVM内存的理解,具体可以参考这篇文章,14年的大佬写得很棒。


    1.2.8 常量池
    1. 整数
    Integer i1 = 120;
    Integer i2 = 120;
    System.out.println(i1==2);	//true
    
    Integer i3 = 200;
    Integer i4 = 200;
    System.out.println(i3==i4);		//false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意: 以上代码之所以会有两种不同的结果,是因为Java在内存中,存入了整数常量,而存储的范围则是[-128-127]。所以,i1与i2都是指向的同一个常量,而i3与i4却不是。

    1. 字符串
    String s1 = "张三";
    Stirng s2 = "张三";
    String s3 = new String("张三");
    String s4 = "张三123";
    String s5 = "123";
    String s6 = "张三"+"123";
    String s7 = s1 + s5;
    System.out.println(s1==s2);		//true
    System.out.println(s1==s3);		//false
    System.out.println(s4==s6);	//true
    System.out.println(s4==s7);	//false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意: 首先,当一个字符串没有使用new的方式创建时,会去常量池中看,是否存在该字符串。如果存在则直接引用,如果不如在,则存入常量池,在引用。其次,如果使用new创建,那么会在堆中开辟一片空间,变量指向的是堆地址。最后,字符串的 “+” 操作,如果双方都是 " " 的形式,那么Java字节会把两个字符串合并。如果双方有一个是对象,那么底层实现时,会创建一个 StringBuilder来append其他字符串

    这里的常量池介绍完全不足,需要找寻专业文章,查看常量池详情。


    1.2.9 类加载机制
    1.2.9.1 Java有四种类加载机制:
    1. 启动类加载器(Bootstrap ClassLoader):负责将存放在\lib目录中,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。(注:仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)
    2. 扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录中的,或被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
    3. 应用程序类加载器(Application ClassLoader):负责加载用户路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,一般情况下该类加载是程序中默认的类加载器。
    4. 用户自定义加载类
      389e14a52b9634d724970cb50c64868e

    1.2.9.2 双亲委派模型

      双亲委派机制指的是,当一个类加载器收到加载请求时,并不会马上区加载,而是会将请求委派给父级。依次递推,直至启动类加载器,如果父级无法执行,那么会逐级下放。主要解决了Java基础类统一加载的问题。
    128f84169d504b312d1f9c60e14dcaa0

    由于请求是逐级下方,所以BootStrap ClassLoader无法使用应用程序加载类,于是在特定情况下,便存在缺陷,例如厂家自定义组件,SPI还需要详细了解。


    1.2.10 GC机制
    1.2.10.1 简介

      GC即是Java的垃圾回收机制,也就是回收内存。而内存的几大分区中,虚拟机栈、本地方法站、PC寄存器(程序计数器)都是私有的,一旦线程结束便会被回收,所以,GC主要回收的是堆

    1.2.10.2 回收机制
    1. 引用计数法: Java对象中,会存储一个数用来记录引用数,当被一个对象引用那么+1,如果引用没有了则-1。例如Dog d = new Dog,则引用+1;如果d = null ,那么此时引用-1。当引用记录为0时,GC便会判定为垃圾,进行回收。如果两个对象互相引用(A有个属性是B,B有个属性是A),那么计数器则无法判断。
    2. 可达性分析法: Java使用一套算法,计算出程序中最活跃的对象。然后遍历该对象下的所有一级或多级引用,如果不在该引用关系中的则被判定为垃圾。
    1.2.10.3 回收策略
  • 相关阅读:
    SpringBoot-配置高级
    idea中打印日志不会乱码,但是部署到外部tomcat中乱码了。
    外网系统怎么访问协同oa?快解析内网端口映射公网
    如何借助CDC快速实现实时数据传输?
    Win11右键菜单反应慢有延迟解决方法分享
    全连接网络实现回归【房价预测的数据】
    用verilog编写FFT软核从0到1最强实现及解析(一)
    【JavaScript复习十七】作用域以及变量提升
    Spring框架(九):Spring注解开发Annotation
    开发趋势 Java Lambda 表达式 第二篇
  • 原文地址:https://blog.csdn.net/qq_44700366/article/details/128208928