lc1.1java基础

1.集合有哪些?数据结构?初始长度?扩容机制?哪些是线程安全的?hashmap的底层原理?

java集合分三种,List、Set、Map

  • List:有序,可重复的集合
  • List关注的是索引,拥有一系列和索引相关的方法,查询速度快。
  • 因为往list集合里插入或删除数据时,会伴随着后面数据的移动,所有插入删除数据速度慢。
  •  
  • ArrayList
  • ArrayList 初始化大小是 10
  • 扩容后的大小= 原始大小+原始大小/2 + 1
  • 原始大小是 10 ,扩容后的大小就是 10 + 5+1 = 16)
  • 线程不安全

   ②LinkedList

    linkedList 是一个双向链表,没有初始化大小,也没有扩容的机制,线程不安全

   ③Vector

   Vector是线程安全的,Vector的初始容量也是10,底层数据结构是数组结构,加载因子为1:

  即当 元素个数 超过 容量长度 时,进行扩容
  扩容增量:原容量的 1倍,如 Vector的容量为10,一次扩容后是容量为20

  • Set:无序、不能重复集合
  • 集合中的对象不按特定的方式排序,只是简单地把对象加入集合中。

①HashSet线程不安全,存取速度快
底层实现是一个HashMap(保存数据),实现Set接口,默认初始容量为16。
加载因子为0.75(3/4):即当 元素个数 超过 容量长度的0.75倍 时,进行扩容
扩容增量:原容量的 1 倍


如 Hash Set的容量为16,一次扩容后是容量为32

 

  • Map:用于键值对的存储 (键值对、键唯一、值不唯一)

Map集合中存储的是键值对,键不能重复,值可以重复。根据键得到值,对map集合遍历时先得到键的set集合,对set集合进行遍历,得到相应的值。

  • 注:通常List与Map最为常用

线程安全的map?

1.hashtable

我们常用的put,get,containsKey等方法都是同步的,所以它是线程安全的

2.synchronizedMap

它其实就是加了一个对象锁,每次操作hashmap都需要先获取这个对象锁,这个对象锁有加了synchronized修饰,

3、ConcurrentHashMap

这个是目前使用最多,而且也是最推荐的一个集合,

看源码其实是可以发现里面的线程安全是通过cas+synchronized+volatile来实现的

它的锁是分段锁,所以它的性能相对来说是比较好的。

ConcurrentHashMap是线程安全的,jdk1.8使用CAS和volatile实现。

支持并发,可以一边更新一边遍历

ConcurrentHashMap jdk1.8中,利用CAS+Synchronized来保证并发更新的安全,底层依然采用数组+链表+红黑树的存储结构。

Java中平时用的最多的map就是hashmap但是它却是线程不安全的。
那除了hashmap还有哪些常见的线程安全的map?
1.hashtable
Map<String,Object> hashtable=new Hashtable<String,Object>();

这是所有人最先想到的,那为什么它是线程安全的?那就看看它的源码,我们可以看出我们常用的put,get,containsKey等方法都是同步的,所以它是线程安全的

2.synchronizedMap
Map<String,Object> synchronizedMap= Collections.synchronizedMap(new Hashtable<String,Object>());
它其实就是加了一个对象锁,每次操作hashmap都需要先获取这个对象锁,这个对象锁有加了synchronized修饰,锁性能跟hashtable差不多。

3、ConcurrentHashMap
Map<String,Object> concurrentHashMap=new ConcurrentHashMap<String,Object>();

这个是目前使用最多,而且也是最推荐的一个集合,实现也是比较复杂的一个。我们看源码其实是可以发现里面的线程安全是通过cas+synchronized+volatile来实现的,其中也可看出它的锁是分段锁,所以它的性能相对来说是比较好的。整体实现还是比较复杂的。

数据结构?

set接口:哈希表存储

arraylist:数组

linkedlist:双向链表

hashmap:哈希表:数组+链表+红黑树

treemap:红黑树

就是研究数据的存储方式。

Set接口数据结构

哈希表存储结构

ArrayList

ArrayList数据结构是数组

LinkedList

LinkedList 数据结构是双向链表

HashMap :

jdk1.8中HashMap底层是哈希表数据结构,数组+链表+红黑树

TreeMap:

TreeMap底层是红黑树数据结构,

