靠着这Java面试210题,成功拿下了10多家国内知名大厂Offer,10万字精华全部分享给大家

article/2025/9/30 0:46:00

写在前面

我坚信,机会永远属于有准备的人,我们与其羡慕他人的成功,不如从此刻起,积累足够多的知识和面试经验,为将来进入更好的公司做好充分的准备!

如何让面试官在短短的几十分钟内认可你的能力? 如何在最短的时间内收获Java技术栈最核心的知识点

这份内容可以算是呕心沥血总结出来的,如果对你有帮助,请不要吝啬你的点赞、评论、收藏!也可以分享给你的同事同学,我们一起进步,祝大家前程有日月!!!

如果大家觉得看起来太麻烦可以私信我要文档(不一定能及时回复)

请添加图片描述

正文

Java基础(33)

面向对象

什么是面向对象?对比面向过程,是两种不同的处理问题的角度,面向过程更注重事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者(对象)、及各自需要什么,比如洗衣机洗衣服:

  • 面向过程:会将任务拆解成一系列的步骤:打开洗衣机----->放衣服----->放洗衣粉----->清洗----->烘干
  • 面向对象:会拆出人和洗衣机两个对象:
    1. 人:打开洗衣机 放衣服 放洗衣粉
    2. 洗衣机:清洗 烘干

从以上例子能看出,面向过程比较直接高效,而面向对象更易于复用、扩展和维护。

封装:封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项,内部细节对外部调用透明,外部调用无需修改或者关心内部实现
继承:继承基类的方法,并做出自己的改变和/或扩展,子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的
多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同

JDK、JRE、JVM之间的区别

JDK:Java Develpment Kit java 开发工具
JRE:Java Runtime Environment java运行时环境
JVM:java Virtual Machine java 虚拟机
在这里插入图片描述

==和equals方法之前的区别

  • ==:对比的是栈中的值,基本数据类型是变量值,引用类型是堆中内存对象的地址
  • equals:object中默认也是采用==比较,通常会重写

Object

public boolean equals(Object obj) {return (this == obj);
}

String

public boolean equals(Object anObject) {if (this == anObject) {return true;}if (anObject instanceof String) {String anotherString = (String)anObject;int n = value.length;if (n == anotherString.value.length) {char v1[] = value;char v2[] = anotherString.value;int i = 0;while (n-- != 0) {if (v1[i] != v2[i])return false;i++;}return true;}}return false;
}

上述代码可以看出,String类中被复写的equals()方法其实是比较两个字符串的内容。

public class StringDemo {public static void main(String args[]) {String str1 = "Hello";String str2 = new String("Hello");String str3 = str2; // 引用传递System.out.println(str1 == str2); // falseSystem.out.println(str1 == str3); // falseSystem.out.println(str2 == str3); // trueSystem.out.println(str1.equals(str2)); // trueSystem.out.println(str1.equals(str3)); // trueSystem.out.println(str2.equals(str3)); // true}}

hashCode()与equals()之间的关系

HashCode介绍:hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,Java中的任何类都包含有hashCode() 函数。

散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

以“HashSet如何检查重复”为例子来说明为什么要有hashCode

对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,看该位置是否有值,如果没有、HashSet会假设对象没有重复出现。但是如果发现有值,这时会调用equals()方法来检查两个对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样就大大减少了equals的次数,相应就大大提高了执行速度。

  • 如果两个对象相等,则hashcode一定也是相同的
  • 两个对象相等,对两个对象分别调用equals方法都返回true
  • 两个对象有相同的hashcode值,它们也不一定是相等的
  • 因此,equals方法被覆盖过,则hashCode方法也必须被覆盖
  • hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

final关键字的作用是什么?

修饰类:表示类不可被继承
修饰方法:表示方法不可被子类覆盖,但是可以重载
修饰变量:表示变量一旦被赋值就不可以更改它的值。
修饰成员变量:

  • 如果final修饰的是类变量,只能在静态初始化块中指定初始值或者声明该类变量时指定初始值。
  • 如果final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值。

修饰局部变量:
系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,即可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码中对final变量赋初值(仅一次)

public class FinalVar {final static int a = 0;//再声明的时候就需要赋值 或者静态代码块赋值/**static{a = 0;}*/final int b = 0;//再声明的时候就需要赋值 或者代码块中赋值   或者构造器赋值/*{b = 0;}*/public static void main(String[] args) {final int localA;   //局部变量只声明没有初始化,不会报错,与final无关。localA = 0;//在使用之前一定要赋值//localA = 1;  但是不允许第二次赋值}
}

修饰基本类型数据和引用类型数据:

