java面试题总结

1.java基础

1.1java的概述

1.1.1java的基本数据类型和引用数据类型

基本数据类型:byte、short、int、long、float、double、char、boolean
引用数据类型: String,以及其他的类型

自动类型转换:从低级别到高级别,系统自动转的;

强制类型转换:什么情况下使用?把一个高级别的数赋给一个别该数的级别低的变量;

1.1.2JVM,JRE和JDK的联系和作用

JVM是java的虚拟机,java的程序需要运行在虚拟机上,不同的平台有不同的虚拟机,因此Java可以实现跨平台。

JRE是指java运行环境,包含了java的虚拟机(JVM)和java程序所需要的核心类库,类如:基本数据类型,字符串的处理,线程等

JDK:JDK是java的开发工具包,JDK包含JRE,而JRE包 含JVM。

1.1.3java编译.calss的过程简述?

因为java是给开发人员看的.class文件是给计算机编译的,而javac就是将java的源文件编译成JVM能够识别的二进制代码,也就是java的字节码文件,从表面上看就是把.java文件编译成了.class文件

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

字节码

Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。

采用字节码的好处:

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

1.1.5TCP的三次握手和四次挥手

    TCP协议的三次握手:

        第一次:客户端向服务器发送连接请求

        第二次:服务器向客户端响应连接请求

        第三次:客户端与服务器建立连接

    TCP协议的四次挥手:

        第一次:客户端向服务器发送断开连接请求

        第二次:服务器向客服端响应收到断开连接请求(因为TCP连接是双向的,所以此时服务器依然可以 向客户端发送信息)

        第三次:客户端等待服务器发送信息完成,向服务器确定全部信息发送完毕,并且断开客户端与服务器的连接

        第四次:服务器向客户端断开连接

1.1.6程序架构C/S和B/S

        C/S    Client Server 基于客户端服务器的程序架构 需要一个服务器或者N个客户端 实现数据传输

        B/S    Browser Server 基于浏览器的程序结构 不许要客户端 只需要通过浏览器访问即可

1.1.7TCP和UDP

    基于TCP(Transmission Control Protocol)协议数据传输的特点

        1.面向连接

        2.安全,可靠

        3.效率低

    基于UDP(User Datagram Protocol)协议的数据传输特点

        1.非面向连接的

        2.不安全,不可靠的

        3.效率高

1.18Http和Https的区别

HTTPS是安全超文本协议,HTTP是超文本传输协议,HTTPS在HTTP基础上有更强的安全性,HTTPS是具有安全性的 SSL 加密传输协议
HTTPS 原理

① 客户端将它所支持的算法列表和一个用作产生密钥的随机数发送给服务器 ;

② 服务器从算法列表中选择一种加密算法,并将它和一份包含服务器公用密钥的证书发送给客户端;该证书还包含了用于认证目的的服务器标识,服务器同时还提供了一个用作产生密钥的随机数 ;

③ 客户端对服务器的证书进行验证(有关验证证书,可以参考数字签名),并抽取服务器的公用密钥;然后,再产生一个称作 pre_master_secret 的随机密码串,并使用服务器的公用密钥对其进行加密(参考非对称加 / 解密),并将加密后的信息发送给服务器 ;

④ 客户端与服务器端根据 pre_master_secret 以及客户端与服务器的随机数值独立计算出加密和 MAC密钥(参考 DH密钥交换算法) ;

⑤ 客户端将所有握手消息的 MAC 值发送给服务器 ;

⑥ 服务器将所有握手消息的 MAC 值发送给客户端 。

java中的设计模式

单例模式

定义:确保一个类最多只有一个实例,并提供一个全局访问点

饿汉式和懒汉式

工厂模式

是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行

主要解决:主要解决接口选择的问题。

观察者模式

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。

意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

简单工厂模式

定义:定义了一个创建对象的类,由这个类来封装实例化对象的行为。

1.2java的基础语法

1.2.1& 和 &&区别, | 和|| 区别

& 和 &&区别:

& :无论左边结果是什么,右边都参与运算。
&&:短路与,如果左边为false,那么右边不参数与运算。
| 和|| 区别:

|:或,两边都运算。

||:短路或,如果左边为true,那么右边不参与运算。

1.2.2break和continue:区别

break:作用于switch ,和循环语句,用于跳出,或者称为结束。

break语句单独存在时,下面不要定义其他语句,因为执行不到,编译会失败。当循环嵌套时,break只跳出当前所在循环。

continue:只作用于循环结构,继续循环用的。

作用:结束本次循环,继续下次循环。该语句单独存在时,下面不可以定义语句,执行不到。

1.2.3java中==和equals之间的区别?

(1)"=="是判断两个变量或实例是不是指向同一个内存空间。


(2)"equals"是判断两个变量或实例所指向的内存空间的值是不是相同。


int和Integer的区别

1、Integer是int的包装类,int则是java的一种基本数据类型
2、Integer变量必须实例化后才能使用,而int变量不需要
3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
4、Integer的默认值是null,int的默认值是0

1.2.4.装箱和拆箱?

装箱:把基本类型用它们相应的引用类型包装起来,使其具有对象的性质。int包装成Integer、float包装成Float拆箱:和装箱相反,将引用类型的对象简化成值类型的数据

1.2.5写出数组的排序算法

快速排序法主要是运用了Arrays中的一个方法Arrays.sort ()实现。

冒泡算法:是运用遍历数组进行比较,通过不断的比较将最小值或者最大值一个一个的遍历出来。

public static void bubble_sort(int[] arr){
for(int i= e;i<arr.length-1;i++){
        int temp;
            for(int j - 8; j<arr.length-1;j++){
            if(arr[j]>arr[j+1]){
            temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;
            }
        }
    }
}

选择排序法是将数组的第一个数据作为最大或者最小的值,然后通过比较循环,输出有序的数组。

public static void bubble_sort(int[] arr){
        for(int i= e;i<arr.length-1;i++){
            int index-i;//假设当前下标为最小值int temp;
            //找出最小值元素下标
            for(int j - i+1; j<arr.length-1;j++){
                if(arr[j]<arr[index]){
                    index=j;
                }
            }
        temp=arr[index];
        arr[index]=arr[j];
        arr[j] = temp;
    }
}

插入排序是选择一个数组中的数据,通过不断的插入比较最后进行排序。|

1.3java的主要关键字

1.3.1get post的请求的区别


Post请求

get请求

url的可见性

url参数不可见

参数url可见;

数据的传输上

通过body体传输参数

通过拼接url进行传递参数;

缓存性

