2020-Java

1 Java虚拟机

JVM执行Java程序的过程

Java源代码文件(.java)会被Java编译器编译为字节码文件(.class),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。

1.1 JVM生命周期

  • 启动。启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点。
  • 运行main()作为该程序初始线程的起点,任何其他线程均由该线程启动。
  • 消亡。当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者**System.exit()**来退出。

1.2 JVM体系结构

  • 类装载器(ClassLoader)(用来装载.class文件)
  • 执行引擎(执行字节码,或者执行本地方法)
  • 运行时数据区(方法区、堆、java栈、PC寄存器、本地方法栈)P 3 内存模型

2.JVM介绍:

ref:https://blog.csdn.net/liyang_nash/article/details/78742623
在执行一个java文件的时候 用到的命令是

java -classpath .HelloWorld
HelloWorld

敲入的是java这个命令。这个命令说明, 首先启动的是一个叫做java的程序, 这个java程序在运行起来之后就是一个JVM进程实例。
上面的命令执行流程是这样的:
1)java命令启动虚拟机进程,虚拟机进程成功启动后,
2)读取参数“HelloWorld”,把他作为初始类加载到内存,对这个类进行初始化和动态链接
3)从这个类的main方法开始执行。
在Java虚拟机内部,有一个叫做类加载器的子系统,这个子系统用来在运行时根据需要加载类。一般来说,虚拟机加载类的时机,在第一次使用一个新的类的时候。
由虚拟机加载的类,被加载到Java虚拟机内存中之后,虚拟机会读取并执行它里面存在的字节码指令。虚拟机中执行字节码指令的部分叫做执行引擎。就像一个人,不是把饭吃下去就完事了,还要进行消化,执行引擎就相当于人的肠胃系统。在执行的过程中还会把各个class文件动态的连接起来。
总结:
1 )虚拟机并不神秘,在操作系统的角度看来,它只是一个普通进程
2 )这个叫做虚拟机的进程比较特殊,它能够加载我们编写的class文件。如果把JVM比作一个人,那么class文件就是我们吃的食物。
3) 加载class文件的是一个叫做类加载器的子系统。就好比我们的嘴巴,把食物吃到肚子里。
4 )虚拟机中的执行引擎用来执行class文件中的字节码指令。就好比我们的肠胃,对吃进去的食物进行消化。
5) 虚拟机在执行过程中,要分配内存创建对象。当这些对象过时无用了,必须要自动清理这些无用的对象。清理对象回收内存的任务由垃圾收集器负责。就好比人吃进去的食物,在消化之后,必须把废物排出体外,腾出空间可以在下次饿的时候吃饭并消化食物。

Java的内存模型

虚拟机内存模型的访问操作和物理计算机处理的基本一致:
在这里插入图片描述
通过多线程机制使得多个任务同时执行处理,所有的线程共享JVM内存区域main memory,而每个线程又单独的有自己的工作内存,当线程与内存区域进行交互时,数据从主存拷贝到工作内存,进而交由线程处理

在这里插入图片描述
在这里插入图片描述
JVM内存模型主要由类加载器子系统运行时数据区(内存空间)、执行引擎以及与本地方法接口等组成。
运行时数据区又由方法区、堆、Java栈、PC寄存器、本地方法栈组成。
各部分区域存储内容:在这里插入图片描述
图中可以看出,在内存空间中方法区是所有Java线程共享的,而Java栈、本地方法栈、PC寄存器则由每个线程私有

https://www.cnblogs.com/dingyingsi/p/3760447.html

程序计数器

  • 是当前线程所执行的字节码的行号指示器

  • 如果线程正在执行的是一个Java方法,那么计数器记录的是正在执行的虚拟机字节码指令的地址;

  • 如果线程正在执行的是一个Native方法,那么计数器的值则为空

字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java 虚拟机的多线程是通过线程轮流切换分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie 方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。

  1. 虚拟机栈:Java 虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame ①)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程
    局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)
StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存
  1. 本地方法栈:本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务