  • 如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;
  • 如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。但是引用的值是可变的
public class FinalReferenceTest{public static void main(){final int[] iArr={1,2,3,4};iArr[2]=-3;//合法 iArr=null;//非法,对iArr不能重新赋值final Person p = new Person(25);p.setAge(24);//合法p=null;//非法 }   
}

为什么局部内部类和匿名内部类只能访问局部final变量?

编译之后会生成两个class文件,Test.class Test1.class

public class Test {public static void main(String[] args)  {     }   //局部final变量a,bpublic void test(final int b) {//jdk8在这里做了优化, 不用写,语法糖,但实际上也是有的,也不能修改final int a = 10;//匿名内部类new Thread(){public void run() {System.out.println(a);System.out.println(b);};}.start();}
}class OutClass {private int age = 12;public void outPrint(final int x) {class InClass {public void InPrint() {System.out.println(x);System.out.println(age);}}new InClass().InPrint();}
}

首先需要知道的一点是: 内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。

这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问的是局部变量的"copy"。这样就好像延长了局部变量的生命周期

将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决问题呢?

就将局部变量设置为final,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协。使得局部变量与内部类内建立的拷贝保持一致。

String、StringBuffer、StringBuilder的区别

  1. String是不可变的,如果尝试去修改,会新生成一个字符串对象,StringBuffer和StringBuilder是可变的
  2. StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效率会更高

重载和重写的区别

  1. 重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
  2. 重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。
public int add(int a,String b)
public String add(int a,String b)
//编译报错

接口和抽象类的区别

  • 抽象类可以存在普通成员函数,而接口中只能存在public abstract 方法。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。
  • 抽象类只能继承一个,接口可以实现多个。

接口的设计目的,是对类的行为进行约束(更准确的说是一种“有”约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。

而抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到A-B时,无法执行)。

抽象类是对类本质的抽象,表达的是 is a 的关系,比如:BMW is a Car。抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现。

而接口是对行为的抽象,表达的是 like a 的关系。比如:Bird like a Aircraft(像飞行器一样可以飞),但其本质上 is a Bird。接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关心。

使用场景:当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。

抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度

List和Set的区别

  • List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素
  • Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用Iterator接口取得所有元素,在逐一遍历各个元素

ArrayList和LinkedList区别

  1. 首先,他们的底层数据结构不同,ArrayList底层是基于数组实现的,LinkedList底层是基于链表实现的
  2. 由于底层数据结构不同,他们所适用的场景也不同,ArrayList更适合随机查找,LinkedList更适合删除和添加,查询、添加、删除的时间复杂度不同
  3. 另外ArrayList和LinkedList都实现了List接口,但是LinkedList还额外实现了Deque接口,所以LinkedList还可以当做队列来使用

HashMap和HashTable有什么区别?其底层实现是什么?

区别 :

  1. HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全;
  2. HashMap允许key和value为null,而HashTable不允许

底层实现:数组+链表实现,jdk8开始链表高度到8、数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在

  1. 计算key的hash值,二次hash然后对数组长度取模,对应到数组下标,
  2. 如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组,
  3. 如果产生hash冲突,先进行equal比较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表
  4. key为null,存在下标0的位置

谈谈ConcurrentHashMap的扩容机制

1.7版本

  1. 1.7版本的ConcurrentHashMap是基于Segment分段实现的
  2. 每个Segment相对于一个小型的HashMap
  3. 每个Segment内部会进行扩容,和HashMap的扩容逻辑类似
  4. 先生成新的数组,然后转移元素到新数组中
  5. 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值

1.8版本

  1. 1.8版本的ConcurrentHashMap不再基于Segment实现
  2. 当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容那么该线程一起进行扩容
  3. 如果某个线程put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进行扩容
  4. ConcurrentHashMap是支持多个线程同时扩容的
  5. 扩容之前也先生成一个新的数组
  6. 在转移元素时,先将原数组分组,将每组分给不同的线程来进行元素的转移,每个线程负责一组或多组的元素转移工作

Jdk1.7到Jdk1.8 HashMap 发生了什么变化(底层)?