post请求不可以缓存

get请求是可以缓存的

安全性

post的请求相对于比较安全

get请求不安全

1.3.2,public,private,protected 作用范围

关键字 当前类 同一package 子孙类 其他package

public √ √ √ √

protected √ √ √ ×

friendly √ √ × ×

private √ × × ×

不写时默认为friendly

public声明的变量及方法,表明在整个包内包外都可使用。

private 声明的变量及方法,只在声明的类内可以使用。

protected包外不可使用。包内可以使用。

1.3.3this和super的区别

this表示当前本类对象的引用,super表示父类对象的引用

1.3.4final 关键字的作用?

被 final 修饰的类不可以被继承,被 final 修饰的方法不可以被重写,被 final 修饰的变量

不可以被改变.如果修饰引用,那么表示引用不可变,引用指向的内容可变.被 final 修饰的

方法,JVM 会尝试将其内联,以提高运行效率,被 final 修饰的常量,在编译阶段会存入常量

池中.

1.3.5final finally finalize区别

final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表

示该变量是一个常量不能被重新赋值。

finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块

中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。

finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调

用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的

最后判断。


1.3.6.为什么重写了equals方法就必须要重写hashcode方法?

(1)如果只重写hashcode方***返回一个hash值,如果这个值相同,我们会认为这个对象的内存地址是相同的,但是它们的值是不一定相同的,因此相同的hashcode不一定有相同的值,但是如果值相同,那他hashcode一定相同

(2)重写equals方法是比较的对象的值,重写hashcode方法和equals方法后,在比较两个对象的时候,先判断hashcode值是否相同,如果返回的hash值就不一样,所以就不会调用equals方法

1.3.7访问修饰符 public,private,protected,以及不写(默认)时的区别

访问修饰符图

1.3.8break ,continue ,return 的区别及作用

break 跳出总上一层循环,不再执行循环(结束当前的循环体)

continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)

return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)

1.3.9. String、StringBuilder、StringBuffer 区别?

String    字符串常量    不可变 使用字符串拼接时是不同的 2 个空间,使用场景:在字符串不经常变化的场景中可以使用String类

StringBuffer 字符串变量    可变    线程安全 字符串拼接直接在字符串后追加,使用场景:在频繁进行字符串运算(如拼接、替换、删除等),并且运行在多线程环境中,则可以考虑使用StringBuffer

StringBuilder 字符串变量    可变    非线程安全 字符串拼接直接在字符串后追加,使用场景:在频繁进行字符串运算(如拼接、替换、和删除等),并且运行在单线程的环境中,则可以考虑使用StringBuilder

1.3.10.关键字: static final instanceof 的用法

static关键字的用法:

1、java中可以通过statin关键字修饰变量达到全局变量的效果;2、static修饰的方法屋于类方法,不需要创建对象就可以调用;3、 static代码块常用于初始化静态变量。

final关键字的用法:

1.当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰;


2. final修饰的方法表示此方法已经是“最后的、最终的”含义,亦即此方法不能被重写;3.final成员变量表示常量,只能被赋值一次,赋值后值不再改变。

instanceof关键字的作用:

Java中的instanceof 是一个二元操作符(运算符)运算符,由于是字母组成,所以是Java的保留关键字,但是和>=,<=,==属同一类,它的作用是用来判断,instanceof 左边对象是否为instanceof右边类的实例,返回一个boolean类型值。还可以用来判断子父类的所屋关系。

bi

1.3.11 String的常用方法

1.str.length()获取字符串长度

String str = "ancdefg";
System.out.println(str.length()); // 长度

2.str.equals(str2)比较字符串内容

String str = "ancdefg";
String str2 = "abcd";
System.out.println(str.equals(str2)); // 比较内容

3.toLowerCase()转为小写 toUpperCase()转为大写

String str3 = "AbCd";
System.out.println(str3.toLowerCase()); // 转换为小写
System.out.println(str3.toUpperCase()); // 转换为大写

4.concat()拼接字符串

String str2 = "abcd";
String str3 = "AbCd";
String str5 = str2.concat(str3); // 拼接字符串

5.indexOf()查找找字符串第一次出现的位置 下标

String s1 = "中abcdefgadrt中你好";
System.out.println(s1.indexOf("a"));

6.substring()截取字符串

    String s3 = "ancdefg";
    System.out.println(s3.substring(1));// 截取字符串
  System.out.println(s3.substring(1, 3)); // 指定开始和结束 截取字符串 包前不包后

1.4面向对象的概述

1.4.1抽象类和接口的区别

a.抽象类是由abstract修饰的 接口是由interface修饰的

b.抽象方法可以继承一个类和实现多个.,接口可以继承一个或多个接口

c.抽象方法可以有public、protected和default这些修饰符,接口中默认只有public final修饰

d.抽象类中可以有构造器,接口中没有构造器

1.4.2重载和重写的区别?

在同一类中方法重载方法名相同参 数列表不同 与返回值和修饰符无关

在父子类中方法的重写方法名参数列表相同 返回值值其父子类 访问修饰符不能严于父类

1.4.3方法的重写

子类重写父类的【方法】,和属性无关。

必须有继承

·方法必须一致(参数,返回值,方法名),方法体不同修饰符:范围可以扩大

抛出的异常:范围可以被缩小,但不能扩大

1.4.4方法的重载

·一个类中定义多个同名的方法,但每个方法具有不同的参数的类型或参数的个数。·方法重载的规则:

。 同一个类中。方法名相同

。参数类别必须不同(个数不同、类型不同、【类型不完全相同情况下】参数排列顺序不同)。返回值、访问修饰符无关

1.4.3值传递和引用传递?

值传递:就是对于基本的数据类型,传递的是值的副本,如果这个副本发生改变,不会影响原来数据的数值

引用传递:是对对象的传递,传递的是对象的引用,如果这个引用被外部做了改变,就会影响所有的对象。

1.4.4成员变量和局部变量的区别?

(1)成员变量是属于类的,存储在堆内存中,局部变量是属于方法的,存储在栈内存中

(2)成员变量能够使用public private static等修饰符修饰,而局部变量不能被public private static等权限修饰符修饰,成员变量和局部变量都可以用final修饰。

(3)成员变量的生命周期随着对象的销毁而销毁,随着对象的创建而存在,局部变量随着方法的调用而销毁。

(4))成员变量如果没有被赋予初值会赋予基本数据类型默认的数值,局部变量如果没有赋予初值,不会有默认值。

1.4.5,静态方法和实例方法的区别?