也会有以上错误。
  1. 方法区:是各个线程共享的内存区域,它用于存储已被虚拟机加载的**类信息、常量、静态变量、**即时编译器编译后的代码等数据。

  2. :Java 堆(Java Heap)是Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建存放对象实例,几乎所有的对象实例都在这里分配内存。Java 堆是垃圾收集器管理的主要区域。Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。

OutOfMemoryError:如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出该异常。

3.1 类加载器子系统

ref:https://www.jianshu.com/p/9ea809edebb6

责加载编译好的.class字节码文件,并装入虚拟机内存,使JVM可以实例化或以其它方式使用加载后的类。
JVM的类加载子系统支持在运行时的动态加载,动态加载的优点有很多,例如可以节省内存空间、灵活地从网络上加载类,动态加载的另一好处是可以通过命名空间的分隔来实现类的隔离,增强了整个系统的安全性
类加载分为装载、链接、初始化三步。

生命周期(7)
在这里插入图片描述
加载

通过类的全限定名来获取定义此类的二进制字节流。如从ZIP包读取、从网络中获取、通过运行时计算生成、由其他文件生成、从数据库中读取等等途径…

将该二进制字节流所代表的静态存储结构转化为方法区的运行时数据结构,该数据存储数据结构由虚拟机实现自行定义。
在内存中生成一个代表这个类的java.lang.Class对象,它将作为程序访问方法区中的这些类型数据的外部接口。

验证

是连接阶段的第一步,且工作量在JVM类加载子系统中占了相当大的一部分。
目的:为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
四个阶段

文件格式验证:
元数据验证:
字节码验证
符号引用验证

双亲委派机制
JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归。如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

Java的垃圾回收器(GC)

GC(基本原理)

将内存中不再被引用的对象进行回收,GC中用于回收的方法称为收集器。
垃圾:不再被引用的对象

Java中垃圾回收有什么目的?什么时候进行垃圾回收?

垃圾回收的目的是识别并且丢弃应用不再使用的对象来释放和重用资源。

23.如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?不会,在下一个垃圾回收周期中,这个对象将是可被回收的。

GC算法

确定对象回收的方法

(1).引用计数算法:
对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不再被使用的,垃圾收集器将回收该对象使用的内存。
引用计数算法实现简单,效率很高,微软的COM技术、ActionScript、Python等都使用了引用计数算法进行内存管理,但是引用计数算法对于对象之间相互循环引用问题难以解决,因此java并没有使用引用计数算法。
(2).根搜索算法:
通过一系列的名为“GC Root”的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存。

ref:https://www.jianshu.com/p/a62697f00b85

分代收集算法

根据对象存活周期的不同,将Java堆划分为新生代老年代,并根据各个年代的特点采用最适当的收集算法。

  • 新生代:大批对象死去,只有少量存活。使用『复制算法』,只需复制少量存活对象即可。
  • 老年代对象存活率高。使用『标记—清理算法』或者『标记—整理算法』,只需标记较少的回收对象即可。

接下来依次介绍以上提及的另外三种算法

1.复制算法
\

  • 把可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用尽后,把还存活着的对象『复制』到另外一块上面,再将这一块内存空间一次清理掉。
  • 优点:每次都是对整个半区进行内存回收,无需考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
  • 缺点:每次可使用的内存缩小为原来的一半,内存使用率低。
    在这里插入图片描述

有研究表明新生代中的对象98%是朝生夕死的,因此没必要按照1:1来划分内存空间,而是分为一块较大的Eden空间和两块较小的Survivor空间,在HotSpot虚拟机中默认比例为8:1:1。每次使用Eden和一块Survivor,回收时将这两块中存活着的对象一次性地复制到另外一块Survivor上,再做清理。可见只有10%的内存会被“浪费”,倘若Survivor空间不足还需要依赖其他内存(老年代)进行分配担保。