  1. 1.7中底层是数组+链表,1.8中底层是数组+链表+红黑树,加红黑树的目的是提高HashMap插入和查询整体效率
  2. 1.7中链表插入使用的是头插法,1.8中链表插入使用的是尾插法,因为1.8中插入key和value时需要判断链表元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使用尾插法
  3. 1.7中哈希算法比较复杂,存在各种右移与异或运算,1.8中进行了简化,因为复杂的哈希算法的目的就是提高散列性,来提供HashMap的整体效率,而1.8中新增了红黑树,所以可以适当的简化哈希算法,节省CPU资源

说一下HashMap的Put方法

先说HashMap的Put方法的大体流程:

  1. 根据Key通过哈希算法与与运算得出数组下标
  2. 如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是Node对象)并放入该位置
  3. 如果数组下标位置元素不为空,则要分情况讨论
    1. 如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进行扩容,如果不用扩容就生成Entry对象,并使用头插法添加到当前位置的链表中
    2. 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红黑树Node,还是链表Node
      1. 如果是红黑树Node,则将key和value封装为一个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value
      2. 如果此位置上的Node对象是链表节点,则将key和value封装为一个链表Node并通过尾插法插入到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插入到链表中,插入到链表后,会看当前链表的节点个数,如果大于等于8,那么则会将该链表转成红黑树
      3. 将key和value封装为Node插入到链表或红黑树中后,再判断是否需要进行扩容,如果需要就扩容,如果不需要就结束PUT方法

泛型中extends和super的区别

  1. <? extends T>表示包括T在内的任何T的子类
  2. <? super T>表示包括T在内的任何T的父类

深拷贝和浅拷贝

深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本数据类型,一种是实例对象的引用。

  1. 浅拷贝是指,只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象,也就是浅拷贝出来的对象,内部的类属性指向的是同一个对象
  2. 深拷贝是指,既会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,深拷贝出来的对象,内部的属性指向的不是同一个对象

HashMap的扩容机制原理

1.7版本

  1. 先生成新数组
  2. 遍历老数组中的每个位置上的链表上的每个元素
  3. 取每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标
  4. 将元素添加到新数组中去
  5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

1.8版本

  1. 先生成新数组
  2. 遍历老数组中的每个位置上的链表或红黑树
  3. 如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去
  4. 如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置
    1. 统计每个下标位置的元素个数
    2. 如果该位置下的元素个数超过了8,则生成一个新的红黑树,并将根节点的添加到新数组的对应位置
    3. 如果该位置下的元素个数没有超过8,那么则生成一个链表,并将链表的头节点添加到新数组的对应位置
  5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

CopyOnWriteArrayList的底层原理是怎样的

  1. 首先CopyOnWriteArrayList内部也是用过数组来实现的,在向CopyOnWriteArrayList添加元素时,会复制一个新的数组,写操作在新数组上进行,读操作在原数组上进行
  2. 并且,写操作会加锁,防止出现并发写入丢失数据的问题
  3. 写操作结束之后会把原数组指向新数组
  4. CopyOnWriteArrayList允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CopyOnWriteArrayList会比较占内存,同时可能读到的数据不是实时最新的数据,所以不适合实时性要求很高的场景

什么是字节码?采用字节码的好处是什么?

**Java中的编译器和解释器:**Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做 字节码(即扩展名为 .class的文件),它不面向任何特定的处理器,只面向虚拟机。
每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。

Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。

**采用字节码的好处:**Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。

Java中的异常体系是怎样的

  • Java中的所有异常都来自顶级父类Throwable。
  • Throwable下有两个子类Exception和Error。
  • Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。
  • Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。
  • RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在程序编译过程中,会导致程序编译不通过。

Java中有哪些类加载器

JDK自带有三个类加载器:bootstrap ClassLoader、ExtClassLoader、AppClassLoader。

  • BootStrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和class文件。
  • ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/lib/ext文件夹下的jar包和class类。
  • AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件。

说说类加载器双亲委派模型

JVM中存在三个默认的类加载器:

  1. BootstrapClassLoader
  2. ExtClassLoader
  3. AppClassLoader

AppClassLoader的父加载器是ExtClassLoader,ExtClassLoader的父加载器是BootstrapClassLoader。

JVM在加载一个类时,会调用AppClassLoader的loadClass方法来加载这个类,不过在这个方法中,会先使用ExtClassLoader的loadClass方法来加载类,同样ExtClassLoader的loadClass方法中会先使用BootstrapClassLoader来加载类,如果BootstrapClassLoader加载到了就直接成功,如果BootstrapClassLoader没有加载到,那么ExtClassLoader就会自己尝试加载该类,如果没有加载到,那么则会由AppClassLoader来加载这个类。

所以,双亲委派指得是,JVM在加载类时,会委派给Ext和Bootstrap进行加载,如果没加载到才由自己进行加载。

GC如何判断对象可以被回收

  • 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收,
  • 可达性分析法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。

引用计数法,可能会出现A 引用了 B,B 又引用了 A,这时候就算他们都不再使用了,但因为相互引用 计数器=1 永远无法被回收。

GC Roots的对象有:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象

可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。

当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”

每个对象只能触发一次finalize()方法

由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它。

JVM中哪些是线程共享区

堆区和方法区是所有线程共享的,栈、本地方法栈、程序计数器是每个线程独有的

你们项目如何排查JVM问题

对于还在正常运行的系统:

  1. 可以使用jmap来查看JVM中各个区域的使用情况
  2. 可以通过jstack来查看线程的运行情况,比如哪些线程阻塞、是否出现了死锁
  3. 可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc比较频繁,那么就得进行调优了
  4. 通过各个命令的结果,或者jvisualvm等工具来进行分析
  5. 首先,初步猜测频繁发送fullgc的原因,如果频繁发生fullgc但是又一直没有出现内存溢出,那么表示fullgc实际上是回收了很多对象了,所以这些对象最好能在younggc过程中就直接回收掉,避免这些对象进入到老年代,对于这种情况,就要考虑这些存活时间不长的对象是不是比较大,导致年轻代放不下,直接进入到了老年代,尝试加大年轻代的大小,如果改完之后,fullgc减少,则证明修改有效
  6. 同时,还可以找到占用CPU最多的线程,定位到具体的方法,优化这个方法的执行,看是否能避免某些对象的创建,从而节省内存

对于已经发生了OOM的系统:

  1. 一般生产系统中都会设置当系统发生了OOM时,生成当时的dump文件(-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base)
  2. 我们可以利用jsisualvm等工具来分析dump文件
  3. 根据dump文件找到异常的实例对象,和异常的线程(占用CPU高),定位到具体的代码
  4. 然后再进行详细的分析和调试

总之,调优不是一蹴而就的,需要分析、推理、实践、总结、再分析,最终定位到具体的问题

一个对象从加载到JVM,再到被GC清除,都经历了什么过程?

  1. 用户创建一个对象,JVM首先需要到方法区去找对象的类型信息。然后再创建对象。
  2. JVM要实例化一个对象,首先要在堆当中先创建一个对象。-> 半初始化状态
  3. 对象首先会分配在堆内存中新生代的Eden。然后经过一次Minor GC,对象如果存活,就会进入S区。在后续的每次GC中,如果对象一直存活,就会在S区来回拷贝,每移动一次,年龄加1。-> 多大年龄才会移入老年代? 年龄最大15, 超过一定年龄后,对象转入老年代。
  4. 当方法执行结束后,栈中的指针会先移除掉。
  5. 堆中的对象,经过Full GC,就会被标记为垃圾,然后被GC线程清理掉。

怎么确定一个对象到底是不是垃圾?

  1. 引用计数: 这种方式是给堆内存当中的每个对象记录一个引用个数。引用个数为0的就认为是垃圾。这是早期JDK中使用的方式。引用计数无法解决循环引用的问题。
  2. 根可达算法: 这种方式是在内存中,从引用根对象向下一直找引用,找不到的对象就是垃圾。

JVM有哪些垃圾回收算法?