(1)静态方法的调用可以通过类名.方法名和对象名.方法名调用,而实例化方法只能够通过对象名.方法名调用

(2)静态方法调用本类成员时,只能够调用静态的成员方法和变量,不能调用非静态的。

1.4.6重定向和内部转发的区别


重定向

内部转发

地址栏的变化

地址栏会发生改变

地址栏不会发生改变

请求的次数

发生两次请求

只发生一次请求

请求的方式不同

重定向需要通过response对象来实现

内部转发需要通过request对象来实现,

资源的访问

重定向可以访问外部服务器中的资源

内部转发只能访问本服务器下的资源

资源的保存

重定向不可以

可以在内部转发过程中把数据保存到request域对象中

1.4.7. 强引用和软引用和弱引用以及虚引用?

1、强引用

最普遍的一种引用方式,如 String s = "abc",变量 s 就是字符串“abc”的强引用,只要

强引用存在,则垃圾回收器就不会回收这个对象。

2、软引用(SoftReference)

用于描述还有用但非必须的对象,如果内存足够,不回收,如果内存不足,则回收。一般用

于实现内存敏感的高速缓存,软引用可以和引用队列 ReferenceQueue 联合使用,如果软引

用的对象被垃圾回收,JVM 就会把这个软引用加入到与之关联的引用队列中。

3、弱引用(WeakReference)

弱引用和软引用大致相同,弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的

生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用

的对象,不管当前内存空间足够与否,都会回收它的内存。

4、虚引用(PhantomReference)

就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象

仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 虚

引用主要用来跟踪对象被垃圾回收器回收的活动。

虚引用与软引用和弱引用的一个区别在于:

虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,

如果发现它还有虚引,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队

列中。

1.4.8.深拷贝和浅拷贝的区别是什么?

浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用

仍然指向原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象.

深拷贝:被复制对象的所有变量都含有与原来的对象相同的值.而那些引用其他对象的变量

将指向被复制过的新对象.而不再是原有的那些被引用的对象.换言之.深拷贝把要复制的对

象所引用的对象都复制了一遍.

1.4.9对象的创建

说到对象的创建,首先让我们看看 Java 中提供的几种对象创建方式:

使用new关键字    调用了构造函数

使用Class的newInstance方法    调用了构造函数

使用Constructor类的newInstance方法    调用了构造函数

使用clone方法    没有调用构造函数

使用反序列化     没有调用构造函数

1.4.10其中Java 面向对象编程三大特性:封装 继承 多态

封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。

继承:关于继承如下 3 点请记住:

子类拥有父类非 private 的属性和方法。

子类可以拥有自己属性和方法,即子类可以对父类进行扩展。

子类可以用自己的方式实现父类的方法。

多态性:父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。

所谓多态就是指程序中定义的引用变量,这个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法),方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。

1.4.11.什么是多态,多态的优点,多态实现的条件?

(1)就是对于同一消息,根据调用对象的不同做出不同的反应,例如一个接口,这个接口的不同实现类,对这个方法的实现不一样,当调用时候得出的结果也就不一样。

(2)多态的优点是可替换性,例如计算一个圆的面积,这个方法同样可以计算圆环的面积。可扩展性就是如果新增一个方法,不会破坏已经存在的方法的继承性和多态性。

(3)多态实现的三个条件:重写继承父类引用指向子类对象。

1.4.12现象对象的五大基本原则

单一职责原则SRP(Single Responsibility Principle)

类的功能要单一,不能包罗万象,跟杂货铺似的。

开放封闭原则OCP(Open-Close Principle)

一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。

里式替换原则LSP(the Liskov Substitution Principle LSP)

子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~

依赖倒置原则DIP(the Dependency Inversion Principle DIP)

高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。

接口分离原则ISP(the Interface Segregation Principle ISP)

设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。


1.5IO流与反射

1.5.1java 中 IO 流分为几种?

按照流的流向分,可以分为输入流和输出流;

按照操作单元划分,可以划分为字节流和字符流;

按照流的角色划分为节点流和处理流。

1.5.2BIO,NIO,AIO 有什么区别?

BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。

NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。

AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

1.5.3字节流和字符流的区别?

字符流和字节流的使用非常相似,但是实际上字节流的操作不会经过缓冲区(内存)而是直接操作文本本身的,而字符流的操作会先经过缓冲区(内存)然后通过缓冲区再操作文件

以字节为单位输入输出数据,字节流按照 8 位传输

以字符为单位输入输出数据,字符流按照 16 位传输

1.5.3Files的常用方法都有哪些?

Files. exists():检测文件路径是否存在。

Files. createFile():创建文件。

Files. createDirectory():创建文件夹。

Files. delete():删除一个文件或目录。

Files. copy():复制文件。

Files. move():移动文件。

Files. size():查看文件个数。

Files. read():读取文件。

Files. write():写入文件。

1.5.4java中的反射,什么是反射机制

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

1.5.5反射机制优缺点

优点: 运行期类型的判断,动态加载类,提高代码灵活度。

缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。

1.5.6Java获取反射的三种方法

1.通过new对象实现反射机制

2.通过路径实现反射机制

3.通过类名实现反射机制

1.5.7 Java的反射机制

(1).反射和类的关系

在程序运行状态中,对任意一个(指的是.class文件),都能够知道这个类所有属性方法

(2).反射和类对象的关系

反射对于某个类的一个对象,都能够调用它的任意一个方法属性

(3).Java反射机制(基于(1) 和 (2))

[1]. 这种动态获取的信息以及动态地调用类对象的方法或者属性功能称为Java语言的反射机制。

[2]. 通俗描述反射机制动态获取对象的信息就称为反射

(4).Java反射机制的好处

极大地提高了应用程序扩展性


1.6常用的API(Spring Date )

1.6.1 String 类的常用方法都有那些?

indexOf():返回指定字符的索引。

charAt():返回指定索引处的字符。

replace():字符串替换。

trim():去除字符串两端空白。

split():分割字符串,返回一个分割后的字符串数组。

getBytes():返回字符串的 byte 类型数 组。

length():返回字符串长度。

toLowerCase():将字符串转成小写字母。

toUpperCase():将字符串转成大写字符。

substring():截取字符串。

equals():字符串比较。

1.6.2自动装箱与拆箱

装箱:将基本类型用它们对应的引用类型包装起来;

拆箱:将包装类型转换为基本数据类型;

1.6.3int 和 Integer 有什么区别

Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。

1.6.4Java 为每个原始类型提供了包装类型

原始类型: boolean,char,byte,short,int,long,float,double

包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

1.6.5字符型常量和字符串常量的区别