2.标记-清除算法

  • 首先『标记』出所有需要回收的对象,然后统一『清除』所有被标记的对象。
    是最基础的收集算法。
    缺点:『标记』和『清除』过程的效率不高;空间碎片太多,『标记』『清除』之后会产生大量不连续的内存碎片,可能会导致后续需要分配较大对象时,因无法找到足够的连续内存而提前触发另一次GC,影响系统性能。
    在这里插入图片描述

3.标记-整理算法

  • 首先『标记』出所有需要回收的对象,然后进行『整理』,使得存活的对象都向一端移动,最后直接清理掉端边界以外的内存。
  • 优点:即没有浪费50%的空间,又不存在空间碎片问题,性价比较高。
    一般情况下,老年代会选择标记-整理算法。

引用

下面四种引用强度一次逐渐减弱

  • 强引用:类似于 Object obj = new Object(); 创建的,只要强引用在就不回收。
  • 软引用内存空间足够,垃圾回收器就不会回收它;内存空间不足了,就会回收这些对象的内存。
  • 弱引用:用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
  • 虚引用:如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

4. static”关键字

ref:https://www.cnblogs.com/dolphin0520/p/3799052.html
在没有创建对象的情况下来进行调用static(方法/变量)
即:被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。
static可以用来修饰类的成员方法类的成员变量,另外可以编写static代码块来优化程序性能。
1)static 方法
静态方法中不能访问非静态成员方法非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的
2)static 变量
静态变量和非静态变量的区别是:
静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。
非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
3)static代码块
static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
与C++ 不同,Java中的static关键字不会影响到变量或者方法的作用域
Java中能够影响到访问权限的只有privatepublicprotected(包括包访问权限)这几个关键字
4.1 Java中是否可以覆盖(override)一个private或者是static的方法?
都不可以。
static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。
重写的前提是必须要继承,private修饰不支持继承,因此private的方法不能重写。

5.Overload和Override的区别

两种都是Java多态性的不同表现。
overload:是一个类中多态性的一种表现。在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型。。Overloaded的方法是可以改变返回值的类型
override:是父类与子类之间多态性的一种表现。在子类中定义某方法与其父类有相同的名称和参数。但子类的访问权限不要低于父类的访问权限。

6.java支持多继承吗?

不支持,Java不支持多继承。每个类都只能继承一个类,但是可以实现多个接口。


6.1。接口和抽象类的区别是什么?

抽象类不考虑方法的实现,只去定义一些方法。在定义类时前面加上abstract关键字,方法名前面也要加abstract。

public abstract class parent{

    public abstract string getName();
}

接口只定义接口名称和方法,不定义方法内容,

public interface WaterPipe{
    public int InWater();
    public int OutWater();
}

区别
在这里插入图片描述
5. 抽象类可以包含非final的变量。接口中声明的变量默认都是final的。

7.Java支持的数据类型有哪些?什么是自动拆装箱?

8种**基本数据类型是:
• byte
• short
• int
• long
• float
• double
• boolean
• char
自动装箱是
Java编译器基本数据类型和对应的对象包装类型**之间做的一个转化。比如:把int转化成Integer,double转化成double,等等。反之就是自动拆箱。

8.值传递和引用传递

  • 对象被值传递,意味着传递了对象的一个副本。因此,就算是改变了对象副本,也不会影响源对象的值。
  • 对象被引用传递,意味着传递的是对象的引用。因此,外部对引用对象所做的改变会反映到所有的对象上。

9.创建线程有几种不同的方式?你喜欢哪一种?为什么?

https://www.cnblogs.com/3s540/p/7172146.html

三种

1)继承Thread类创建线程

2)实现Runnable接口创建线程(推荐使用)

3)使用Callable和Future创建线程

-------------------------------------继承Thread类创建线程-------------------------------------

通过继承Thread类来创建并启动多线程的一般步骤如下

1】定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。

2】创建Thread子类的实例,也就是创建了线程对象

3】启动线程,即调用线程的start()方法

代码实例

public class MyThread extends Thread{//继承Thread类

  public void run(){

  //重写run方法
  }
}

