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. 实现Runnable接口
public class MyThread implements Runnable{
	@Override
	public void run(){
		System.out.println("方式二");
	}
}
//调用
//Thread th = new Thread(new MyThread());
//th.start();
  1. 实现Callable接口
class MyThread<T> implements Callable {

    @Override
    public T call() throws Exception {
        return null;
    }
}
//调用
//        MyThread<Integer> th = new MyThread();
//        FutureTask<Integer> task = new FutureTask<>(th);
//        Thread thread = new Thread(task);
//        thread.start();
//        Integer integer = task.get();
//        System.out.println(integer);

线程池创建线程的方法,归根结底应该还是三者之一。每一次线程时间片结束,而线程未完成时,下一次之所以又可以接着执行,是因为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

注意: 以上代码之所以会有两种不同的结果,是因为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

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

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


1.2.9 类加载机制
1.2.9.1 Java有四种类加载机制:
  1. 启动类加载器(Bootstrap ClassLoader):负责将存放在<JAVA_HOME>\lib目录中,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。(注:仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)
  2. 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\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 回收策略
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值