形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符

含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)

占内存大小 字符常量只占两个字节 字符串常量占若干个字节(至少一个字符结束标志)

1.6.6什么是字符串常量池?

字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。


1.7java web

2.1.session和cookie的区别?

( 1) seesion是通过服务器产生,服务器保存的,Cookie是服务器产生,浏览器保存。

(2) session是没有大小限制,cookie有大小限制最多4kb。

(3) cookie只能够存储String类型数据,session可以存储任意类型数据。

(4) cookie不能存储敏感信息,session能够存储敏感信息。

(5)浏览器关闭cookie不会消失,session会消失。

session和Cookie之间的联系,Cookie中存储这session的唯─标识Sessionld。

2.2.cookie的缺陷?

(1) cookie不能存储敏感信息,并且是明文的(2)不同的浏览器对应的cookie存储的大小有限制(3) cookie存储的内容只能是字符串

2.3.session的运行过程?

(1) seesion是由服务器产生,服务器保存的,服务器产生一个Session对象时会带有一个唯一的SessionlD来标识Session会话对象,同时会产生一个Cookie来存储这个SessionlD。

(2) seession对象在一个浏览器第一次访问的时候,会保存存储Sessionld的Cookie。

(3)当后续再次访问的时候,这个请求就会携带Cookie,浏览器会先查看Cookie中存储的SessionlD来找到相对应的Session会话对象,来区分不同的对象。

2.4Servlet的生命周期和注册方式

  • 注册方式
    • 1、实现servlet接口(偏向底层)
    • 2、继承GenericServlet(觉得1不好用,就开发了这个,现在基本没啥用)
    • 3、继承HttpServlet(现在大多数用这个)
  • 生命周期
    • Servlet 初始化后调用 init () 方法。Servlet 调用 service() 方法来处理客户端的请求。Servlet 销毁前调用 destroy() 方法。最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。


2.6.request的getAttribute()和getParameter()的区别?

( 1) request.getAttribute()使用的时候返回的是一个对象

request.getparameter ()使用的时候返回的是一个字符串

(2) request.getAttribute()使用的时候需要先通过setAttribute来实现赋值。

(3) request.getParameter获取的是前端控件中通过Post或者Get请求传入的数据,或者是url中的参数。

2.8.如何处理中文乱码问题?

( 1) request.setCharaterEncoding("UTF-8")解决请求中文乱码问题(2) response.setContentType("text/html";charset=utf-8)

2.9.JSP的四大作用域?

( 1) page就是当前页面中有效,不过需要通过pageContext方法来实现设置属性,获取属性

(2) request就是在某个请求中有用

(3) session就是在某个会话中祝作中va学科(4) application就是作用域在整个应用中

2.10.Jsp常用的动作(指令)?

(1) jsp: include就是一个页面包含另一个页面(2) jsp: useBean就是实例化一个javaBean

(3) jsp: setProperty就是给javaBean设置某个属性(4) jsp: getProperty就是获取javaBean的属性(5) jsp: forward就是跳转某个界面

2.11.execute,executeQuery,executeUpdate的区别是什么?

(1) execute就是当你不知道是执行的数据库语句是哪个的时候就可以使用execute方法,如果执行的是查询语句就会返回true,如果是增删改就会返回false

(2) executeQuery是执行的查询,返回的是一个Resultset结果集(3) executeUpdate是执行的增删改操作,返回的是一个int类型的数据,也就是数据库受影响行数。

2.12JDBC的使用步骤

    1. 加载 jdbc 驱动程序
    1. 拼接 jdbc 需要连接的 url
    1. 创建数据库的连接
    1. 创建一个Statement
    1. 执行SQL语句
    1. 处理执行完SQL之后的结果
    1. 关闭使用的JDBC对象

2.12.JDBC的ResultSet?

( 1) ResultSet是存储的数据库查询结果,只存储查询结果的一部分信息。

(2) ResultSet类似于一个链表,如果想要获取全部的信息,可以通过指针指向的部分信息去执行next方法来获取全部的信息,如果没有信息了就返回false,有就会返回结果集。

2.13.数据库连接池的原理,为什么要使用连接池?

(1)数据库连接池就是一个连接缓冲池,当用户需要一个连接的时候,直接从数据库连接池中取出来一个,当连接不需要使用的时候就直接返还到缓冲池,这样做可以节约数据库连接资源。同时数据库连接池可以通过设置最大连接数来避免无限制的连接。

好处:使用连接池的好处在于避免了重复创建数据库连接,节约了数据库连接资源。使用数据库连接池的另一个好处就是可以实现多个操作共享一个连接。

2.12.JDBC的ResultSet?

( 1) ResultSet是存储的数据库查询结果,只存储查询结果的一部分信息。

(2) ResultSet类似于一个链表,如果想要获取全部的信息,可以通过指针指向的部分信息去执行next方法来获取全部的信息,如果没有信息了就返回false,有就会返回结果集。

2.13.数据库连接池的原理,为什么要使用连接池?

(1)数据库连接池就是一个连接缓冲池,当用户需要一个连接的时候,直接从数据库连接池中取出来一个,当连接不需要使用的时候就直接返还到缓冲池,这样做可以节约数据库连接资源。同时数据库连接池可以通过设置最大连接数来避免无限制的连接。

好处:使用连接池的好处在于避免了重复创建数据库连接,节约了数据库连接资源。使用数据库连接池的另一个好处就是可以实现多个操作共享一个连接。

2.14.为什么要使用PreparedStatement ?

(1) PreparedStatement是一个预处理的Statement,他继承了Statement,不会涉及到sql语句的拼接,会直接执行相关的sql语句(2) PreparedStatement处理的速度比较快,能够解决sql注入的问题。2.15.JDBC连接步骤?

(1)创建数据库连接驱动(2)创建数据库连接对象

(3)创建StateMent对象,初始化sql语句

(4))执行sql语句,返回结果集

(5)处理结果集

2.16.jsp有哪些内置对象,作用分别是什么?

(1) session一个会话对象

(2) request用来封装用户发送的请求

(3) response是用来封装用户请求返回的结果集

(4) page就是当前界面

(5) application是封装的整个应用程序

(6) Exception是封装的界面返回的异常

2.18.Session的销毁条件?

关闭浏览器只会使存储在客户端浏览器内存中的session cookie失效,不会使服务器端的session对象失效。因此销毁httpsession有以下3中方法:1.直接调用HttpSession的invalidate()方法,该方法使 HttpSession 失效

2.服务器进程被停止或者服务器卸载了当前WEB应用