public class Main {

  public static void main(String[] args){

    new MyThread().start();//创建并启动线程

  }
}

-----------------------------------实现Runnable接口创建线程---------------------------------

通过实现Runnable接口创建并启动线程一般步骤如下:

1】定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体

2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象

3】第三部依然是通过调用线程对象的start()方法来启动线程

public class MyThread2 implements Runnable {//实现Runnable接口
  public void run(){
  //重写run方法
  }
}
public class Main {
  public static void main(String[] args){
    //创建并启动线程
    MyThread2 myThread=new MyThread2();
    Thread thread=new Thread(myThread);
    thread().start();
    //或者    new Thread(new MyThread2()).start();
  }
}

------------------------------使用Callable和Future创建线程----------------------------
(还没看)

------------------------------------三种创建线程方法对比----------------------------------

实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。

10.线程和进程

10.1 进程和线程区别
根本区别
一个程序至少有一个进程,一个进程至少有一个线程;线程影响进程,进程调用线程;
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

主要区别:
它们是不同的操作系统资源管理方式。
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。
线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉
多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
总结:
1.进程资源分配最小单位,线程程序执行的最小单位;
2.进程有自己独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。线程没有独立的地址空间,有独立的堆栈和局部变量,,一个线程死掉就等于整个进程死掉。
3.CPU切换、创建线程、占用资源线程都比进程少
4. 进程对资源保护要求高,开销大,效率相对较低,线程资源保护要求不高,但开销小,效率高,可频繁切换;

10.1 线程通信
ref:java实现线程通信的四种方式
(1)synchronized同步
当两个线程同时持有一个类的对象时,即使两个线程调用不同的方法(方法名前用synchronized修饰),但由于他们是同步执行的。可实现线程通信。本质上是“共享内存”式通信,多个线程访问同一共享变量,谁拿到锁,谁执行。

(2)while轮询
是多线程同时执行,会牺牲部分CPU性能。
线程A不断地改变条件,线程ThreadB不停地通过while语句检测这个条件(list.size()==5)是否成立 ,从而实现了线程间的通信。会浪费CPU资源。因为JVM调度器将CPU交给线程B执行时,它没做啥“有用”的工作,只是在不断地测试 某个条件是否成立。

(3)wait/notify机制
线程A要等待某个条件满足时(list.size()==5),才执行操作。
A,B之间如何通信的呢?也就是说,线程A如何知道 list.size() 已经为5了呢?

这里用到了Object类的 wait() 和 notify() 方法。

当条件未满足时(list.size() !=5),线程A调用wait() 放弃CPU,并进入阻塞状态。—不像②while轮询那样占用CPU

条件满足时,线程B调用 notify()通知 线程A,所谓通知线程A,就是唤醒线程A,并让它进入可运行状态。

这种方式的一个好处就是CPU的利用率提高了。

(4)管道通信
来实现两个线程之间的二进制数据的传播

11.死锁(deadlock)

11.1 死锁产生条件

  • 互斥条件:进程对所分配的资源进行排他式使用
  • 请求和保持条件:进程已经保持一个资源,但是又提出新的资源请求,而这个新资源又被其他进程占有,发生进程阻塞,但又对自己的资源保持。
  • 不剥夺条件:进程获得资源后,不得剥夺
  • 环路等待条件

11.2 避免死锁方法

  • 一个线程不可以获得多个锁
  • 避免一个线程在所内获得多个资源
  • 用定时锁代替内部锁机制
  • 数据库锁,加锁和解锁要在一个数据库中

11.3 如何确保N个线程可以访问N个资源同时又不导致死锁?
多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。

12.Java集合类框架的基本接口有哪些?

https://www.cnblogs.com/zedosu/p/6599652.html

12.1 JAVA两大接口:

  • Collection:元素集合。List(有序元素集合)和Set(无序元素集合)接口继承了Collection接口
    ListArrayListLinkedList 实现了List接口
    setHashSet实现了Set接口
  • Map:键值对集合
    HashMapHashTable实现了Map接口
    java.util.Collection [I]