ConcurrentHashMap

ConcurrentHashMap是线程安全的,jdk1.8使用CAS和volatile实现。而jdk1.8以前通过锁分段技术、可重入锁实现。

支持并发,可以一边更新一边遍历

ConcurrentHashMap jdk1.8中的实现已经抛弃了Segment分段锁机制,利用CAS+Synchronized来保证并发更新的安全,底层依然采用数组+链表+红黑树的存储结构。

初始长度?

ArrayList、Vector默认初始容量为10

HashSet,HashMap,默认初始容量为16

扩容机制?

扩容点规则是,新增的时候发现容量不够用了,就去扩容
ArrayList

扩容后的大小= 原始大小+原始大小/2 + 1。(例如:原始大小是 10 ,扩容后的大小就是 10 + 5+1 = 16)

哪些是线程安全的?

线程安全和线程不安全的集合

Vector、HashTable、ConcurrentHashMap是线程安全的;

ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等都是线程不安全的。

值得注意的是:为了保证集合是线程安全的,相应的效率也比较低;线程不安全的集合效率相对会高一些。

vector、HashTable、ConcurrentHashMap是线程安全的

hashmap的底层原理?

①HashMap的工作原理

1.通过put()和get()方法储存和获取对象。

2.当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,然后找到bucket位置来储存值对象。

3.当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。

4.HashMap使用链表来解决碰撞问题,

当发生碰撞了,对象将会储存在链表的下一个节点中。

5.HashMap在每个链表节点中储存 键值对 对象。

hashmap遍历?

HashMap遍历的四种方法

方法1:使用For-Each迭代entries

这是最常见的方法,并在大多数情况下更可取的。当你在循环中需要使用Map的键和值时,就可以使用这个方法

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
    System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue())
}

注意:For-Each循环是Java5新引入的,所以只能在Java5以上的版本中使用。如果你遍历的map是null的话,For-Each循环会抛出NullPointerException异常,所以在遍历之前你应该判断是否为空引用。

方法2 使用For-Each迭代keys和values

如果你只需要用到map的keys或values时,你可以遍历KeySet或者values代替entrySet

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
 
//iterating over keys only
for (Integer key : map.keySet()) {
    System.out.println("Key = " + key);
}
 
//iterating over values only
for (Integer value : map.values()) {
    System.out.println("Value = " + value);
}

这个方法比entrySet迭代具有轻微的性能优势(大约快10%)并且代码更简洁

方法3 使用Iterator迭代

使用泛型

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
    Map.Entry<Integer, Integer> entry = entries.next();
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}

1.使用迭代器

iterator<Map.entry<String,object>> iterator=hashmap.entryset().iterator();

hashmap由entry来组成的,每一个entry就是一个键值对,

调用.entryset().iterator(),拿到一个迭代器,使用while循环进行遍历,

hashmap由entry来组成的,每一个entry就是一个键值对,

调用.entryset().iterator(),拿到一个迭代器,使用while循环进行遍历,

2.使用forEach()

3.增强for循环

hashmap结构?

数据结构

实际上HashMap是一个“链表散列”

我们知道在Java中最常用的两种结构是数组和模拟指针(引用),几乎所有的数据结构都可以利用这两种来组合实现,HashMap也是如此。实际上HashMap是一个“链表散列”,如下是它数据结构:

从上图我们可以看出HashMap底层实现还是数组,只是数组的每一项都是一条链。

HashMap :

jdk1.8中HashMap底层是哈希表数据结构,数组+链表+红黑树,HashMap是线程不安全的,允许使用null键和null值,

HashMap根据键的HashCode值存储数据,具有很快的访问速度。

HashMap存入的键值对在遍历时的顺序是随机的。

HashMap不支持并发

存储?

以数组方式进行存储的,将key-value键值对作为数组的一个元素进行存储。

Key-value都是Map.Entry中的属性。将key的值进行hash之后进行存储,即每一个key都是计算hash值,然后再存储。每一个hash值对应一个数组下标,

不同的key值可能具有相同的hash值,一个数组的某个位置出现两个相同的元素,hashmap采用链表的形式进行存储。