3.距离上一次收到客户端发送的session id时间间隔超过了session的最大有效时间

设置HttpSession 的过期时间: session.setMaxInactivelnterval(5);单位为秒

或者在web.xml文件中设置HttpSession的过期时间:单位为分钟:30
















2.Java的集合

2.1集合的概述

2.1.1集合的特点

集合的特点主要有如下两点:

对象封装数据,对象多了也需要存储。集合用于存储对象。

对象的个数确定可以使用数组,对象的个数不确定的可以用集合。因为集合是可变长度的。

2.1.2集合和数组的区别

数组是固定长度的;集合可变长度的。

数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。

数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。

2.1.3使用集合框架的好处

容量自增长;

提供了高性能的数据结构和算法,使编码更轻松,提高了程序速度和质量;

允许不同 API 之间的互操作,API之间可以来回传递集合;

可以方便地扩展或改写集合,提高代码复用性和可操作性。

通过使用JDK自带的集合类,可以降低代码维护和学习新API成本。
















2.1.4常用的集合类有哪些?

Map接口和Collection接口是所有集合框架的父接口:

Collection接口的子接口包括:Set接口和List接口

Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等

Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等

List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、List、Queue三种子接口。我们比较常用的是Set、List,Map接口不是collection的子接口。

Collection集合主要有List和Set两大接口

2.1.5哪些集合类是线程安全的?

vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。

statck:堆栈类,先进后出。

hashtable:就比hashmap多了个线程安全。

enumeration:枚举,相当于迭代器。

















2.2Collection接口

2.2.0 ArrayList和LinkedList数组结构

ArrayList集合特点

源代码:

        1.底层是一个Object类型的数组

        2.当我们访问无参构造,ArrayList类帮我们维护一个空的数组,当我们第一次赋值,数组长度改为10

        3.原数组长度为10,当我们添加第11个元素,扩容1.5倍,通过复制数组实现,需要移动元素

        4.删除元素,也需要移动元素

集合访问特点:

        1.默认添加,查询,修改快

        2.插入元素,删除元素慢,因为要移动元素

        3.线程不安全的

        4.有序的,可以为空,可以重复

LinkedList集合特点:

双向链表

没有初始大小,没有上限大小,空间不连续,数据有序号,没有下标

增删快,因为不需要移动元素

查询慢,因为数据结构为双向链表不能直接根据序号获取某一个元素,必须从前往后或者从后往前

允许为null,可以重复

线程不安全

空间不连续,但是数据有序号,序号从0开始,依次累计

2.2.1迭代器 Iterator 是什么?有什么特点?

Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。

特点

Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。

2.2.2Iterator 和 ListIterator 有什么区别?

Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。

Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。

ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

说一下 ArrayList 的优缺点

2.2.3ArrayList的优缺点

ArrayList的优点:

ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快。

ArrayList 在顺序添加一个元素的时候非常方便。

ArrayList 的缺点如下:

删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。

插入元素的时候,也需要做一次元素复制操作,缺点同上。

ArrayList 比较适合顺序添加、随机访问的场景。

2.2.4ArrayList 和 LinkedList 的区别是什么?

数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。

随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。

增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。

内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。

线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

2.2.5ArrayList 和 Vector 的区别是什么?

这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合

线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。

性能:ArrayList 在性能方面要优于 Vector。

扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。

同步:Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。

Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。

2.2.6阐述 ArrayList、Vector、LinkedList 的存储性能和特性?

ArrayList、LinkedList、Vector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。

Vector 中的方法由于加了 synchronized 修饰,因此 Vector 是线程安全的容器,但性能上较ArrayList差。

LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以 LinkedList 插入速度较快。









2.2.7List 和 Set 的区别

List , Set 都是继承自Collection 接口

List 特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。

Set 特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。

Set和List对比

另外 List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。

Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。

List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变

2.2.8说一下 HashSet 的实现原理?

HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。

2.2.9HashSet是如何保证数据不可重复的?

首先先检查重复的问题,HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较。HashSet 中的add ()方***底层使用HashMap 的put()方法,它的底层就是hashmap

HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复( HashMap 比较key是否相等是先比较hashcode 再比较equals )。

2.2.10HashSet与HashMap的区别

HashMap

    HashSet

实现了Map接口

    实现Set接口

存储键值对

    仅存储对象

调用put()向map中添加元素

    调用add()方法向Set中添加元素

HashMap使用键(Key)计算Hashcode    

HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false

HashMap相对于HashSet较快,因为它是使用唯一的键获取对象

    HashSet较HashMap来说比较慢

2.2.11Collection 和 Collections 有什么区别?

java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。

Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。


2.3Map接口

2.3.0.HashMap 数据结构

HashMap数据结构:

HashMap是一个无序的集合,它就是将以上两种数据结构进行了结合,HashMap是一空间换时间的一种做法。

HashMap JDK1.7 数组 + 单向链表

HashMap JDK1.8 数组 + 单向链表 + 红黑树

HashMap存储数据的过程

1.根据key计算出hash值,在加上一些扰动算法,最终得出当前元素应该存放的数组的位置

a.数组默认长度为16

b.负载因子 0.75 表示数组的使用率达到百分之75 就扩容数组 扩容2倍 resize();

2.根据key值计算出存储在数组的中的位置,如果数组中已经有值,那么向下延伸为单向链表

3.如果链表的长度大于8 并且集合中的元素个数大于64 那么链表将转换为红黑树 以此来缓解单向链表查询慢的问题;当红黑树元素个数小于6的时候,就会转换回链表。

2.3.1说一下 HashMap 的实现原理?

HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

HashMap的数据结构: 在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

HashMap 基于 Hash 算法实现的

当我们往Hashmap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。

核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。

需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率。

2.3.2HashMap在JDK1.7和JDK1.8中有哪些不同?

JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

jDK1.8之后

相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
















JDK1.7 VS JDK1.8 比较

JDK1.8主要解决或优化了一下问题:

1.resize 扩容优化

2.引入了红黑树,目的是避免单条链表过长而影响查询效率,红黑树算法请参考

3.解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。

不同    

JDK 1.7    

JDK 1.8

存储结构    

数组 + 链表    

数组 + 链表 + 红黑树

初始化方式

单独函数:inflateTable()

直接集成到了扩容函数resize()中

存放数据的规则

无冲突时,存放数组;冲突时,存放链表

无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树

插入数据方式

头插法(先讲原位置的数据移到后1位,再插入数据到该位置)

尾插法(直接插入到链表尾部/红黑树)

扩容后存储位置的计算方式

全部按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1)

按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量)