java.util.Collection [I]

|—java.util.List [I]
    |—java.util.ArrayList [C]
    |—java.util.LinkedList [C]
    |—java.util.Vector [C]
        |—java.util.Stack [C]

|—java.util.Set [I]
    |—java.util.HashSet [C]
    |—java.util.SortedSet [I]
        |—java.util.TreeSet [C]

java.util.Map [I]

|—java.util.SortedMap [I]
    |—java.util.TreeMap [C]
|—java.util.Hashtable [C]
|—java.util.HashMap [C]
    |—java.util.LinkedHashMap [C]
|—java.util.WeakHashMap [C]

在这里插入图片描述
12.2 Arraylist和Linkedlist的区别
Arraylist

  • 底层基于动态数组实现。
  • 随机访问数组元素的效率高。
  • 末尾增删元素效率高,增删数组中的元素因为要移动数据效率低。
  • 数组元素的容量是固定的,当元素个数超过其容量时,需要扩容(以当前容量的1.5倍扩容)。扩容的过程中,会进行大量的数组复制操作。

Linkedlist

  • 使用的是循环双向链表数据结构

  • 随机访问效率低:移动指针。

  • 增删元素效率高,只需修改指针。

  • 一个表项包括3个部分:元素内容、前驱表项、后继表项。无论LikedList是否为空,链表内部都有一个header表项,它既表示链表的开始,也表示链表的结尾。
    在这里插入图片描述
    12.3 Vector

  • 可实现自动增长的对象数组, 以实现类似动态数组的功能。

  • 创建了一个向量类的对象后,可以往其中随意插入不同类的对象。

  • vector是线程(Thread)同步(Synchronized)的,所以它也是线程安全的

12.4 数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?

Array可以包含基本类型对象类型ArrayList只能包含对象类型
• Array大小固定的,ArrayList的大小是动态变化的。
• ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
• 对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

12.5 HashMap的工作原理是什么?

https://www.cnblogs.com/chengxiao/p/6059914.html

HashMap对象是根据其Key的hashCode来获取对应的Value。
哈希冲突的解决方案有多种: 开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式。
 HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对
在这里插入图片描述
HashMap由数组+链表组成的,数组是HashMap的主体链表则是主要为了解决哈希冲突而存在的,
如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;
如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;
对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。
所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
存储位置的获得:
在这里插入图片描述
HashCode(): 当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了
HashMap 有两个参数

  • 初试容量(16):哈希表创建时的容量
  • 加载因子(0.75):创建时可以达到满的程度(当达到满的程度后需要扩容)
    扩容:创建一个长度为原来容量2倍的容量,将原理的数据一次插入到新数组中。用key的hash值和(2^n - 1)做与运算
    Problem:HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。

12.6 HashMap和Hashtable有什么区别?
HashMap和Hashtable都实现了Map接口。

  • HashMap是非synchronized的,所以线程不安全;单线程性能比hashtable好。- Hashtable是synchronized,线程安全,但是单线程比hashmap慢
  • HashMap 可以接受null(key和value 都可以为null),HashTable不可以接受null
  • HashMap不能保证随时间推移map元素次序不变
  • 遍历方式不同HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtableenumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。
  • 初始容量不同HashMap的初始长度为16,之后每次扩充变为原来的两倍。Hashtable的初始长度是11,之后每次扩充容量变为之前的2n+1(n为上一次的长度)

12.7 ConcurrentHashMap 工作原理

https://www.cnblogs.com/study-everyday/p/6430462.html

ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成,如下图所示:
在这里插入图片描述
Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到的锁分离技术,而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样

(操作可以再看看)

红黑树

12.8 HashSet 和TreeSet区别

https://www.cnblogs.com/qdhxhz/p/8551957.html

  • Set 不允许包含相同元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。

  • Set 判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不会接受这两个对象。
    在这里插入图片描述
    HashSet:

    1. 不能保证元素的排列顺序,顺序有可能发生变化

    2. 不是同步

    3. 集合元素可以是null,但只能放入一个null

    当向HashSet结合中存入一个元素时,HashSet会调用该对象的**hashCode()**方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。
    简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相等