HashMap的存储结构

  1. HashMap底层是以数组方式进行存储的。将key-value键值对作为数组的一个元素进行存储。
  2. Key-value都是Map.Entry中的属性。其中将key的值进行hash之后进行存储,即每一个key都是计算hash值,然后再存储。每一个hash值对应一个数组下标,数组下标是根据hash值和数组长度计算得来的。
  3. 由于不同的key值可能具有相同的hash值,即一个数组的某个位置出现两个相同的元素,对于这种情况,hashmap采用链表的形式进行存储。

2.线程的创建?开启?状态?sleep和wait的区别?线程池?死锁?如何保证线程安全?

线程的创建?


无返回:
1.实现Runnable接口,重写run();
2.继承Thread类,重写run();


有返回:
1.实现Callable接口,重写call(),利用FutureTask包装Callable,并作为task传入Thread构造函数;
2.利用线程池
线程的启动:


启动线程的唯一方法就是通过Thread类的start()实例方法。

①.继承Thread类创建线程类

定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。

创建Thread子类的实例,即创建了线程对象。

调用线程对象的start()方法来启动该线程。

②. 通过Runnable接口创建线程类

定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

调用线程对象的start()方法来启动该线程。

③. 通过Callable和Future创建线程

创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

使用FutureTask对象作为Thread对象的target创建并启动新线程。

调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

调用线程对象的start()方法来启动该线程

状态?

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

创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态

就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。

运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。

阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,wait等方法都可以导致线程阻塞

死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪

sleep和wait的区别?

sleep():线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象

wait():同时释放对象的机锁,使得其他线程能够访问

1.所属对象不同:
sleep属于Thread类中的方法,wait属于Object类中的方法
2.对锁的控制权不同:
调用sleep方法的线程对象,虽然在指定时间内不会获得CPU的执行权,但是并没有释放对锁的控制权,即当休眠状态的线程获得锁的时候,其他线程不能重新获得锁,但是wait方法是释放锁的,使其他线程可以获得锁而获得资源的控制权;


 

1.sleep:此方法来自于Thread类,是thread类的静态方法,可以类名点调用

2.wait:此方法来自于Object类,必须由锁对象进行调用

线程池?

以提高服务器性能

一个线程池包括以下四个基本组成部分:

1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;

  2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
   3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
  4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

死锁?

死锁:是指两个或者两个以上的进程在执行过程中,处于相互等待的状态

怎么防止死锁?

加锁顺序(线程按照一定的顺序加锁)

加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)

如何保证线程安全?

使用synchronized同步代码块,或者用Lock锁;

1.使用多线程之间同步synchronized或使用锁(lock),将可能会发生数据冲突问题,只能让当前一个线程进行执行。

2.代码执行完成后释放锁,然后才能让其他线程进行执行。

这样的话,就可以解决线程不安全问题。

3.==和equals的区别?

1.对于基本数据类型:(byte,short,int,long,float,double,char,boolean),比较的是值

对于引用数据类型 ,比较地址 

2.equals如果重写,比较的是值,如果不重写,比较的是地址

4.对反射的理解?

反射:就是程序在运行过程中能够获取自身的信息,(即能获取java中反射类的字节码,)在java中只要给定类的名字就能够通过反射机制获取该类的所有信息;

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

对于任意一个类,知道这个类的所有属性方法;对于任意一个对象,都能够调用它的任意一个方法属性

获取class类的方式有哪些?

获取class类的方式:

1.Class class=Class.forName(“被获取类的全限定类名”)
2.Class class=类名.class
3.Class class=类对象(this).getClass()

第一种,使用 Class.forName 静态方法。

第二种,使用 .class 方法。

第三种,使用类对象的 getClass() 方法。

反射获取对象的实例

通过反射获取对象实例

//1.获取Class对象
Class stuClass = Class.forName(“fanshe.method.Student”);  1. class.forname()获取字节码对象
Student xxxx=stuClass .newInstance();  2.引用.new Instance()获取实例;

1..通过class的静态方法获取,

Class class=Class.forName(“被获取类的全限定类名”)

2..调用运行时类本身的.class属性

Class class=类名.class

3.通过运行时类的对象获取

Class class=类对象(this).getClass()
 