  1. MarkSweep 标记清除算法
    :这个算法分为两个阶段,标记阶段:把垃圾内存标记出来,清除阶段:直接将垃圾内存回收。
    这种算法是比较简单的,但是有个很严重的问题,就是会产生大量的内存碎片。
  2. Copying 拷贝算法:
    为了解决标记清除算法的内存碎片问题,就产生了拷贝算法。拷贝算法将内存分为大小相等的两半,每次只使用其中一半。垃圾回收时,将当前这一块的存活对象全部拷贝到另一半,然后当前这一半内存就可以直接清除。
    这种算法没有内存碎片,但是他的问题就在于浪费空间。而且,他的效率跟存货对象的个数有关。
  3. MarkCompack 标记压缩算法:为了解决拷贝算法的缺陷,就提出了标记压缩算法。这种算法在标记阶段跟标记清除算法是一样的,但是在完成标记之后,不是直接清理垃圾内存,而是将存活对象往一端移动,然后将端边界以外的所有内存直接清除。

这三种算法各有利弊,各自有各自的适合场景。

什么是STW?

STW: Stop-The-World,是在垃圾回收算法执行过程当中,需要将JVM内存冻结的一种状态。在STW状态下,JAVA的所有线程都是停止执行的-GC线程除外,native方法可以执行,但是,不能与JVM交互。GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的重点。

JVM有哪些垃圾回收器?

  • 新生代收集器:
    • Serial
    • ParNew
    • Parallel Scavenge
  • 老年代收集器:
    • CMS
    • Serial Old
    • Parallel Old
  • 整堆收集器:
    • G1

垃圾回收分为哪些阶段

GC分为四个阶段:

  • 第一:初始标记 标记出GCRoot直接引用的对象。STW
  • 第二:标记Region,通过RSet标记出上一个阶段标记的Region引用到的Old区Region。
  • 第三:并发标记阶段:跟CMS的步骤是差不多的。只是遍历的范围不再是整个Old区,而只需要遍历第二步标记出来的Region。
  • 第四:重新标记: 跟CMS中的重新标记过程是差不多的。
  • 第五:垃圾清理:与CMS不同的是,G1可以采用拷贝算法,直接将整个Region中的对象拷贝到另一个Region。而这个阶段,G1只选择垃圾较多的Region来清理,并不是完全清理。

什么是三色标记?

三色标记:是一种逻辑上的抽象。将每个内存对象分成三种颜色:

  1. 黑色:表示自己和成员变量都已经标记完毕。
  2. 灰色:自己标记完了,但是成员变量还没有完全标记完。
  3. 白色:自己未标记完。

JVM参数有哪些?

JVM参数大致可以分为三类:

  1. 标注指令: -开头,这些是所有的HotSpot都支持的参数。可以用java -help 打印出来。
  2. 非标准指令: -X开头,这些指令通常是跟特定的HotSpot版本对应的。可以用java -X 打印出来。
  3. 不稳定参数: -XX 开头,这一类参数是跟特定HotSpot版本对应的,并且变化非常大。详细的文档资料非常少。在JDK1.8版本下,有几个常用的不稳定指令:

java -XX:+PrintCommandLineFlags : 查看当前命令的不稳定指令。
java -XX:+PrintFlagsInitial : 查看所有不稳定指令的默认值。
java -XX:+PrintFlagsFinal: 查看所有不稳定指令最终生效的实际值。

Java并发(20)

线程的生命周期?线程有几种状态

线程通常有五种状态,创建,就绪,运行、阻塞和死亡状态:

  1. 新建状态(New):新创建了一个线程对象。
  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
  3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
  5. 死亡状态(Dead):线程执行完了或者因异常退出了run方法,该线程结束生命周期。

阻塞的情况又分为三种:

  1. 等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是object类的方法
  2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
  3. 其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法

sleep()、wait()、join()、yield()之间的的区别

锁池:所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。

等待池:当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中

  1. sleep 是 Thread 类的静态本地方法,wait 则是 Object 类的本地方法。
  2. sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
sleep就是把cpu的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回cpu资源,参与cpu的调度,获取到cpu资源后就可以继续运行了。而如果sleep时该线程有锁,那么sleep不会释放这个锁,而是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁。也就是说无法执行程序。如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出interruptexception异常返回,这点和wait是一样的。
  1. sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
  2. sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。
  3. sleep 一般用于当前线程休眠,或者轮循暂停操作,wait 则多用于多线程之间的通信。
  4. sleep 会让出 CPU 执行时间且强制上下文切换,而 wait 则不一定,wait 后可能还是有机会重新竞争到锁继续执行的。
  5. yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行
  6. join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到线程A结束或中断线程
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("22222222");}});t1.start();t1.join();// 这行代码必须要等t1全部执行完毕,才会执行System.out.println("1111");
}22222222
1111

对线程安全的理解

不是线程安全、应该是内存安全,堆是共享内存,可以被所有线程访问,当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的。