2.3.3为什使用而红黑树而不使用二叉树了?

之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持"平衡"是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。

java8不是用红黑树来管理hashmap,而是在hash值相同的情况下(且重复数量大于8),用红黑树来管理数据。 红黑树相当于排序数据,可以自动的使用二分法进行定位,性能较高。一般情况下,hash值做的比较好的话基本上用不到红黑树。

红黑树牺牲了一些查找性能 但其本身并不是完全平衡的二叉树。因此插入删除操作效率略高于AVL树。

2.3.4为什么hashmap的负载因子是0.75

负载因子是0.75的时候,这是时间和空间的权衡,空间利用率比较高,而且避免了相当多的Hash冲突,使得底层的链表或者是红黑树的高度也比较低,提升了空间效率。

2.3.5HashMap 与 HashTable 有什么区别?

线程安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);

效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;

对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,d但是可以有一个或多个键所对应的值为 null。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛NullPointerException。

初始容量大小和每次扩充容量大小的不同

①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后.



扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。

②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。也就是说 HashMap 总是使用2的幂作为哈希表的大小。

底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。

2.3.6如何决定使用 HashMap 还是 TreeMap?

对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。

2.3.7HashMap 和 ConcurrentHashMap 的区别

ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)

HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。

2.3.8ConcurrentHashMap 和 Hashtable 的区别?

ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。

底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;

实现线程安全的方式(重要):

① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;

② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,这样会越来越激烈导致效率非常低。

2.3.9ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?

JDK1.7

首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。

在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现,结构如下:

一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。

JDK1.8

在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

2.3.10hashmap的头插法和尾插法

JDK1.8之前采用的是头插法插入数据,头插法就是从个空表开始,重复读入数据,生成新结点将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头结点之后。

JDK1.8来用的尾插法插入数据。尾插法:从个空表开始,重复读入数据,生成新结点将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表尾结点之后。









1.2.2List,Set,Map三者的区别和特点?

List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。

Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。

Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。

Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap



JVM

1.1内存模型(1.7 和1.8),每个区域做什么

方法区:本地方法区存放的是类的字节码信息,静态方法,静态变量,常量

本地方法栈:加载native方法的地方,native方法就是底层的方法,比如:C,C++编写的方法

程序计数器:每一个程序在运行时需要知道运行到那个地方的了,程序计数器就是干这种事的。

虚拟机栈:管理程序运行的地方,每个方法的运行都伴随着压栈和出栈,先进后出的原则。每一个栈帧存放的都是方法的参数,方法的返回值,方法中的局部变量,以及父帧和子帧等信息。

堆:存放对象的地方,一般new一个对象,指针存放在栈中,对象存放在堆中,其中,堆又分为新生代,老年代,永久代。

以上五部分:其中方法区和堆是线程共享的,其他三部分是线程私有的,我们一般进行垃圾回收都集中在线程共享区域。数据共享区域,堆管存储,栈管运行。

1.2堆的内存组成(新生代和老年代)

堆分为:新生代,老年代和永久代(jdk8之后就消失了,新增了元空间)。新生代有分为伊甸区,两个幸存区,内存的默认比例是:8:1:1

一个对象被new出来之后先进入到伊甸区,伊甸区达到一定的阈值(70%)之后要进行一次Minor GC,又称小GC,这时伊甸区没有用的对象清洗掉,幸存的对象被转移到幸存区。这里面具体来讲:每一次小gc,一般除了清理伊甸区之外还会清理幸存区的from区,两个区域幸存的对象转移到幸存区的To区,转移完毕之后TO区变为from区。周而复始,当幸存区中的对象年龄超过15岁,会被转移到老年代。而一些大对象直接进入老年代。老年代中如果内存不够了,需要进行一次Major GC又称之为full GC,清理老年代中存储的对象,老年代中进行的gc一般比较耗时,一是小gc的10倍时间。

永久代中存储的是我们程序运行时自身所需要的类,比如说:tomcat运行web项目,而web项目需要导入很多的jar包,一般就将这些jar包加载到永久代中。

永久代现在基本上没有了,jdk1.6的时候还有,jdk1.7时候已经去永久代了,jdk1.8之后就彻底没有了,变为了元空间。因为Oracle收购了BEA公司的JRocket ,需要将JRocket与SUN公司的HotSpot进行整合。

1.3类的加载过程

Java中ClassLoader(抽象类)的主要职责就是负责将各种class文件加载到JVM中,生成这个类的各种数据结构,然后分布到JVM对应的内存区域中。

1;加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象

2;验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

3;准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。

4;解析:主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析(这里涉及到字节码变量的引用,如需更详细了解,可参考《深入Java虚拟机》)。

5;初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。

1.4类的加载器

类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例,在虚拟机提供了3种类加载器,引导(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器),下面分别介绍

启动(Bootstrap)类加载器

启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。

扩展(Extension)类加载器

扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类,它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器。

系统(System)类加载器

也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader。它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。

1.5什么情况下会出现OOM异常?

分为两种情况:

1:老年代进行了一次full GC之后,还是无法满足对象存储的空间需求,会报 Java Heap Space 类型的OOM异常,这种异常必定伴随着一个full GC.

2:如果永久代不够用,也会报OOM异常:PermGen space ,是因为元空间不足导致的。

1.6默认的堆内存大小是多少?

最小值: 服务器的可用内存1/64

最大值: 服务器可用内存的 1/4

一般我们在服务器上会将最小值和最大值设置为相同的值,原因是堆空间是先使用最小值的空间进行存储,空间不足,进行小GC,并且扩大内存,一直达到最大值为止,这期间伴随着大量的GC,和空间的升级,效率不高,所以我们一般会通过日志查看堆内存使用的情况,并给出一个特定的值。

1.7jdk1.6,1.7,1.8之间内存上的区别?

1.9新生代的对象进入老年代的条件

默认年龄达到15岁,不过可以通过MaxTenuringThreshold 参数进行修改,但是该参数只能在只对Serial和ParNew两个新生代收集器有用 ,其他的指定也没有作用。


1.20双亲委派模型?如何打破的?

双亲委派模型:

双亲委派摸型就是类加载器在收到加载类的请求的时候,会一层一层的先去父类加载器让父类先去加载这个类,如果父类没有这个类就在子类中加载,如果最顶层的父类加载之后,就会成功返回

双亲委派模型的好处作用:

1.避免类的重复加载。一旦一个类被父类加载器加载之后,就不会再被委派给子类进行加载。
2.保护程序安全。保证了Java程序的隐定运行,可以避免类的重复加载,也保证了Java的核心API不被篡改。