TreeSet

  • 可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序定制排序,其中自然排序为默认的排序方式。向 TreeSet中加入的应该是同一个类的对象。
  • TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
  • 自然排序
    自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
  • 定制排序
    定制排序,实现 int **compare(To1,To2)**方法

12.9 Collection 和 Collections的区别

  • java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口List与Set
  • Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序搜索等各种操作。

13.迭代器(Iterator)

迭代器:就是提供一种方法对一个容器对象(LinkedList, ArrayList…)中的各个元素进行访问,而又不暴露该对象容器的内部细节。
13.1 .Iterator和ListIterator的区别是什么?

13.String、StringBuffer、StringBuilder

在这里插入图片描述
StringBuffer的很多方法用synchronized修饰,意味着多个线程只能互斥地调用这个方法。

Object的equal()和==的区别? 四大组件

equals():是Object的公有方法,具体含义取决于如何重写,比如String的equals()比较的是两个字符串的内容是否相同
""== :对于基本数据类型来说,比较的是两个变量值是够是否相等,对于引用类型来说,比较的是两个对象的内存地址是否相同

Java内存模型是围绕着并发编程中原子性、可见性、有序性这三个特征来建立:
原子性(Atomicity):一个操作不能被打断,要么全部执行完毕,要么不执行.基本类型数据的访问大都是原子操作, long 和double类型的变量是64位,但是在32位JVM中,32位的JVM会将64位数据的读写操作分为2次32位的读写操作来进行,这 就导致了long、double类型的变量在32位虚拟机中是非原子操作,数据有可能会被破坏,也就意味着多个线程在并发访问的时候是线程非安全的
可见性:一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量这种修改(变化)。。
有序性 有序性是指对于单线程的执行代码,我们总是认为代码的执行是按顺序依次执行的

锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)
为了保证多线程读写数据时保证数据的一致性,可以采用两种方式:
  同步:如用synchronized关键字,或者使用锁对象
使用volatile关键字:用一句话概括volatile,它能够使变量在值发生改变时能尽快地让其他线程知道
synchronized关键字
是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。synchronized既可以加在一段代码上,也可以加在方法上。
Volatile关键字
将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制
volatile与synchronized区别
1)volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
2)volatile仅能使用在变量级别,synchronized则可以使用在变量,方法、代码段.
3)volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性.
4)volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
6、使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。

22.内存泄漏和内存溢出的区别和联系
ref:https://www.jianshu.com/p/2a2a5ec2af00

内存泄漏memory leak
:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。

内存溢出 out of memory指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。

内存溢出分类

1.栈内存溢出

程序所要求的栈深度过大导致,可以写一个死递归程序触发。

2.堆内存溢出

(1)如果是内存溢出,则通过 调大 -Xms,-Xmx参数。
(2)如果是内存泄露,则看对象如何被 GC Root 引用。

3.持久带内存溢出(OutOfMemoryError: PermGen space)

持久带中包含方法区,方法区包含常量池

因此持久带溢出有可能是(1) 运行时常量池溢出,也有可能是(2)方法区中保存的Class对象没有被及时回收掉或者Class信息占用的内存超过了我们配置。

用String.intern()触发常量池溢出。
Class对象未被释放,Class对象占用信息过多,有过多的Class对象。可以导致持久带内存溢出。

4.无法创建本地线程

系统内存的总容量不变,堆内存、非堆内存设置过大,会导致能给线程分配的内存不足。

Caused by: java.lang.OutOfMemoryError:unable to create new native thread

3、二者的关系
内存泄漏的堆积最终会导致内存溢出;内存溢出就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误。
内存溢出的原因及解决方法:

内存溢出原因:
1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
4.使用的第三方软件中的BUG; 5.启动参数内存值设定的过小

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值