是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。在Java中,堆是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显式的分配和释放。

目前主流操作系统都是多任务的,即多个进程同时运行。为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。

在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。

Thread和Runable的区别

Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。

//会卖出多一倍的票
public class Test {public static void main(String[] args) {// TODO Auto-generated method stubnew MyThread().start();new MyThread().start();}static class MyThread extends Thread{private int ticket = 5;public void run(){while(true){System.out.println("Thread ticket = " + ticket--);if(ticket < 0){break;}}}}
}
//正常卖出
public class Test2 {public static void main(String[] args) {// TODO Auto-generated method stubMyThread2 mt=new MyThread2();new Thread(mt).start();new Thread(mt).start();}static class MyThread2 implements Runnable{private int ticket = 5;public void run(){while(true){System.out.println("Runnable ticket = " + ticket--);if(ticket < 0){break;}<

http://chatgpt.dhexx.cn/article/1bWQWwKZ.shtml

相关文章

笔记总结备份

目录 文章目录 目录前言版本控制常用git 命令 操作系统系统机器数时间管理早期的 Linux 时间系统 中断内存管理内存分区malloc申请一块内存的背后原理RTOS 系统栈和协议栈寄存器磁盘调度算法虚拟内存页面置换算法分段段页式 Volatile&#xff08;可见性&#xff09;Atomic &…

MySQL常见面试题总结

MySQL常见面试题总结5---性能优化 单表优化设计字段索引查询SQL&#xff1a;充分利用索引&#xff0c;减少IO和全表扫描引擎事务锁定表使用外键Java方面 读写分离缓存批量读取和延迟修改分库分表垂直拆分水平拆分表分区 分库分表导致的问题事务一致性问题&#xff08;见分布式事…

Java并发编程实战_盖兹

文章目录 第一部分 基础知识第1章 简介1.1 并发简史1.2 线程的优势1.3 线程带来的风险1.4 线程无处不在(框架线程或类线程并发注意点) 第2章 线程安全性2.1 什么是线程安全性2.2 原子性2.3 加锁机制内置锁&#xff1a;Synchronized关键字可重入锁&#xff1a;获取锁的操作粒度是…

算法(上)

算法 文章目录 算法1. 数组1. 剑指Offer&#xff1a;数组旋转2. 剑指Offer&#xff1a;调整数组顺序使奇数位于偶数前面3. 剑指Offer&#xff1a; 顺时针打印矩阵4. 剑指Offer: 数组中出现次数超过一半的数字5. 剑指Offer&#xff1a;丑数6. 剑指Offer: 数组中的逆序对7. 剑指O…

面试八股知识总结

问题 序列化变量的声明和定义C语言宏中“#”和“##”区别C中extern "C" 的作用了解C中编译时的优化C的特点是什么C的异常处理机制C和C&#xff0c;java的区别C 11 nullptr 和 NULL#ifdef、#else、#endif和#ifndef的作用C 语言的关键字 static 和 C 的关键字static有什…

数据结构学习笔记(参考书籍:大话数据结构和CSDN)

有些解释内容为搬运&#xff0c;如有侵权&#xff0c;联系删除&#xff01;&#xff01;&#xff01; 数据结构 线性表 顺序存储 优点&#xff1a;无须为表中元素之间的逻辑关系而增加额外的存储空间&#xff1b;可以快速的存取表中任一位置的元素。 缺点&#xff1a;插入…

常用汉字5000个(按拼音)

2019独角兽企业重金招聘Python工程师标准>>> 阿,啊,哀,唉,挨,矮,爱,碍,安,岸,按,案,暗,昂,袄,傲,奥,八,巴,扒, 吧,疤,拔,把,坝,爸,罢,霸,白,百,柏,摆,败,拜,班,般,斑,搬,板,版, 吧,疤,拔,把,坝,爸,罢,霸,白,百,柏,摆,败,拜,班,般,斑,搬,板,版, 办,半,伴,扮,拌,瓣,帮…

3500常用汉字书法体检测数据集

毛笔字数据集已收集 草书30044楷书12900行书29465隶书14001篆书9386 数据集有gif格式和jpg格式&#xff0c;白底黑字&#xff0c;支持向量机可训练 样例展示&#xff0c;一共4.5万张 关注微信公众号&#xff1a;酷尔编程&#xff0c;领取

整理的3500个常用汉字的调用字典

3500个常用汉字的调用字典 下载地址:https://download.csdn.net/download/hj960511/85034461 资源说明&#xff1a; 总数目&#xff1a; 目录截图 实际字典情况&#xff1a; 调用方法&#xff1a; 思路&#xff1a;通过读取文件并转换成数组即可进行调用和输出使用 pyt…

编程 常用3500汉字 常用字符

没有重复字符 最后三个字符是空格回车换行制表符 长度4374 1234567890-*/~!#$%^&&#xffe5;…():"{}[]|\?<>,.;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&#xff1a;。&#xff0c;、&#xff01;&#xff1f;&#xff1b;《》—&#xff08;…

【Java】创建日期对象Date

2019独角兽企业重金招聘Python工程师标准>>> 1. 根据指定格式的日期字符串创建Date对象 /*** 通过字符串创建日期** param dateStr yyyy-MM-dd* return 日期*/public static Date createDateFromString(String dateStr) {SimpleDateFormat format new SimpleDateF…

Python-----定义类对象和创建实例对象

如何定义类对象 定义类对象的语法格式&#xff1a; class 类名(object)# 属性和方法其中&#xff0c; 类名由一个或多个单词组合而成&#xff0c;一般来说建议每个单词的首字母大写且其余字母全部小写&#xff0c;例如&#xff1a; SomeClass。(object)表示该类对象继承自Pytho…

For循环类的调用(创建对象)

C#语法复习2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 开发工具与关键技术&#xff1a;vs 作者&#xff1a;卢佳琪 撰写时间&#xff1a;撰写时间&#xff1a;2019年1月31日 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~…

js如何创建JSON对象

js如何创建JSON对象 一、总结 一句话总结&#xff1a;直接创建js数组和js对象即可&#xff0c;然后JSON.stringify就可以获取json字符串&#xff0c;js中的一切都是对象&#xff0c;而且js中的对象都是json对象 js 一切 对象 json对象 我们可以在JavaScript 中使用 JSON&#x…

new`是如何创建对象实例的?

new 是如何创建对象实例的&#xff1f; 1&#xff0c;new操作符做了哪些事情 new操作符的作用&#xff1a;创建对象的实例 用于创建一个用户自定义的对象的实例或者具有构造函数的内置对象的实例 class Person {constructor(name) {this.name name} } // 创建自定义对象的…

46 使用构造函数创建对象

文章目录 1、JavaScript内置的构造函数2、自定义构造函数3、构造函数中的return关键字 1、JavaScript内置的构造函数 JavaScript提供了Object、String、Number等构造函数&#xff0c;通过“new 构造函数()”即可创建对象。使用new关键字创建对象的过程称为实例化&#xff0c;实…

【数字图像处理Matlab】- 实验二:图像噪声及图像复原

2.1 编程实现&#xff1a;复原由运动模糊高斯噪声造成的退化图像&#xff0c;对比逆滤波、维纳滤波以及最小二乘方滤波方法。 代码如下&#xff1a; I imread(005.bmp); I im2double(I); subplot(3, 3, 1),imshow(I),title(原始图像); IG rgb2gray(I); subplot(3, 3, 2),i…

深度CV基础——图像噪声和滤波

一&#xff0c;图像噪声 1.图像噪声的概念&#xff1a; 图像噪声是图像在获取或是传输过程中受到随机信号干扰&#xff0c;妨碍人们对图像理解及分析处理的信号。很多时候将图像噪声看做多维随机过程&#xff0c;因而描述噪声的方法完全可以借用随机过程的描述&#xff0c; 也…

图像噪声的分类与模型

噪声是干扰和妨碍人类认知和理解信息的重要因素&#xff0c;而图像噪声则是图像中干扰和妨碍人类认识和理解图像信息的重要因素。由于噪声本身具有不可预测性&#xff0c;可以将它当做一种随机误差&#xff08;这种误差只有通过概率统计的方法来识别&#xff09;。因此&#xf…

图像噪声认识

噪声&#xff0c;就是在获取有效信息过程中得到的一些冗余或干扰的无用信息&#xff0c;可以是自然现象中客观的存在&#xff0c;也可以是在获取信息的过程中出现误差等干扰而形成&#xff0c;总之就是没用的&#xff0c;但或许会对想要获取的有效信息进行干扰的信息。 图像噪…