如何打破双亲委派模型:

1.重写loaddass(方法破坏双亲委派摸型

loadclass的作用就是通过指定的全限定名加或载class,因为双亲委派机制的实现就是通过这个方法实现的,这个方法可以指定类通过什么加载器来加载,所以我们如果改写他的规则,就相当于打破了双亲委派机制

⒉线程上下文类加载器破坏双亲委派模型:

双亲委派模型的局限性:父类加载器无法加载子类加载器路径中的类。双亲委派模型最典型的不适用场景是SPI的使用。所以提供了一种线程上下文类加载器,能够使父类加载器调用子类加载器进行加载。简单来说就是接口定义在了启动类的加载器中,而实现类定义在了其他类加载器中,当启动类加载器需要加载其他子类加载器路径中的类时,使用了线程上下文类加载器(默认是应用程类加载器)来实现父类调用子类的加载器进行类的加载

3.OSGi实现模块化热部署

OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块(Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi环境下,类加载器不在是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构

1.21内存的泄露和内存的溢出

内存泄露(memory leak),是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

内存溢出(out of memory),是指程序在申请内存时,没有足够的内存空间供其使用,出现内存溢出。

内存泄露最终也会导致内存溢出。


1.22说一下堆栈的区别?

物理地址

堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩)

栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。

内存分别

堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。

栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。

存放的内容

堆存放的是对象的实例和数组。因此该区更关注的是数据的存储

栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。

PS:

静态变量放在方法区

静态的对象还是放在堆。

程序的可见度

堆对于整个应用程序都是共享、可见的。

栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。

1.23队列和栈是什么?有什么区别?

队列和栈都是被用来预存储数据的。

操作的名称不同:

队列的插入称为入队,队列的删除称为出队。栈的插入称为进栈,栈的删除称为出栈。

可操作的方式不同:

队列是在队尾入队,队头出队,即两边都可操作。而栈的进栈和出栈都是在栈顶进行的,无法对栈底直接进行操作。

操作的方法不同:

队列是先进先出(FIFO),即队列的修改是依先进先出的原则进行的。新来的成员总是加入队尾(不能从中间插入),每次离开的成员总是队列头上(不允许中途离队)。而栈为后进先出(LIFO),即每次删除(出栈)的总是当前栈中最新的元素,即最后插入(进栈)的元素,而最先插入的被放在栈的底部,要到最后才能删除。


1.24 JVM 调优的工具?

JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。

jconsole:用于对 JVM 中的内存、线程和类等进行监控;

jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。

1.25常用的 JVM 调优的参数都有哪些?

-Xms2g:初始化推大小为 2g;

-Xmx2g:堆最大内存为 2g;

-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;

-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;

–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;

-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;

-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;

-XX:+PrintGC:开启打印 gc 信息;

-XX:+PrintGCDetails:打印 gc 详细信息。

项目中有遇到JVM调优吗?在哪?

  1. 比如之前遇到的cpu load过高:(load数要低于cpu的数量)
    1. cpu load值 就是运行中的线程和等待中的线程数
    2. 当请求大于处理的能力时,cpu load就会升高
    3. cpu load过高的原因:从数据库方面来说,可能是异常的sql导致的
      1. 通过命令:top-u mysql -c检测当前占用cpu资源最多的进程命令 --(-c 是为了显示进程对应的执行命令语句,方便查看是什么操作导致load过高)
      2. 找到异常的sql之后
      1. 是索引的问题就选择合适的索引
      2. 调整sql语句,比如对应order by分页采用延迟关联
      3. 业务层面增加缓存,减少直接对数据里的访问



1.26对象的创建

说到对象的创建,首先让我们看看 Java 中提供的几种对象创建方式:

Header

解释

使用new关键字

调用了构造函数

使用Class的newInstance方法

    调用了构造函数

使用Constructor类的newInstance方法

调用了构造函数

使用clone方法    

没有调用构造函数

使用反序列化    

没有调用构造函数

下面是对象创建的主要流程:

虚拟机遇到一条new指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载。类加载通过后,接下来分配内存。若Java堆中内存是绝对规整的,使用“指针碰撞“方式分配内存;如果不是规整的,就从空闲列表中分配,叫做”空闲列表“方式。划分内存时还需要考虑一个问题-并发,也有两种方式: CAS同步处理,或者本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。然后内存空间初始化操作,接着是做一些必要的对象设置(元信息、哈希码…),最后执行<init>方法。


1.27为对象分配内存

类加载完成后,接着会在Java堆中划分一块内存分配给对象。内存分配根据Java堆是否规整,有两种方式:

指针碰撞:如果Java堆的内存是规整,即所有用过的内存放在一边,而空闲的的放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样便完成分配内存工作。

空闲列表:如果Java堆的内存不是规整的,则需要由虚拟机维护一个列表来记录那些内存是可用的,这样在分配的时候可以从列表中查询到足够大的内存分配给对象,并在分配后更新列表记录。


1.28处理并发安全问题

对象的创建在虚拟机中是一个非常频繁的行为,哪怕只是修改一个指针所指向的位置,在并发情况下也是不安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案:

1.对分配内存空间的动作进行同步处理(采用 CAS + 失败重试来保障更新操作的原子性);

2.把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 时,才需要同步锁。通过-XX:+/-UserTLAB参数来设定虚拟机是否使用TLAB。



GC垃圾回收

1.1简述一下垃圾回收机制

在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

1.2GC怎么判断对象是否可回收

它把内存中的每一个对象都看作一个节点,并且定义了一些对象作为根节点“GC Roos”。如果一个对象中有另一个对象的引用,那么就认为第一个对象有一条指向第二个对象的边,如下图断示。NM会起一个线程从所有的GC Roots开始往下遍历,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收.

1.3GC常用垃圾回收算法

复制算法    他将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收

优点:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。

缺点:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。

标记清除算法:1。标记(Mark) :从根集合开始扫描,对存活的对象进行标记。2清除(Sweep):扫描整个内存空间,回收未被标记的对象,使用free-list记录可以区域。

优点:实现简单,不需要对象进行移动。

缺点:标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。

标记整理算法:标记 - 整理”算法的标记过程与“标记 - 清除”算法大致相同,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

优点:解决了标记-清理算法存在的内存碎片问题。

缺点:仍需要进行局部对象移动,一定程度上降低了效率。

分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。

1.4GC在堆内存的工作过程

一个对象被new出来之后先进入到伊甸区,伊甸区达到一定的阈值(70%)之后要进行一次Minor GC,又称小GC,这时伊甸区没有用的对象清洗掉,幸存的对象被转移到幸存区。这里面具体来讲:每一次小gc,一般除了清理伊甸区之外还会清理幸存区的from区,两个区域幸存的对象转移到幸存区的To区,转移完毕之后TO区变为from区。周而复始,当幸存区中的对象年龄超过15岁,会被转移到老年代。而一些大对象直接进入老年代。老年代中如果内存不够了,需要进行一次Major GC又称之为full GC,清理老年代中存储的对象,老年代中进行的gc一般比较耗时,一是小gc的10倍时间。














1.5.如何判断一个对象是否要被回收?

引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;

可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。


1.7GC Roots被激活的条件?

(1)调用System.gc的时候

(2)系统自己决定激活的条件。

1.8能够作为GC roots的对象有哪些?

(1)虚拟栈引用的对象

(2)本地栈中调用Native方法的对象

(3)方法区类静态变量引用的对象

(4)方法区常量引用的对象

1.9垃圾收集器都使用了哪些算法?

G1,CMS 使用的都是标记清除算法

新生代的垃圾收集器都是用的复制算法

剩余的老年代的都用到是标记整理算法。

1.20常用的垃圾回收器?

常见的垃圾回收器有7个,可以分为四大类:

串行垃圾收集器、并行垃圾收集器、CMS 垃圾收集器、G1 垃圾收集器。

串行的垃圾回收器是:Serial Serial Old

并行的垃圾回收器有:Paraller Paraller Old ParNew

新生代的垃圾回收器:Serial ParNew Paraller G1

老年代的有哪些:Serial Old Paraller Old CMS G1

JDK1.9之后默认的垃圾回收器是G1

1.21JVM 有哪些垃圾回收器?

Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;

ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;

Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;

Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;

Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;

CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。

G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

1.22常见的垃圾收集器的优劣对比?

工作原理:

Serial(串行) 用户线程访问资源,到达一定的安全点之后,开始进行垃圾回收,垃圾回收是单线程的,一般效率比较低,一般是jdk刚开始发行的时候使用,或者是在桌面应用程序上使用。在垃圾回收期间,用户线程是暂停的状态。在我们的新生代是复制算法,工作在老年代的Serial Old 是标记整理算法

ParNew (并行): PartNew 收集器实质上是 Serial 收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为和 Serial 收集器完全一致。尤其是 JDK7 之前的遗留系统中首选的新生代收集器。

Parallel 收集器: 是并行垃圾收集器,是jdk1.8默认的垃圾收集器。它的工作机制与 ParNew 垃圾收集器是一样的,只是在此基础之上,增加了两个和系统吞吐量相关的参数,使得其使用起来更加的灵活和高效。通过这两个参数,Parallel垃圾收集器可以自动的进行参数的调整达到性能的一个提升。

所谓的吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值,例如用户代码加上垃圾回收总耗时100秒,其中垃圾收集花掉了1秒钟,那吞吐量就是 99%。

CMS 收集器:首先是老年代的垃圾收集器,在G1出现之前,CMS曾是最好的垃圾收集器,但是jdk的版本中没有哪一个的垃圾收集器默认是CMS的。他是基于标记清除算法的。基本上互联网公司很多都指定老年代的回收器是CMS。

G1:是目前最好的垃圾收集器,新生代和老年代都可以使用。用于替代

CMS,适用于较大的堆(大于4~6G),JDK9 之后默认使用 G1 收集器,其垃圾收集的时间能控制在 10毫秒以内。

1.23最好的垃圾回收算法是哪个?

分代整理算法:就是根据每一个区域的实际情况,选择合适的算法。

因为新生代中的对象都是朝生夕死,死亡率极高,所以我们使用复制算法,老年代根据垃圾收集器的不同,选择使用标记清理和标记整理算法。

说白了就是根据实际情况选择相应的算法

1.24详细介绍一下 CMS 垃圾回收器?

CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。

CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。


1.25CMS的一个工作方式是怎样的?

CMS 收集器是基于 标记-清除 算法实现的,它的运作过程相对复杂,分为四个步骤,包括:

1. 初始标记(CMS initial mark) 从根路径开始,快速的根据可达性分析对我们的对象进行快速的标记,这个动作是需要停止用户线程的。

2. 并发标记(CMS concurrent mark) 用户线程和垃圾回收线程一起工作,代码边执行边标记

3. 重新标记(CMS remark) 重新标记是需要再次停止用户线程的,对遗漏的对象进行最后一次标记。

4. 并发清除(CMS concurrent sweep)对之前标记的没有指针指向的对象进行精准的清理。

其中初始标记、重新标记着两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下 GC

Roots 能够直接关联到的对象,速度很快;

1.26G1垃圾收集器的工作方式?

和之前最大的不同是:G1也有伊甸区,幸存区,老年代以及大对象存储的空间,但是是逻辑意义上的不是物理上。它整个内存划分成了N多个等值的Region区域,每一个Region大小可以指定,必须是:1M,2M,4M,8M,16M,32M中的一种。也就是说必须是2的N次方,最大是32M。

G1 有三种垃圾收集模式,分别是 Young GC、Mixed GC、Full GC:

重点说一下Mixed GC,它是夹杂在Young GC和full GC之间的,我们新生代的对象有一部分需要进入老年代的时候,触发一次Mixed GC,它不仅清理新生代的内存,还清理一部分老年代的GC,因为老年代的存储也是在Region中的,不是一个整体,所以清理一部分是完全可以实现的。清理老年代是一个费时的操作,完全清理是很费时的,清理一部分够用即可,效率要比完全清理要高效。

1.27JVM默认的垃圾收集器是什么?

JDK1.7:Parallel Scavenge (新生代)+ Parallel Old (老年代)

JDK1.8:Parallel Scavenge (新生代)+ Parallel Old (老年代)

JDK1.9:G1

1.28java中的类加载器

1)根类加载器(bootstrap class loader) :它用来加载Java 的核心类,是用原生代码来实现的,并不继承自java.lang.ClassLoader(负责加载$JAVA_HOME中

jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

2)扩展类加载器(extensions class loader) :它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。

3)系统类加载器(system class loader) ︰被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类

#后端##面试##Java开发##Java找工作##大厂#
全部评论
希望楼主分享的这些我都能吃透吧
点赞 回复 分享
发布于 2022-09-03 10:01 陕西

相关推荐

10-15 09:13
已编辑
天津大学 soc前端设计
点赞 评论 收藏
分享
点赞 评论 收藏
分享
36 183 评论
分享
牛客网
牛客企业服务