//如何获取Class的实例(4种)
    @Test
    public void test4() throws ClassNotFoundException{
        //1.调用运行时类本身的.class属性
        Class clazz1 = Person.class;
        System.out.println(clazz1.getName());

        Class clazz2 = String.class;
        System.out.println(clazz2.getName());

        //2.通过运行时类的对象获取
        Person p = new Person();
        Class clazz3 = p.getClass();
        System.out.println(clazz3.getName());

        //3.通过Class的静态方法获取.通过此方式,体会一下,反射的动态性。
        String className = "com.model.Person";


        Class clazz4 = Class.forName(className);
//        clazz4.newInstance();
        System.out.println(clazz4.getName());

        //4.(了解)通过类的加载器
        ClassLoader classLoader = this.getClass().getClassLoader();
        Class clazz5 = classLoader.loadClass(className);
        System.out.println(clazz5.getName());

如何用反射取私有属性Filed?

// 获得指定类的属性

clazz.getDeclaredField("name");

// 反射的对象在使用时应该取消 Java 语言访问检查。

field.setAccessible(true);取消 Java 语言访问检查

 

5. 常用的设计模式有哪些?在项目中哪里有用到?单例中懒汉饿汉优缺点?

常用设计模式:

单例模式;

代理模式

桥接模式

在项目中哪里有用到?

哪里用到:
spring容器中的bean对象默认使用到了单例模式;
2.spring中的aop用到了代理模式
3.jdbc连接数据库用到了桥接模式

 

单例中懒汉式饿汉式优缺点?

饿汉优缺点:

在加载类时就构建,

优点:线程安全,反应速度快

缺点:资源效率不高,

 

懒汉:在第一次被使用时被构建,在多线程加锁synchornized:

优点:资源利用率高
缺点:第一次加载不够快

restful设计风格:Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

 

6.jdk1.8的新特性有哪些?

1.原来的HashMap集合底层是数组加链表,1.8以后引入了红黑树,当碰撞的元素个数大于8时 & 总容量大于64,会有红黑树的引入 ;1.8之后链表新进元素加到末尾
2.Lambda表达式
3.Optional类:Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
4.Stream API:新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中
等…

 

Lambda表达式:Lambda允许把函数作为一个方法的参数(函数作为参数传递到方法中)。

语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。

Default关键字:通过使用default修饰方法,可以让我们在接口里面定义具体的方法实现 接口的实现类实现了这个接口之后,可以直接调用

方法引用:方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

Date Time API:加强对日期与时间的处理。

Optional类:Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。

 

1.引入了红黑树,

2.Lambda表达式

Lambda允许把函数作为一个方法的参数(函数作为参数传递到方法中)。

语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。

3.Optional类:解决空指针异常

4.Date Time API:加强对日期与时间的处理。

 

7.session的实现原理?session的生命周期?session如何存储数据?

session的实现原理:当客户端访问服务器时,服务器会创建session,同时将session_id传递给客户端浏览器,浏览器将这个session_id保存在内存中,服务器根据这个session_id,就能取得客户端的数据状态

 

session的生命周期?

在用户访问第一次访问服务器时创建,服务器会把长时间没有活动的Session从服务器内存中清除

 

session如何存储数据?

session如何储存数据:httpSession.setAttribute(键值对)

 

8.类加载机制?代码块的执行顺序?

类加载器

类加载分为三个步骤:加载、连接、初始化。

1.加载

类加载指的是将class文件读入内存,并为之创建一个java.lang.Class对象,即程序中使用任何类时,系统都会为之建立一个java.lang.Class对象,系统中所有的类都是java.lang.Class的实例。

类的加载由类加载器完成,JVM提供的类加载器叫做系统类加载器,此外还可以通过继承ClassLoader基类来自定义类加载器。

通常可以用如下几种方式加载类的二进制数据:

从本地文件系统加载class文件。

从JAR包中加载class文件,如JAR包的数据库启驱动类。

通过网络加载class文件。

把一个Java源文件动态编译并执行加载。

2.连接

连接阶段负责把类的二进制数据合并到JRE中,其又可分为如下三个阶段:

验证:确保加载的类信息符合JVM规范,无安全方面的问题。

准备:为类的静态Field分配内存,并设置初始值。

解析:将类的二进制数据中的符号引用替换成直接引用。

3.初始化

该阶段主要是对静态Field进行初始化,在Java类中对静态Field指定初始值有两种方式:

声明时即指定初始值,如static int a = 5;

使用静态代码块为静态Field指定初始值,如:static{    b = 5;    }

JVM初始化一个类包含如下几个步骤:

假如这个类还没有被加载和连接,则程序先加载并连接该类。

假如该类的直接父类还没有被初始化,则先初始化其直接父类。

假如类中有初始化语句,则系统依次执行这些初始化语句。

所以JVM总是最先初始化java.lang.Object类。

类初始化的时机(对类进行主动引用时):

创建类的实例时(new、反射、反序列化)。

调用某个类的静态方法时。

使用某个类或接口的静态Field或对该Field赋值时。

使用反射来强制创建某个类或接口对应的java.lang.Class对象,如Class.forName("Person")

初始化某个类的子类时,此时该子类的所有父类都会被初始化。

直接使用java.exe运行某个主类时。

代码块的执行顺序?

代码块的执行顺序:
1.在主类中定义的静态代码块会优先于主方法(main)执行;
2.静态块优先于构造块执行;
2.1.无论产生多少实例化对象,静态块都只执行一次;
3.构造块优先于构造方法执行,每产生一个新对象就调用一次构造块,构造块可以进行简单的逻辑操作;
4.有继承关系的话,在main方法里new子类,则先调用父类,再调用子类;

静态代码块 >>> 构造代码块 >>>> 构造方法

如果有继承的话

父类静态代码块>>>子类的静态代码块>>>父类的构造代码块>>>父类的构造方法>>>子类的构造代码块>>>子类的构造方法

类加载分为三个步骤:加载、连接、初始化。

2.连接
验证:准备:解析:

类装载流程

 

代码块的执行顺序?

静态代码块 >>> 构造代码块 >>>> 构造方法

 

 

9.cookie和session的区别?

1.cookie保存在客户端,session保存在服务器端,
2.cookie不安全,session安全

3.cookie储存为String数据,session储存为Object类型数据
4.cookie可以长时间保存一些数据,session随着会话结束,销毁数据;

 

10.java中字符串的方法有哪些?string stringbuild stringbuffer的区别?

1.equals():比较两个字符串是否相等
2.toString():转换成String类型
3.String.valueOf():转换成String类型
4.indexOf():指出 String 对象内子字符串的开始位置
5.charAt():返回指定索引处char值

 

string stringbuild stringbuffer的区别?

String 不可变的对象,StringBuffer 是线程安全的,StringBuilder 是非线程安全的,

StringBuilder 的性能却高于 StringBuffer,在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

 

11.jvm调优和垃圾回收机制?

jvm调优:

JVM调优目标:使用较小的内存占用来获得较高的吞吐量或者较低的延迟

程序在上线前的测试或运行中有时会出现一些大大小小的JVM问题,比如cpu load过高、请求延迟、tps降低等,甚至出现内存泄漏(每次垃圾收集使用的时间越来越长,垃圾收集频率越来越高,每次垃圾收集清理掉的垃圾数据越来越少)、内存溢出导致系统崩溃,因此需要对JVM进行调优,使得程序在正常运行的前提下,获得更高的用户体验和运行效率。

这里有几个比较重要的指标:

内存占用:程序正常运行需要的内存大小。

延迟:由于垃圾收集而引起的程序停顿时间。

吞吐量:用户程序运行时间占用户程序和垃圾收集占用总时间的比值。

当然,和CAP原则一样,同时满足一个程序内存占用小、延迟低、高吞吐量是不可能的,程序的目标不同,调优时所考虑的方向也不同,在调优之前,必须要结合实际场景,有明确的的优化目标,找到性能瓶颈,对瓶颈有针对性的优化,最后进行测试,通过各种监控工具确认调优后的结果是否符合目标。

2、JVM调优工具

调优可以依赖、参考的数据有系统运行日志堆栈错误信息、gc日志、线程快照、堆转储快照等。

jvm调优:
1.使用JDK提供的内存查看工具,如JConsole和Java VisualVM
2.控制堆内存各个部分所占的比例
3.采用合适的垃圾收集器

 


1.使用JDK提供的内存查看工具,
2.控制堆内存各个部分所占的比例
3.采用合适的垃圾收集器


垃圾回收机制:

在Java中存在着四种垃圾回收算法,标记清除算法、复制算法、标记整理算法以及分代回收算法。

 

标记清除算法:

标记出所有需要被回收的对象,回收被标记的对象所占用的空间,然后再把使用的空间一次清理掉

 

复制算法

将内存分为大小相同的两块,每次使用其中的一块,当第一块的内存使用完后,将还存活的对象复制到另一块去,

 

标记整理算法

在完成标记之后,将存活对象都向一端移动,然后清理掉端边界以外的内存。

 

分代收集算法

将 java 堆分为新生代和老年代

 

(1)新生代

采用 复制 回收算法,GC 时把少量的存活对象复制过去即可。

新生代也划分了三个部分比例:Eden:S1:S2=8:1:1

Eden:形容有很多新生对象在里面创建

S1和S2:为幸存者,经历 GC 后仍然存活下来的对象

工作原理如下:

Eden对外提供堆内存,当 Eden区快要满了,触发垃圾回收机制,把存活对象放入 Survivor A 区,清空 Eden 区,Eden区被清空后,继续对外提供堆内存;

当 Eden 区再次被填满,对 Eden区和 Survivor A 区同时进行垃圾回收,把存活对象放入 Survivor B区,

同时清空 Eden 区和Survivor A 区;当某个 Survivor区被填满,把多余对象放到Old 区;

当 Old 区也被填满时,进行 下一阶段的垃圾回收

 

(2)老年代

老年代的特点是:存活对象多、垃圾少

通过少量地移动对象就能清理垃圾,标记整理的回收机制

标记整理算法,

 

什么是垃圾?

所有不再存活的对象。

判断是否存活有两种方法

引用计数法和可达性分析

 

引用计数法:

为每一个创建的对象分配一个引用计数器,用来存储该对象被引用的个数。当该个数为零,意味着没有人再使用这个对象,可以认为“对象死亡”。但是,这种方案存在严重的问题,就是无法检测“循环引用”:当两个对象互相引用,即时它俩都不被外界任何东西引用,它俩的计数都不为零,因此永远不会被回收。而实际上对于开发者而言,这两个对象已经完全没有用处了。

因此,Java 里没有采用这样的方案来判定对象的“存活性”。

 

为每一个创建的对象分配一个引用计数器,用来存储该对象被引用的个数,

当该个数为零,意味着没有人再使用这个对象,可以认为“对象死亡”。

 

可达性分析

把所有引用的对象想象成一棵树,从树的根结点 GC Roots 出发,持续遍历,   找出所有连接的树枝对象,这些对象则被称为“可达”对象,或称“存活”对象。

其余的对象,“垃圾”

 

12.java中锁的种类和基本原理?

锁种类
1.乐观锁/悲观锁
2.独享锁/共享锁
3.互斥锁/读写锁
4.可重入锁

基本原理

乐观锁:

每次去拿数据的时候都认为别人不会修改,所以不会上锁,在更新的时候会判断一下在此期间别人有没有去更新这个数据,使用版本号,

 

悲观锁

每次去拿数据的时候都认为别人会修改自己的数据,每次在拿数据的时候都会上锁,别人想拿这个数据就会阻塞直到它拿到锁,synchronized关键字的实现就是悲观锁

 

独享锁

该锁一次只能被一个线程所持有,Lock就是独享锁,Synchronized也是独享锁,

 

互斥锁

互斥锁在Java中的具体实现就是ReentrantLock

 

读写锁:

ReadWriteLock

 

可重入锁

可重入锁又名递归锁

在同一个线程在外层方法  获取锁的时候,在进入内层方法 会自动获取锁,

ReetrantLock和Synchronized都是可重入锁。

 

13.collection和collections的区别?

ollection 是一个集合接口

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

 

14.java如何跳出循环?

1.continue:跳出本次循环,继续下一次循环
2.break:跳出本层循环,循环体的结束。
3.return:跳出这个方法。 

15.排序有哪些?原理是什么?
1冒泡排序:

如果是将元素从小到大排序,每次就冒出一个最大的元素,放在数组的最后面。冒泡排序是通过交换相邻元素来实现的

快速排序算法:

(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。 
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。
对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

选择排序:假如我们要从小到大排序,假设第一个元素arr[0]是最小的元素,然后将arr[0]与之后的所有元素依次比较,谁小谁就到前面来,遍历完以后整个数组中最小的元素就在arr[0]这个位置上了。再从arr[1]开始,假设arr[1]是最小的元素,arr[1]与之后所有的元素依次比较,谁小就到arr[1]这个位置上来。依次类推,当arr[arr.length-2]这个位置元素确定时,最后一个元素就在最后一个位置了,排序结束了

16.什么是堆栈?

堆栈就是只能在一端插入和删除数据的链表,最后一个添加的数据第一个被删除。后进先出链表或是先进后出链表

什么是内存溢出?

内存溢出:应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。此时程序就运行不了,系统会提示内存溢出

有垃圾回收机制为什么还会出现内存溢出的情况?

内存资源是有限的,垃圾回收只回收垃圾,对于你的程序运行有用的对象不会被回收。内存溢出分两种情况一种是栈溢出,比如调用了一个无限递归。还有一种是堆溢出,即new出来的对象没有及时销毁,比如一直new。

 

17.内存模型的理解?

为了解决多线程通信,变量共享的并发问题并且保障跨平台行,Java定义了自己的内存模型.内存模型描述了程序中各个变量之间的关系,以及操作系统将变量存储到内存以及从内存中取出变量这样的细节.此处的变量不包括局部变量与方法参数,后者是线程私有的,不会被共享.所以说JMM帮助屏蔽了操作系统底层的细节,保障了程序的正确性.

 

18.泛型的理解?

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?

顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),

然后在使用/调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,

操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

擦除是将泛型类型以其父类代替,如String 变成了Object等。其实在使用的时候还是进行带强制类型的转化,只不过这是比较安全的转换,因为在编译阶段已经确保了数据的一致性

用于指定添加到集合的类型
好处:
1.类型参数化,通用、可以像方法一样的参数一样传递,非常实用。
2.安全、编译时检查类型是否正确,降低类型强转报错率。
3.提高代码重用率。

 

被指定为一个参数,可以用在类、接口和方法中,被称为泛型类、泛型接口、泛型方法。

 

19.java的基本类型有哪些,int占几个字节?byte占几个字节?

byte、short、int、long、float、double、char、boolean;int 4字节;byte 1字节

20.常见的异常类有哪些?处理方式?

空指针异常:NullPointerException;

下标越界异常:ArrayIndexOutOfBoundsException;

操作数据库异常:SQLException;

输入输出异常:IOException;

类找不到异常:

算数异常:

异常处理的方式有哪些?

Java异常机制用到的几个关键字:

try、catch、finally、throw、throws。

try --将要被监听的代码,放在try语句块之内

catch -- 用于捕获异常

finally -- finally语句块总是会被执行。

throw -- 用于抛出异常。

throws --如果在主方法上使用了throws抛出,就表示在主方法里面可以不用强制性进行异常处理,

 

21.枚举的了解?

枚举enum的构造函数只能是private类型的

是单例模式;

有一个values方法

来获取 枚举实例对象 数组;

 

枚举是列出某些有穷序列集的所有成员的程序,一个类里定义几个静态变量,每个变量都是这个类的实例。

22.final、finally、finalize关键字的区别?volatile关键字的了解?

final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。

finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。

finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System的gc()方法的时候,由垃圾回收器调用finalize(),回收垃圾。

 

volatile关键字的了解?

 

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2.禁止进行指令重排序。

 

指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。

 

volatile关键字的作用:
内存可见性
禁止指令重排
volatile不会让线程阻塞,响应速度比synchronized高

 

23.在一个list中存放的String类型的字符串,如何实现把其中所有带“王”的字符串从list中去除?

1.遍历集合for(String s:list)
2.判断遍历的对象中是否含有“王”if(s.indexOf(“王”)!=-1)
3.执行删除list.remove(s)

 

24.String a=“123”;

String b="123";

a+=b;生成一个对象?

3

 

25.如何序列化和反序列化?序列化的目的?

序列化:将Java对象转换为字节序列的过程

反序列化:将字节序列转换为Java对象的过程。

实现序列化:实现Serializable、Externalizable接口

实现反序列化:用ObjectInputStream(对象输入流)类包含一个readObject()方法用来“反序列化”一个对象

 

序列化的目的?

采用Java序列化与反序列化技术,一是可以实现数据的持久化;二是可以对象数据的远程通信

 

 

 

猜你喜欢

转载自blog.csdn.net/abu1216/article/details/111995167