Java50道常见面试题,不容错过,一键收藏吧!!!

1. int 和 Integer的区别有哪些?运用场景?

1、int属于基本数据类型,Integer属于int对应的包装类。

2、既然是类,就需要实例化才可以使用,而基本数据变量不需要。

3、Integer的默认值是null,而int的默认值是0

1、为什么java会保留基本数据类型?

对于java来说,常说“一切皆对象”,那为啥java还有基本数据类型,对此需要明白,基本数据类型和对象类型的区别

1、基本数据类型

基本数据类型存在的是:值,基本数据类型在内存中存放的位置是栈。(声明在方法中的局部变量,基本类型作为成员变量,也存放在堆中)。

例如int a = 1 ;long a = 12L的形式定义的称为自动变量,自动变量存放的是字面值,没有类的存在。

2、对象类型

对象类型是对对象的引用,对象存放在堆中。

3、堆和栈的区别

那数据存放在堆中和栈中有什么区别呢?

栈:

1)栈的存取速度比堆快,仅次于直接位于CPU的寄存器。

2)栈中的数据的大小和生存周期是确定的。

3)栈中的数据可以共享。

堆:

1)堆可以动态的分配内存大小,生存期也不必告诉编译器。

2)堆在运行时动态分配内存,存取速度慢。

综上所述,可以简单的理解为,为了高效,可以把一些数值小,简单的变量存放在栈中。

2、有了基本数据类型为什么会出现Integer/包装类?

将基本数据类型封装成对象的好处是:

1、在对象中可以定义更多的功能方法操作该数据。例如:基本数据类型和字符串直接的转换。

2、编码过程中只接收对象的情况,例如List中只存入对象,不能存入基本数据类型;泛型不支持基本数据类型。

为什么在项目里面用Integer?

1,因为Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况,在JSP开发中,Integer的默认为null,所以用el表达式在文本框中显示时,值为空白字符串,而int默认的默认值为0,所以用el表达式在文本框中显示时,结果为0,所以,int不适合作为web层的表单数据的类型。

2,在Hibernate中,如果将OID定义为Integer类型,那么Hibernate就可以根据其值是否为null而判断一个对象是否是临时的,如果将OID定义为了int类型,还需要在hbm映射文件中设置其unsaved-value属性为0。

3,Integer提供了多个与整数相关的操作方法,例如,将一个字符串转换成整数,Integer中还定义了表示整数的最大值和最小值的常量。

============================================================================

2. string 有哪些方法?replace和replcaeAll的区别?stringbuilder stringbuffer的区别?

length():求字符串的长度

indexOf():求某个字符在字符串中的位置

charAt():求一个字符串中某个位置的值

equals():比较两个字符串是否相同

replace():将字符串中的某些字符用别的字符替换掉。形如replace(“abc”,”ddd”);字符串中的abc将会被ddd替换掉。

split():根据给定正则表达式的匹配拆分此字符串。形如 String s = “The time is going quickly!”; str1=s.split(" ");

substring():输出一个新的字符串,它是此字符串中的子串,形如substring(3,7);它将字符串中的第四个第五个第六个输出。

trim():将字符串开头的空白(空格)和尾部的空白去掉。

format():使用指定的语言环境、格式字符串和参数返回一个格式化字符串。

toLowerCase():将字符串中所有的大写改变成小写

toUpperCase():将字符串中所有的小写改变为大写

StringBuffer 和StringBuilder都是操作字符串的,且都可变,因为他们底层的char[]数组没有被final修饰(final知识点),它们都是继承的同一个父类AbstractStringBuilder,但是StringBuilder是线程不安全的,因为它的append()方法没有使用synchronized线程同步锁,有线程安全问题,但是它的效率就要快点,所以在单线程时考虑用StringBuilder,多线程就考虑用StringBuffer;

3. static关键字运用在哪些地方?各自的含义?final 关键字运用在哪些地方?各自含义?

(1)静态的意思。可以修饰成员变量和成员方法。

(2)静态的特点:

​ A:随着类的加载而加载

​ B:优先与对象存在

​ C:被类的所有对象共享

​ 这其实也是我们判断该不该使用静态的依据。

​ 举例:饮水机和水杯的问题思考

​ D:可以通过类名调用

​ 既可以通过对象名调用,也可以通过类名调用,建议通过类名调用。

(3)静态的内存图

​ 静态的内容在方法区的静态区

(4)静态的注意事项;

​ A:在静态方法中没有this对象

​ 原因:静态是随着类的加载而加载,this是随着对象的创建而存在。

​ 静态比对象先存在。

​ B:静态只能访问静态(代码测试过)

(5)静态变量和成员变量的区别

​ A:所属不同

​ 静态变量:属于类,类变量

​ 成员变量:属于对象,对象变量,实例变量

​ B:内存位置不同

​ 静态变量:方法区的静态区

​ 成员变量:堆内存

​ C:生命周期不同

​ 静态变量:静态变量是随着类的加载而加载,随着类的消失而消失

​ 成员变量:成员变量是随着对象的创建而存在,随着对象的消失而消失

​ D:调用不同

​ 静态变量:可以通过对象名调用,也可以通过类名调用

​ 成员变量:只能通过对象名调用

final关键字:

\1. 用来修饰数据,包括成员变量和局部变量,该变量只能被赋值一次且它的值无法被改变。对于成员变量来讲,我们必须在声明时或者构造方法中对它赋值;

\2. 用来修饰方法参数,表示在变量的生存期中它的值不能被改变;

\3. 修饰方法,表示该方法无法被重写;

\4. 修饰类,表示该类无法被继承

4. List和Set的区别?ArrayList 和Linked List的区别?Hash Set和TreeSet的区别?

– List和set区别:

相同:list和set都是collection下的单value集合;

不同:list有序,可重复

set无序,不可重复

– ArrayList 和Linked List的区别:

****相同点****:

都是List接口的实现类,都可以存放任意类型任意多个数据,都是线程不安全的,都是有序的(添加元素的顺序和输出的顺序一致)

****不同点****:

①:ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。

②:对于随机访问get和set,ArrayList觉得优于LinkedList,因为能可以根据下标索引找到具体的值,数组它需要保证连续性,需要移动元素,增删的效率比较低;

③:对于新增和删除操作add和remove,LinedList比较占优势,因为元素的存储可以不连续,底层基于链表实现,添加/删除元素时,上一个节点只需要指向下一个节点即可,查询的效率慢,没有下标去找元素,需要遍历整个链表,速度慢;

– HashSet和TreeSet区别:

HashSet:

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

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

HashSet底层是采用HashMap实现的

HashSet底层是哈希表实现的

TreeSet:

Treeset中的数据是排好序的,不允许放入null值。

TreeSet是通过TreeMap实现的,只不过Set用的只是Map的key。

TreeSet的底层实现是采用二叉树(红-黑树)的数据结构。

5. HashMap 的数据结构是怎样的?是如何解决Hash碰撞的?HashMap是线程安全的吗?如何实现线程安全的HashMap?

jdk1.7以前是数组+链表;

jdk1.8 以后是数组+链表+红黑树;

(红黑树是为了提高查询速度了,因为链表过长,查询速度会很慢,红黑树能提高查询速度 ,当链表长度达到 8 编程 红黑树,当链表长度 小于 6 编程链表

链表为了解决Hash冲突 , 不同的key进行 hash计算可能hash值一样,那么计算出来的存储索引位置也一样,这叫hash冲突,HashMap使用链表的方式来解决Hash冲突,就是在当前索引位置的元素上向下拖一个链表

. 1HashMap 不是线程安全的。

2.在put或resize时会发生,后一个线程操作的数据会把前一个线程操作的数据覆盖,或者数据丢失的情况。

使用Hashtable类或者是ConcurrentHashMap类进行替换.;

使用Collections.synchronizedMap(new Hashtable())进行替换;

6. Object有哪些方法?

clone()

clone()函数的用途是用来另存一个当前存在的对象。

hashCode()和equale()

· equale()用于确认两个对象是否相同。

· hashCode()用于获取对象的哈希值,这个值的作用是检索,具体的作用可以参考这里

· 哈希值相同的对象不一定equale()

· equale()返回true的两个对象一定相同。

toString()和getClass()

toString()返回一个String对象,用来标识自己

getClass()返回一个Class对象,如果打印出来会发现结果是如下格式

classpackage.name.xxx

· 1

因为返回的是一个class对象,后面可以跟class类的方法。用的是谁的构造函数,那么getClass返回的就是谁的类型。

getClass()经常用于java反射机制

wait(),wait(long),wait(long,int),notify(),notifyAll()

这几个函数体现的是Java的多线程机制

在使用的时候要求在synchronize语句中使用

wait()用于让当前线程失去操作权限,当前线程进入等待序列

notify()用于随机通知一个持有对象的锁的线程获取操作权限

notifyAll()用于通知所有持有对象的锁的线程获取操作权限

wait(long) 和wait(long,int)用于设定下一次获取锁的距离当前释放锁的时间间隔

finalize()

原文链接:https://blog.csdn.net/u014590757/article/details/80262163

7. Java 多线程中 线程越多 任务执行越快吗?为什么?

首先,创建进程比创建线程要多占用系统资源,系统资源不足往往会引起系统性能的下降,导致任务完成的比较慢。其次,由于多个进程要操作同一个数据集合,必然会因为数据争用导致进程状态改变,同多个线程状态改变相比,进程切换要使用更多的CPU时间。最后,使用单进程方式,由于进程少,每个进程又可以较多的获得CPU时间片,从而能够很大的改善进程的性能。由此可见,并不是使用多进程处理数据就一定比使用多个线程的单进程快。

8. Java 造成多线程不安全的原因是什么?可以通过哪些方式来达到多线程安全?

1,多个线程在操作共享数据。

2,有多条语句对共享数据进行运算。

原因:这多条语句,在某一个时刻被一个线程执行时,还没有执行完,就被其他线程执行了。

解决办法:

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

1.把竞争访问的资源标识为private;

2.同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。

9. CAS synchronized voliate 各自的原理和区别 及各自在Java中的运用场景?

1)volatile本质是告诉JVM当前变量在寄存器中的值是不确定的,需要从主存中读取。synchronized则是锁定当前变量,只有当前线程可以访问该变量,其它线程被阻塞。

2)volatile仅能使用在变量级别,synchronized则可以使用在变量、方法。

3)volatile仅能实现变量修改的可见性,而synchronized则可以保证变量修改的可见性和原子性。《Java编程思想》上说,定义long或double时,如果使用volatile关键字(简单的赋值与返回操作),就会获得原子性。(常规状态下,这两个变量由于其长度,其操作不是原子的)

4)volatile不会造成线程阻塞,synchronized会造成线程阻塞。

5)使用volatile而不是synchronized的唯一安全情况是类中只有一个可变的域。

简明扼要来说就是一下三点:

(1)、volatile只能作用于变量,使用范围较小。synchronized可以用在变量、方法、类、同步代码块等,使用范围比较广。
(2)、volatile只能保证可见性和有序性,不能保证原子性。而可见性、有序性、原子性synchronized都可以包证。
(3)、volatile不会造成线程阻塞。synchronized可能会造成线程阻塞

CAS的缺陷

乐观锁只能保证一个共享变量的原子操作。如上例子,自旋过程中只能保证value变量的原子性,这时如果多一个或几个变量,乐观锁将变得力不从心,但互斥锁能轻易解决,不管对象数量多少及对象颗粒度大小。

长时间自旋可能导致开销大。假如CAS长时间不成功而一直自旋,会给CPU带来很大的开销。

synchronized与CAS的区别

对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能

对于资源竞争严重的情况,CAS自旋的概率会比较大(比如getAndAddInt方法中的do-while循环),从而浪费更多的CPU资源,效率低于synchronized。

10. CAS 如何解决ABA问题?

ABA问题。CAS的核心思想是通过比对内存值与预期值是否一样而判断内存值是否被改过,但这个判断逻辑不严谨,假如内存值原来是A,后来被一条线程改为B,最后又被改成了A,则CAS认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种情况对依赖过程值的情景的运算结果影响很大。解决的思路是引入版本号,每次变量更新都把版本号加一。

11. synchronized 和Java中的Lock接口有什么区别?

来源:
lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;

异常是否释放锁:
synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)

是否响应中断
lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;

是否知道获取锁
Lock可以通过trylock来知道有没有获取锁,而synchronized不能;

Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度,

性能来说:

synchronized是托管给JVM执行的,
而lock是java写的控制锁的代码。

12. wait sleep 方法的区别?

· 这两个方法来自不同的类分别是Thread和Object

· 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法(锁代码块和方法锁)。

· wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)

· sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

· sleep方法属于Thread类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。但在sleep的过程中过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。

· 注意sleep()方法是一个静态方法,也就是说他只对当前对象有效,通过t.sleep()让t对象进入sleep,这样的做法是错误的,它只会是使当前线程被sleep 而不是t线程

· wait属于Object的成员方法,一旦一个对象调用了wait方法,必须要采用notify()和notifyAll()方法唤醒该进程;如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()方法的对象。wait()方法也同样会在wait的过程中有可能被其他对象调用interrupt()方法而产生

如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。

需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到wait()/sleep()/join()后,就会立刻抛出InterruptedException。

waite()和notify()因为会对对象的“锁标志”进行操作,所以它们必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronizedblock中进行调用,虽然能编译通过,但在运行时会发生illegalMonitorStateException的异常。

说一下为什么使用wait()方法时,一般是需要while循环而不是if?

while会一直执行循环,直到条件满足,执行条件才会继续往下执行。if只会执行一次判断条件,不满足就会等待。这样就会出现问题。

我们知道用notify() 和notifyAll()可以唤醒线程,一般我们常用的是notifyAll(),因为notify(),只会随机唤醒一个睡眠线程,并不一定是我们想要唤醒的线程。如果使用的是notifyAll(),唤醒所有的线程,那你怎么知道他想唤醒的是某个正在等待的wait()线程呢,如果用while()方法,就会再次判断条件是不是成立,满足执行条件了,就会接着执行,而if会直接唤醒wait()方法,继续往下执行,根本不管这个notifyAll()是不是想唤醒的是自己还是别人,可能此时if的条件根本没成立。

13. Java 线程池各参数的意义?Java 提供了哪几种默认的线程池实现和各自的功能?

corePollSize:核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建线程去执行任务。

maximumPoolSize:最大线程数。表明线程中最多能够创建的线程数量。

keepAliveTime:空闲的线程保留的时间。

TimeUnit:空闲线程的保留时间单位。

BlockingQueue:阻塞队列,存储等待执行的任务。参数有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue可选。

ThreadFactory:线程工厂,用来创建线程

RejectedExecutionHandler:队列已满,而且任务量大于最大线程的异常处理策略。

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

· 单线程化线程池(newSingleThreadExecutor);

submit():提交任务。

shutdown():方法用来关闭线程池,拒绝新任务。

应用场景:串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行

· 可控最大并发数线程池(newFixedThreadPool);

· 可回收缓存线程池(newCachedThreadPool);

· 支持定时与周期性任务的线程池(newScheduledThreadPool)。

14. Java 中 线程A B C 三个线程如何让其执行顺序为C-B-A

· [1] 使用线程的join方法

· [2] 使用主线程的join方法

· [3] 使用线程的wait方法

· [4] 使用线程的线程池方法

· [5] 使用线程的Condition(条件变量)方法

· [6] 使用线程的CountDownLatch(倒计数)方法

· [7] 使用线程的CyclicBarrier(回环栅栏)方法

· [8] 使用线程的Semaphore(信号量)方法

@Slf4j
public class TestCountDownLatch {
    private static CountDownLatch latch = new CountDownLatch(1);
    private static CountDownLatch latch2 = new CountDownLatch(1);
    public static void main(String[] args) {

        Runnable r=()->{
            try {
                latch2.await();
                log.info("running");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread t1 = new Thread(r, "A");
        
        Runnable r2=()->{
            try {
                latch.await();
                log.info("r2 running");
                latch2.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread t2 = new Thread(r2, "B");

        Runnable r3=()->{
            log.info("r3 running");
            latch.countDown();
        };
        Thread t3 = new Thread(r3, "C");

        t3.start();
        t1.start();
        t2.start();

    }
}

CyclicBarrier(回环栅栏)方法:

@Slf4j
public class TestCyclicBarrier {
    static CyclicBarrier barrier1 = new CyclicBarrier(2);
    static CyclicBarrier barrier2 = new CyclicBarrier(2);
    public static void main(String[] args) {

        Runnable r=()->{
            try {
                //放开栅栏2
                barrier2.await();
                log.info("running");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        };
        Thread t1 = new Thread(r, "A");

        Runnable r2=()->{
            try {
                //放开栅栏1
                barrier1.await();
                log.info("r2 running");
                //放开栅栏2
                barrier2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        };
        Thread t2 = new Thread(r2, "B");

        Runnable r3=()->{
            try {
                log.info("r3 running");
                //放开栅栏1
                barrier1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        };
        Thread t3 = new Thread(r3, "C");

        t3.start();
        t1.start();
        t2.start();

    }
}

执行结果:

00:13:14.520 [C] INFO cn.hb.test.TestCyclicBarrier - r3 running
00:13:14.520 [B] INFO cn.hb.test.TestCyclicBarrier - r2 running
00:13:14.520 [A] INFO cn.hb.test.TestCyclicBarrier - running

参考网址:https://www.cnblogs.com/myseries/p/11575757.html

15. CountDownLatch 和 CyclicBarrier的区别

1、CountDownLatch简单的说就是一个线程等待,直到他所等待的其他线程都执行完成并且调用countDown()方法发出通知后,当前线程才可以继续执行。

2、cyclicBarrier是所有线程都进行等待,直到所有线程都准备好进入await()方法之后,所有线程同时开始执行!

3、CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。

4,、CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。如果被中断返回true,否则返回false

CountDownLatch的使用场景:

在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。 这个时候就可以使用CountDownLatch。CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。

CyclicBarrier可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。

16.JVM 内存模型是怎样的?

在这里插入图片描述

从图上可以看到,大致分为以下组件:

①. 类加载器子系统

②. 运行时数据区 : 方法区 堆 虚拟机栈 本地方法栈 程序计数器

③. 执行引擎

④. 本地方法库

17.堆内存的分代模型是怎样的?Eden和Spurvior的比例?

把堆内存分为新生代和老年代,新生代又分为Eden区、From Survivor和To Survivor。一般新生代中的对象基本上都是朝生夕灭的,每次只有少量对象存活,因此新生代采用复制算法,只需要复制那些少量存活的对象就可以完成垃圾收集;老年代中的对象存活率较高,就采用标记-清除和标记-整理算法来进行回收。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kIrDo8vp-1634221213127)(file:///C:\Users\ASUS\AppData\Local\Temp\ksohtml\wps673C.tmp.jpg)]

在这些区域的垃圾回收大概有如下几种情况:

新生代使用时minor gc, 老年代使用的full gc 同时会触发一minor gc

l 大多数情况下,新的对象都分配在Eden区,当Eden区没有空间进行分配时,将进行一次Minor GC,清理Eden区中的无用对象。清理后,Eden和From Survivor中的存活对象如果小于To Survivor的可用空间则进入To Survivor,否则直接进入老年代);Eden和From Survivor中还存活且能够进入To Survivor的对象年龄增加1岁(虚拟机为每个对象定义了一个年龄计数器,每执行一次Minor GC年龄加1),当存活对象的年龄到达一定程度(默认15岁)后进入老年代,可以通过-XX:MaxTenuringThreshold来设置年龄的值。

解释:Eden空间不够,执行MinoGC, Eden和S0中存活的对象进入S1,对象年龄计数+1 ,

如果S1空间不够,直接进 入老年代 ,否则对象默认经过 15次MinorCG后才会进入老年代

l 当进行了Minor GC后,Eden还不足以为新对象分配空间(那这个新对象肯定很大),新对象直接进入老年代。

l 多个对象总占To Survivor空间一半以上且年龄相等,该对象直接进入老年代,比如Survivor空间是10M,有几个年龄为4的对象占用总空间已经超过5M,则年龄大于等于4的对象都直接进入老年代,不需要等到MaxTenuringThreshold指定的岁数。

l 在进行Minor GC之前,会判断老年代最大连续可用空间是否大于新生代所有对象总空间,如果大于,说明Minor GC是安全的,否则会判断是否允许担保失败,如果允许,判断老年代最大连续可用空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则执行Minor GC,否则执行Full GC。

解释:Minor GC之前,如果预测老年代内存不够,就进行Full GC老年代,否则就Minor GC新生代

l 当在java代码里直接调用System.gc()时,会建议JVM进行Full GC,但一般情况下都会触发Full GC,一般不建议使用,尽量让虚拟机自己管理GC的策略。

l 永久代(方法区)中用于存放类信息,jdk1.6及之前的版本永久代中还存储常量、静态变量等,当永久代的空间不足时,也会触发Full GC,如果经过Full GC还无法满足永久代存放新数据的需求,就会抛出永久代的内存溢出异常。

l 大对象(需要大量连续内存的对象)例如很长的数组,会直接进入老年代,如果老年代没有足够的连续大空间来存放,则会进行Full GC。

18. 出现OOM的原因会有哪些?

1、Java heap space

当堆内存(Heap Space)没有足够空间存放新创建的对象时,就会抛出java.lang.OutOfMemoryError:Javaheap space错误(根据实际生产经验,可以对程序日志中的OutOfMemoryError配置关键字告警,一经发现,立即处理)。

*原因分析*

Javaheap space错误产生的常见原因可以分为以下几类:

1、请求创建一个超大对象,通常是一个大数组。

2、超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值。

3、过度使用终结器(Finalizer),该对象没有立即被GC。

4、内存泄漏(Memory Leak),大量对象引用没有释放,JVM无法对其自动回收,常见于使用了File等资源没有回收。

2、GC overhead limit exceeded

当Java进程花费98%以上的时间执行GC,但只恢复了不到2%的内存,且该动作连续重复了5次,就会抛出java.lang.OutOfMemoryError:GC overhead limit exceeded错误。简单地说,就是应用程序已经基本耗尽了所有可用内存,GC也无法回收。

3、Permgen space

该错误表示永久代(Permanent Generation)已用满,通常是因为加载的class数目太多或体积太大。

*原因分析*

永久代存储对象主要包括以下几类:

1、加载/缓存到内存中的class定义,包括类的名称,字段,方法和字节码;

2、常量池;

3、对象数组/类型数组所关联的class;

4、JIT编译器优化后的class信息。

PermGen的使用量与加载到内存的class的数量/大小正相关。

4、Metaspace

JDK 1.8使用Metaspace替换了永久代(Permanent Generation),该错误表示Metaspace已被用满,通常是因为加载的class数目太多或体积太大。

需要特别注意的是调整Metaspace空间大小的启动参数为-XX:MaxMetaspaceSize。

5、Unable to create new native thread

每个Java线程都需要占用一定的内存空间,当JVM向底层操作系统请求创建一个新的native线程时,如果没有足够的资源分配就会报此类错误。

*原因分析*

JVM向OS请求创建native线程失败,就会抛出Unableto createnewnativethread,常见的原因包括以下几类:

1、线程数超过操作系统最大线程数ulimit限制;

2、线程数超过kernel.pid_max(只能重启);

3、native内存不足;

该问题发生的常见过程主要包括以下几步:

1、JVM内部的应用程序请求创建一个新的Java线程;

2、JVM native方法代理了该次请求,并向操作系统请求创建一个native线程;

3、操作系统尝试创建一个新的native线程;并为其分配内存;

4、如果操作系统的虚拟内存已耗尽,或是受到32位进程的地址空间限制,操作系统就会拒绝本次native内存分配;

5、JVM抛出java.lang.OutOfMemoryError:Unableto createnewnativethread错误。

6、Out of swap space?

该错误表示所有可用的虚拟内存已被耗尽。虚拟内存(Virtual Memory)由物理内存(Physical Memory)和交换空间(Swap Space)两部分组成。当运行时程序请求的虚拟内存溢出时就会报Outof swap space?错误。

*原因分析*

该错误出现的常见原因包括以下几类:

1、地址空间不足;

2、物理内存已耗光;

3、应用程序的本地内存泄漏(native leak),例如不断申请本地内存,却不释放。

4、执行jmap-histo:live命令,强制执行Full GC;如果几次执行后内存明显下降,则基本确认为Direct ByteBuffer问题。

7、Kill process or sacrifice child

有一种内核作业(Kernel Job)名为Out of Memory Killer,它会在可用内存极低的情况下“杀死”(kill)某些进程。OOM Killer会对所有进程进行打分,然后将评分较低的进程“杀死”,具体的评分规则可以参考Surviving the Linux OOM Killer。

不同于其他的OOM错误,Killprocessorsacrifice child错误不是由JVM层面触发的,而是由操作系统层面触发的。

*原因分析*

默认情况下,Linux内核允许进程申请的内存总量大于系统可用内存,通过这种“错峰复用”的方式可以更有效的利用系统资源。

然而,这种方式也会无可避免地带来一定的“超卖”风险。例如某些进程持续占用系统内存,然后导致其他进程没有可用内存。此时,系统将自动激活OOM Killer,寻找评分低的进程,并将其“杀死”,释放内存资源。

8、Requested array size exceeds VM limit

JVM限制了数组的最大长度,该错误表示程序请求创建的数组超过最大长度限制。

JVM在为数组分配内存前,会检查要分配的数据结构在系统中是否可寻址,通常为Integer.MAX_VALUE-2。

此类问题比较罕见,通常需要检查代码,确认业务是否需要创建如此大的数组,是否可以拆分为多个块,分批执行。

9、Direct buffer memory

Java允许应用程序通过Direct ByteBuffer直接访问堆外内存,许多高性能程序通过Direct ByteBuffer结合内存映射文件(Memory Mapped File)实现高速IO。

*原因分析*

Direct ByteBuffer的默认大小为64 MB,一旦使用超出限制,就会抛出Directbuffer memory错误

19. Java是如何通过什么算法认定某个对象是可回收的?

判断对象是否已死有引用计数算法和可达性分析算法

(1)引用计数算法

给每一个对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;每当有一个地方不再引用它时,计数器值减1,这样只要计数器的值不为0,就说明还有地方引用它,它就不是无用的对象。如下图,对象2有1个引用,它的引用计数器值为1,对象1有两个地方引用,它的引用计数器值为2 。

注意:当某些对象之间互相引用时,无法判断出这些对象是否已死,有循环引用的问题;

适用场景:Client模式(桌面应用);单核服务器。可以用-XX:+UserSerialGC来选择Serial作为新生代收集器

(2)可达性分析算法

当一个对象到GC Roots没有任何引用链相连(GC Roots到这个对象不可达)时,就说明此对象是不可用的,是死对象;

20. 常用的垃圾收集器有哪些?都各自运用了哪些算法?

–Serial收集器 采用的是复制算法

复制算法:

把内存分为大小相等的两块,每次存储只用其中一块,当这一块用完了,就把存活的对象全部复制到另一块上,同时把使用过的这块内存空间全部清理掉

缺点:实际可使用的内存空间缩小为原来的一半,比较适合

–ParNew收集器 复制算法

ParNew就是一个Serial的多线程版本,其它与Serial并无区别;

适用场景:多核服务器;与CMS收集器搭配使用。当使用-XX:+UserConcMarkSweepGC来选择CMS作为老年代收集器时,新生代收集器默认就是ParNew,也可以用-XX:+UseParNewGC来指定使用ParNew作为新生代收集器

–Parallel Scavenge:

Parallel Scavenge也是一款用于新生代的多线程收集器,与ParNew的不同之处是,ParNew的目标是尽可能缩短垃圾收集时用户线程的停顿时间,Parallel Scavenge的目标是达到一个可控制的吞吐量。

Parallel收集器以多线程,采用复制算法进行垃圾收集工作,收集完之后,用户线程继续开始执行;在老年代,当用户线程都执行到安全点时,所有线程暂停执行,Parallel Old收集器以多线程,采用标记整理算法进行垃圾收集工作。

适用场景:注重吞吐量,高效利用CPU,需要高效运算且不需要太多交互。可以使用-XX:+UseParallelGC来选择Parallel Scavenge作为新生代收集器,jdk7、jdk8默认使用Parallel Scavenge作为新生代收集器。

–Serial Old收集器:

Serial Old收集器是Serial的老年代版本,同样是一个单线程收集器,采用标记-整理算法。

–CMS收集器:

CMS收集器是一种以最短回收停顿时间为目标的收集器,以“最短用户线程停顿时间”著称。整个垃圾收集过程分为4个步骤

① 初始标记:标记一下GC Roots能直接关联到的对象,速度较快
② 并发标记:进行GC Roots Tracing,标记出全部的垃圾对象,耗时较长
③ 重新标记:修正并发标记阶段引用户程序继续运行而导致变化的对象的标记记录,耗时较短
④ 并发清除:用标记-清除算法清除垃圾对象,耗时较长

整个过程耗时最长的并发标记和并发清除都是和用户线程一起工作,所以从总体上来说,CMS收集器垃圾收集可以看做是和用户线程并发执行的。

–G1 收集器 分代回收算法:

把堆内存分为新生代和老年代,新生代又分为Eden区、From Survivor和To Survivor。一般新生代中的对象基本上都是朝生夕灭的,每次只有少量对象存活,因此新生代采用复制算法,只需要复制那些少量存活的对象就可以完成垃圾收集;老年代中的对象存活率较高,就采用标记-清除和标记-整理算法来进行回收。

在这里插入图片描述

在这些区域的垃圾回收大概有如下几种情况:

新生代使用时minor gc, 老年代使用的full gc 同时会触发一minor gc

l 大多数情况下,新的对象都分配在Eden区,当Eden区没有空间进行分配时,将进行一次Minor GC,清理Eden区中的无用对象。清理后,Eden和From Survivor中的存活对象如果小于To Survivor的可用空间则进入To Survivor,否则直接进入老年代);Eden和From Survivor中还存活且能够进入To Survivor的对象年龄增加1岁(虚拟机为每个对象定义了一个年龄计数器,每执行一次Minor GC年龄加1),当存活对象的年龄到达一定程度(默认15岁)后进入老年代,可以通过-XX:MaxTenuringThreshold来设置年龄的值。

解释:Eden空间不够,执行MinoGC, Eden和S0中存活的对象进入S1,对象年龄计数+1 ,

如果S1空间不够,直接进 入老年代 ,否则对象默认经过 15次MinorCG后才会进入老年代

l 当进行了Minor GC后,Eden还不足以为新对象分配空间(那这个新对象肯定很大),新对象直接进入老年代。

l 多个对象总占To Survivor空间一半以上且年龄相等,该对象直接进入老年代,比如Survivor空间是10M,有几个年龄为4的对象占用总空间已经超过5M,则年龄大于等于4的对象都直接进入老年代,不需要等到MaxTenuringThreshold指定的岁数。

l 在进行Minor GC之前,会判断老年代最大连续可用空间是否大于新生代所有对象总空间,如果大于,说明Minor GC是安全的,否则会判断是否允许担保失败,如果允许,判断老年代最大连续可用空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则执行Minor GC,否则执行Full GC。

解释:Minor GC之前,如果预测老年代内存不够,就进行Full GC老年代,否则就Minor GC新生代

l 当在java代码里直接调用System.gc()时,会建议JVM进行Full GC,但一般情况下都会触发Full GC,一般不建议使用,尽量让虚拟机自己管理GC的策略。

l 永久代(方法区)中用于存放类信息,jdk1.6及之前的版本永久代中还存储常量、静态变量等,当永久代的空间不足时,也会触发Full GC,如果经过Full GC还无法满足永久代存放新数据的需求,就会抛出永久代的内存溢出异常。

l 大对象(需要大量连续内存的对象)例如很长的数组,会直接进入老年代,如果老年代没有足够的连续大空间来存放,则会进行Full GC。

21. 你实际项目中运用到过jvm参数调优吗?分享下你调优得经历

1使用JMP jvisualvm分析内存快照;

2设置参数

-Xms 初始堆大小,默认物理内存的1/64 -Xms512M

-Xmx 最大堆大小,默认物理内存的1/4 -Xms2G

-Xmn 新生代内存大小,官方推荐为整个堆的3/8 -Xmn512M

-Xss 线程堆栈大小,jdk1.5及之后默认1M,之前默认256k -Xss512k

-XX:NewRatio=n 设置新生代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4 -XX:NewRatio=3

-XX:SurvivorRatio=n 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:8,表示Eden:Survivor=8:1:1,一个Survivor区占整个年轻代的1/8 -XX:SurvivorRatio=8

我们依据Java Performance这本书的建议的设置原则进行设置,

Java整个堆大小设置,Xmx 和 Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍,Xmx和Xms的大小设置为一样,避免GC后对内存的重新分配。而Full GC之后的老年代内存大小,我们可以通过前面在Visual VM中添加的插件Visual GC查看。先手动进行一次GC,然后查看老年代的内存占用。

新生代Xmn的设置为老年代存活对象的1-1.5倍。

老年代的内存大小设置为老年代存活对象的2-3倍。

22. MySql 中Myisam引擎和innodb引擎的区别?

索引的存储结构都支持B+树来存储;

1从锁的机制:

MyISAM只支持表锁****:

MyISAM的表级锁有两种模式:表共享读锁表独占写锁****;

InnoDB支持表级锁行级锁****;

InnoDB实现了两种类型的行锁:共享锁、排他锁。
共享锁:允许一个事务去读一行,阻止其它事务获取相同数据集的排他锁。
排他锁:允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。

2事务

MyISAM存储引擎不支持事务,它强调的是高性能的查询,适合读多写少、原子性要求低的情形

对于InnoDB存储引擎则提供了较为完善的事务支持,一共支持4个等级的事务隔离级别:未提交读(ISOLATION_READ_UNCOMMITTED)、已提交读(ISOLATION_READ_COMMITTED)、可重复读(ISOLATION_REPEATABLE_READ)、可序列化(ISOLATION_SERIALIZABLE)。

未提交读是最低的隔离级别,允许读取未提交的事务变更,会导致脏读、不可重复读和幻读。

已提交读是MySQL默认的隔离级别,允许读取事务提交后的结果,但是依然可能发生不可重复读和幻读问题。

可重复读对同一字段多次读取都是一致的,但是由于Inno DB的Gap Lock算法的存在,并不会导致幻读问题,但是对于Oracle或者SQL Server,则会发生幻读。

可序列化是最高级别的隔离级别,它要求同一时间最多只能有一个事务在执行,但是会严重影响数据库的性能。

同时,对于较高的隔离级别可能更容易造成锁冲突和死锁。

3外键:

MyISAM可以支持没有主键的表存在。
InnoDB必须设置主键

MyISAM将一张表的结构和内容分为三个文件:
1、表结构文件,后缀名为frm
2、索引文件,后缀名为MYI
3、数据文件,后缀名为MYD

4存储结构

(1)MyISAM存储引擎

MyISAM将一张表的结构和内容分为三个文件:
1、表结构文件,后缀名为frm
2、索引文件,后缀名为MYI
3、数据文件,后缀名为MYD

InnoDB存储引擎

相比MyISAM,InnoDB只有两个文件:
1、表结构文件,后缀名为frm
2、数据、索引文件,后缀名为ibd

4使用场景

1、如果应用程序对数据的一致性要求比较高,那么需要选择InnoDB,因为InnoDB支持事务和外键
2、以读操作为主的业务,适合使用MyISAM。对于读多写多的业务,适合使用InnoDB。

23. mysql 中innodb有哪些索引?各自运用场景?

1普通索引Normal:允许重复的值出现,可以在任何字段上面添加

2唯一索引 Unique:除了不能有重复的记录外,其它和普通索引一样,可以在值是唯一的字段添加(用户名、手机号码、身份证、email,QQ),可以为null,并且可以有多个null

3主键索引:是随着设定主键而创建的,也就是把某个列设为主键的时候,数据库就会給改列创建索引。这就是主键索引.唯一且没有null值,

4全文索引:用来对表中的文本域(char,varchar,text)进行索引, 全文索引针对MyISAM有用InnoDB不支持全文索引,所以一般不用,默认只支持英文. -使用ES,Lucene代替就

24.说说你对b+tree的理解,为什么选择的是b+tree 而不是 二叉树 红黑树呢?BTree 和

B+Tree又有什么区别?

25. 如果一个sql走的是覆盖索引,它还会回表吗?

不会,(回表:辅助索引扫描完之后还会扫描主键索引,这叫回表)

覆盖索引:如果Select name 查询的列正好包含在辅助索引的节点的键值中,它就不需要在扫描主键索引了,这个叫覆盖索引。所以不要写Select *

26.A B C 三个字段的索引 查询条件只有 B C 会走该索引吗?

会的,MySQL索引的匹配规则是向左匹配;

27.为什么开发规范会要求每张表有个自增的主键id呢?如果没有这个主键id mysql会怎样处理?

键是数据库确保数据行在整张表唯一性的保障。它是定位到一条记录并且确保不会重复存储的逻辑机制。主键也同时可以被外键引用来建立表与表之间的关系。

主键存在的作用:

1、确保一张表中的数据不会出现重复行。

2、在查询中引用单独的一行记录。

3、支持外键。

如果没有主键:

1.如果没有主键,Mysql会选择第一个不包含null的唯一索引作为主键索引,

2.如果不满足条件一,那么会选择一个隐藏的行RowID作为主键索引

查看隐藏rowId:Select _rowid from t_user

28.delete truncate drop的区别?

1、在速度上,一般来说,drop> truncate > delete。

2、在使用drop和truncate时一定要注意,虽然可以恢复,但为了减少麻烦,还是要慎重。

3、如果想删除部分数据用delete,注意带上where子句,回滚段要足够大;

如果想删除表,当然用drop;

如果想保留表而将所有数据删除,如果和事务无关,用truncate即可;

如果和事务有关,或者想触发trigger,还是用delete;

如果是整理表内部的碎片,可以用truncate跟上reuse stroage,再重新导入/插入数据。

参考文档:https://www.cnblogs.com/zhizhao/p/7825469.html

29. count(1) count(*) count(字段)的区别?

count(*)包括了所有的列,相当于行数,在统计结果的时候, 不会忽略列值为NULL
count(1)包括了忽略所有列,用1代表代码行,在统计结果的时候, 不会忽略列值为NULL
count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是只空字符串或者0,而是表示null)的计数, 即某个字段值为NULL时,不统计。

执行效率上:
列名为主键,count(列名)会比count(1)快
列名不为主键,count(1)会比count(列名)快
如果表多个列并且没有主键,则 count(1) 的执行效率优于 count(
如果有主键,则 select count(主键)的执行效率是最优的
如果表只有一个字段,则 select count(
)最优。

30. 如何优化sql

1先定位到慢sql:

– 使用阿里巴巴的连接池Druid,进行慢sql的监控;

– 使用mysql自带的命令查看;

l Mysql运行多久

show status like ‘uptime’;

l CRUD执行次数

完整语法:Show session/global status like ‘%Com_select%’

l 查看执行的线程

show processlist

可以用来查看当前数据库SQL正在执行的情况,定位SQL的状态

2分析慢sql原因:

–看下sql是否复杂,过于复杂的话,可以简化,可以发几条简单的sql进行数据的获取,或者在代码层做些处理;

–是否联表的次数过多,可以减少联表的查询,适当的增加一些冗余字段;

–查询的时候不要写select *,需要什么字段就写什么字段,避免回表(回表面试点);

–增加增量查询,数据量大时,可以搞一张汇总表,直接查汇总表;

–做分库分表(垂直水平(雪花算法保证id唯一性));

31. mysql 库存的场景下如何解决高并发的问题?

1减库存时给上锁,但是直接在DB层上锁会严重影响效率;

2使用缓存,把库存提前拿出来,将库存作为信号量存贮到redission(底层已经封装好加锁下锁,用lua脚本保证原子性,还有看门狗实时监控锁的过期时间)

3使用消息队列;

32. 再创建记录的时候 高并发场景下如何规避数据重复创建?

1做幂等性处理,每条记录在创建完之后都给一个唯一的标识,在创建时先从数据库中出查询是否有这条记录有就不用创建;

2既然是高并发,那我们可以用分布式锁(基于 redis 的 setnx 来解决这一问题);

33. mysql的事务ACID的各自含义?事务隔离级别?

· Atomicity(原子性):一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。

· Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。

· Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

· Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

隔离级别:

MySQL 的事务隔离是在 MySQL. ini 配置文件里添加的,在文件的最后添加:

transaction-isolation = REPEATABLE-READ

可用的配置值:READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE。

· READ-UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读)。

· READ-COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读)。

· REPEATABLE-READ:可重复读,默认级别,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读)。

· SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。

脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。

不可重复读 :是指在一个事务内,多次读同一数据。

幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了

34. binlog redolog undolog 的区别?

binlog二进制日志是mysql-server层的,主要是做主从复制,时间点恢复使用
redo log重做日志是InnoDB存储引擎层的,用来保证事务安全
undo log回滚日志保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读

35. redis 的数据结构有哪些?

Redis主要有5种数据类型,包括String,List,Hash,Set,ZSet。

string 是 redis 最基本的类型;

使用场景:常规key-value缓存应用。常规计数: 微博数, 粉丝数。

Redis hash 是一个键值(key => value)对集合。Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象

使用场景:存储部分变更数据,购物车,用户信息等

Redis list 列表是简单的字符串列表,按照插入顺序排序

使用场景:存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的数据

Redis set是string类型的无序集合。集合是通过hashtable实现的,概念和数学中个的集合基本类似,可以交集,并集,差集等等,set中的元素是没有顺序的。所以添加,删除,查找的复杂度都是O(1)。

应用场景:交集、并集、差集的操作,比如交集,可以把两个人的粉丝列表整一个交集

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

应用场景:去重但可以排序,如获取排名前几名的用户,排行榜;

参考文献:https://www.cnblogs.com/jasonZh/p/9513948.html

36. redis 你运用在什么场景?如何做到缓存 mysql的数据一致性?

结合业务来说,如:登录功能,存储用户信息,商城商品,库存,购物车,热点信息等;

采用延时双删可以保证缓存和数据库的一致性;

在增删改的操作时,先删除一次缓存,再更新数据库,再延时,再删除缓存,

确保缓存数据不是旧数据, 防止在更新数据的时候,有其他操作将旧数据读到缓存中

然后,延时删除 ,保证旧数据,会被删除

37. redis 又如何解决库存场景下的高并发问题?

可以用Redis的分布式锁redission;

\36. 分布式锁用过吗?如何实现的?

*①:数据库乐观锁;*

*②:redis分布式锁;*

redis的string类型中可以使用setnx 来判断“当key不存在时,设置数值并返回1,当key存在时,返回0”。整个操作过程是原子性的执行,所以可以用在分布式锁的场景中,返回1获得锁,返回0没有获得锁,

jedisCommands.set(“key”, “1”, “NX”, “EX”, 60);//如果"key"不存在,则将其设值为1,同时设置60秒的自动过期时间;

可以使用redis String类型里面的一个命令,setnx,这个命令在有key值的时候,是设值不进去的,没有key才可以设置值,但是有些问题,假如我程序挂了,那么这个锁就会一直在这儿,行成了死锁,所以需要设置锁的过期时间,让它自己释放锁;需要保证设置过期时间和加锁是原子性的(用set命令 NX EX进行组合);释放锁时需要判断是不是自己的锁,不要误删别的锁(需要保证判断锁和删除锁是原子的),用lua脚本来实现;

还可以用redssion,它已经封装好了加锁释放锁,也用到了lua脚本保证原子性;它有个自动释放锁的时间,默认30s,有个看门狗,会去检测这个时间,如果程序还没执行完,看门狗会刷新这个释放锁的时间,直到业务执行完,自己释放锁,

当然也可以自己去指定释放锁的时间,释放了锁,其他的线程会拿到锁,当我业务执行完了,释放锁的时,会去释放另一个线程的锁,redssion是不允许出现这种情况的,会抛一个错误,而我们只需要在内部try一下,自己消耗这个错误,就行了;

集成redission 新建它的一个客户端(新建一个配置类,将它的客户端定义到bean中)其他地方只需要注入这个客户端,去调用它的lock 或者unlock方法;

*③********:********基于zookeeper 临时顺序节点+watch*

临时节点+重试:

根据Zookeeper的临时节点的特性实现分布式锁,先执行的线程在zookeeper创建一个临时节点,代表获取到锁,后执行的线程需要等待,直到临时节点被删除说明锁被释放,第二个线程可以尝试获取锁。

临时顺序节点+watch(下一个监听上一个):
在这里插入图片描述

37. redis 如何做到高可用?部署的方式有几种?

1主从集群:

2哨兵模式:

3Cluster集群模式:

l Redis主从优缺

n 从是数据的一种备份 ; 防止数据丢失

n 读写分离,分担读的压力

n 主从是哨兵,cluster集群的基石

l Redis主从的缺点

n 不能分担写的压力

n 主挂了集群不可用,没法自动主备切换

n 存储得不到扩容,存储数据总量是主的数据总量

l 哨兵的优点

\1. 哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。

\2. 主从可以自动切换,系统更健壮,可用性更高。

l 哨兵的缺点

\1. 哨兵依然是主从模式,没法解决写的压力,只能减轻读的压力

\2. 存储得不到扩容,存储数据总量是主的数据总量

当主服务器宕机后,从服务器切换成主服务器的那段时间,服务是不能用的。

cluster优缺点:

如果半数以上master节点与故障节点通信超过(cluster-node-timeout),认为该节点故障,自动触发故障转移操作. 故障节点对应的从节点自动升级为主节点 , 如果某个主挂掉,而没有从节点可以使用,那么整个Redis集群进入宕机状态

38.如何解决redis hotkey的场景?

发现热点key之后,需要对热点key进行处理。

使用二级缓存

可以使用 guava-cache或hcache,发现热点key之后,将这些热点key加载到JVM中作为本地缓存。访问这些key时直接从本地缓存获取即可,不会直接访问到redis层了,有效的保护了缓存服务器。

备份(不建议)

把热点key在多个redis上都存一份,当有热key请求进来的时候,在redis中随机选取一台,进行访问取值,返回数据。

但是这种情况维护代价非常大,假设有100个备份KEY,那么在删除或者更新时,也需要更新100个KEY,所以这种方案不是很推荐

39. 解释下 什么是缓存击穿 缓存穿透 缓存雪崩 ? 如何解决?

1缓存击穿

​ 1概念

​ 1数据库和redis中都没有的数据,从缓存中没有获取导数据,全部到数据库中去拿数据,造成数据库的压力大

​ 2解决方案

​ 1数据库中和缓存中都没有,直接存储改key, 值为 null,并设置一个短时间的过期时间;

​ 2布隆过滤器(概率性结构器BitMap) hutool工具包:没有的直接就返回一个拖底数据

2缓存穿透

​ 概念

​ 缓存中没有数据,数据库中有,大量请求访问,全部访问数据库,造成压力大

​ 解决方案

​ 1加锁(只让一个请求去数据库拿数据,拿到数据再放到缓存中,其他的请求就到缓存中拿数据)

3缓存雪崩

​ 1概念

​ 主要是在缓存中的数据设置了统一过期时间,大量请求从缓存中访问不到数据,全部走数据库,造成压力过大,数据库宕机

​ 2解决方案

​ 1过期时间设置为随机值

​ 2热点数据永不过期

40. redis 是如何做到高吞吐量的?

· 高效的数据结构

Redis 支持的几种高效的数据结构 string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集 合)

· 多路复用 IO 模型

· 事件机制

参考文档:https://blog.csdn.net/agonie201218/article/details/108989546

41. redis 是如何实现持久化的?RDB 和 AOF的区别?

AOF:实时更新数据到磁盘;

RDB:使用时间间隔,对数据进行快照存储;

RDB 是一个非常紧凑(compact)的文件,它保存了 Redis 在某个时间点上的数据集,占用的内存空间是比AOF小;

做数据的备份适合选用RDB,实时的更新数据不适合做数据的备份;

RDB 非常适用于灾难恢复;

AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整,一致性要比RDB高

42.redis 内存淘汰机制有哪些?

volatile-lru :从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据

42. redis 的过期删除方式有哪些?

①、定时删除

在设置某个key 的过期时间同时,我们创建一个定时器,让定时器在该过期时间到来时,立即执行对其进行删除的操作。

优点:定时删除对内存是最友好的,能够保存内存的key一旦过期就能立即从内存中删除。

缺点:对CPU最不友好,在过期键比较多的时候,删除过期键会占用一部分 CPU 时间,对服务器的响应时间和吞吐量造成影响。

②、惰性删除

设置该key 过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。

优点:对 CPU友好,我们只会在使用该键时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查。

缺点:对内存不友好,如果一个键已经过期,但是一直没有使用,那么该键就会一直存在内存中,如果数据库中有很多这种使用不到的过期键,这些键便永远不会被删除,内存永远不会释放。从而造成内存泄漏。

③、定期删除

每隔一段时间,我们就对一些key进行检查,删除里面过期的key。

优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。

缺点:难以确定删除操作执行的时长和频率。

如果执行的太频繁,定期删除策略变得和定时删除策略一样,对CPU不友好。

如果执行的太少,那又和惰性删除一样了,过期键占用的内存不会及时得到释放。

另外最重要的是,在获取某个键时,如果某个键的过期时间已经到了,但是还没执行定期删除,那么就会返回这个键的值,这是业务不能忍受的错误。

**43.**RocketMQ 是如何做到消息不丢失的?

从Producer分析:如何确保消息正确的发送到了Broker?

默认情况下,可以通过同步的方式阻塞式的发送,check SendStatus,状态是OK,表示消息一定成功的投递到了Broker,状态超时或者失败,则会触发默认的2次重试。此方法的发送结果,可能Broker存储成功了,也可能没成功

采取事务消息的投递方式,并不能保证消息100%投递成功到了Broker,但是如果消息发送Ack失败的话,此消息会存储在CommitLog当中,但是对ConsumerQueue是不可见的。可以在日志中查看到这条异常的消息,严格意义上来讲,也并没有完全丢失

RocketMQ支持 日志的索引,如果一条消息发送之后超时,也可以通过查询日志的API,来check是否在Broker存储成功

从Broker分析:如果确保接收到的消息不会丢失?

消息支持持久化到Commitlog里面,即使宕机后重启,未消费的消息也是可以加载出来的

Broker自身支持同步刷盘、异步刷盘的策略,可以保证接收到的消息一定存储在本地的内存中

Broker集群支持 1主N从的策略,支持同步复制和异步复制的方式,同步复制可以保证即使Master 磁盘崩溃,消息仍然不会丢失

从Cunmser分析:如何确保拉取到的消息被成功消费?

消费者可以根据自身的策略批量Pull消息

Consumer自身维护一个持久化的offset(对应MessageQueue里面的min offset),标记已经成功消费或者已经成功发回到broker的消息下标

如果Consumer消费失败,那么它会把这个消息发回给Broker,发回成功后,再更新自己的offset

如果Consumer消费失败,发回给broker时,broker挂掉了,那么Consumer会定时重试这个操作

如果Consumer和broker一起挂了,消息也不会丢失,因为consumer 里面的offset是定时持久化的,重启之后,继续拉取offset之前的消息到本地

44**.**RocketMQ的集群模式和广播模式有什么区别?

(1)集群模式:同一个consumerGroupName下的多个consumer平摊消息队列中的消息,例如三个消费者处于同一个group下,且订阅了同一个topic,加入生产者往消息队列中放入了这个topic的6条消息,那么消费者消费消息的总和为6条,消费完的消息不能被其他实例所消费

(2)广播模式:指的是consumer属于同一个ConsumerGroup,消息也会被ConsumerGroup中的每个Consumer都消费一次,广播消费中ConsumerGroup概念可以认为在消息划分方面无意义

**45.**RocketMQ在一个实例里面 一个topic创建多个tag的消费者 会出现什么情况?

会出现接收消息的时候,只能够接收到一部分,其他的会被覆盖掉;

这种现象的原因是:消息的分配是Broker决定的,而不是Consumer端,Consumer端发心跳给Broker,Broker收到后存到consumerTable里(就是个Map),key是GroupName,value是ConsumerGroupInfo。ConsumerGroupInfo里面是包含topic等信息的,但是问题就出在上一步骤,key是groupName,同GroupName的话Broker心跳最后收到的Consumer会覆盖前者的。这样同key,肯定产生了覆盖

解决方案就是:初始化多个ConsumerBean,每个ConsumerBean中的配置不同的groupId和tag,同时注册不同的监听器

44.RocketMQ 消费者实例数大于队列数量会出现什么现象?

45. RocketMQ会重复消费吗?如何解决重复消费带来的问题?

网络的原因

1、消费端处理消息的业务逻辑保持幂等性
2、保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现

46. Rocket MQ 消息积压问题如何解决?

情况0,如果消息是可以被丢弃的,那么就修改代码直接将代码中获取的消息直接丢弃,不做任何处理;

情况1, Topic中MessageQueue的数量大于Consumer的实例数量,可以将Consumer扩容,MessageQueue 会进行Rebalance重新分配给Consumer实例,此时多个Consumer实例可以迅速消费掉堆积的消息,但是要考虑到的后续如果业务中有DB操作,DB是否支持这么高的读写操作;

情况2, Topic中MessageQueue的数量小于Consumer的实例数量,此时直接扩容Consumer的实例数量是没用的,扩容后的Consumer实例仍然无法消费MessageQueue里面的消息; 此时可以修改项目代码,新建一个临时的Topic,制定临时Topic的MessageQueue数量为多个,然后再启动多个临时消费实例;此时Consumer将堆积的Topic里面对应的消息,不处理,收到后直接丢到新的Topic里面去,让消费者去消费

如果Consumer和Queue不对等,上线了多台也在短时间内无法消费完堆积的消息怎么办?
• 准备一个临时的topic
• queue的数量是堆积的几倍
• queue分布到多Broker中
• 上线一台Consumer做消息的搬运工,把原来Topic中的消息挪到新的Topic里,不做业务逻辑处理,只是挪过去
• 上线N台Consumer同时消费临时Topic中的数据
• 改bug
• 恢复原来的Consumer,继续消费之前的Topic

Rocket MQ 常见面试:

https://blog.csdn.net/weixin_39922929/article/details/110347477

47. 分布式设计中的CAP原则解释下

Consistency(一致性):每次读操作都能保证返回的是最新数据;

Availability(可用性):任何一个没有发生故障的节点,会在合理的时间内返回一个正常的结果;
备注:在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性),换句话就是说,任何时候,任何应用程序都可以读写数据。

Partition tolerance(分区容错性):以当节点间出现网络分区,照样可以提供服务。

CAP理论指出:CAP三者只能取其二,不可兼得。

48. 在微服务场景下涉及到很多链路调用 如何解决分布式事务的?2PC 3PC TCC模式?

常见的解决方案有2PC、TCC、 可靠消息最终一致性、最大努力通知;

2PC即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段( Prepare phase).提交阶段( commit phase ) , 2是指两个阶段, P是指准备阶段, C是指提交阶段

在这里插入图片描述
在这里插入图片描述

缺点:

l 在第一阶段,如果参与者迟迟不回复协调者,就会造成事务的阻塞,性能不好。

l 单节点故障,如果协调器挂了,参与者会阻塞,比如在第二阶段,如果事务协调器宕机,参与者没办法回复信息,长时间处于事务资源锁定,造成阻塞(事务操作是要加锁的)。

l 在第二阶段,如果在事务协调器发出"commit"执行后宕机,一部和参与者收到了消息提交了事务,而 一部分没有消息没法做出事务提交操作,这样就出现了数据不一致。

l 在第二阶段,如果事务事务协调器发出“commit”指令后宕机,收到“commmit”指令的参与者也宕机了, 那么事务最终变成了什么效果,提交了还是没提交?没有谁知道。

TCC, 是基于补偿型事务的AP系统的一种实现, 具有弱一致性

补偿理解:按照事先预定的方案去执行,如果失败了就走补偿方案(撤销)

在这里插入图片描述

事务开始时,业务应用会向事务协调器注册启动事务。之后业务应用会调用所有服务的try接口,完成一阶段准备。之后事务协调器会根据try接口返回情况,决定调用confirm接口或者cancel接口。如果接口调用失败,会进行重试。

*–Seata*

l Transaction Coordinator(TC):事务协调器,它是独立的中间件,需要独立部署运行,它维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通信协调各各分支事务的提交或回滚。 相当于是一个软件需要单独部署

l Transaction Manager ™:事务管理器, TM需要嵌入应用程序中工作,它负责开启一个全局事务,并最终 向TC发起全局提交或全局回滚的指令。

l Resource Manager (RM):控制分支事务, 负责分支注册、状态汇报,并接收事务协调器TC的指令, 驱动 分支(本地)事务的提交和回滚。

案例:注册用户送积分

与传统的2PC相比,架构层次方面,传统2PC方案的RM实际上是在数据库层, RM本质上就是数据库自身,通过XA协议实现,而Seata的RM是以jar包的形式作为中间件层部署在应用程序这一侧的。

两阶段提交方面,传统2PC无论第二阶段的决议是commit还是rollback ,事务性资源的锁都要保持到Phase2完成才释放。而Seata的做法是在Phase1就将本地事务提交,这样就可以省去Phase2持锁的时间,整体提高效率

*Seata工作原理:*

开启全局事务----->从TC获取XID----->开始执行业务----->将XID携带到分支事务----->分支事务向TC注册本地分支事务----->写入本地undo log----->发起全局事务的提交----->分支事务删除undo log

在这里插入图片描述

49. Spring 如何实现事务的?导致Spring事务失效的原因有哪些?Spring事务的传播机制有哪些?

–Spring对事务的支持:

TransactionDefinition:封装事务的隔离级别,超时时间,是否为只读事务和事务的传播规则等事务属性,可通过XML配置具体信息。

PlatformTransactionManager:根据TransactionDefinition提供的事务属性配置信息,创建事务。

TransactionStatus:封装了事务的具体运行状态。比如,是否是新开启事务,是否已经提交事务,设置当前事务为rollback-only等。

–失效原因:

未启用spring事务管理功能
方法不是public类型的
数据源未配置事务管理器
自身调用问题
异常类型错误
异常被吞了
业务和spring事务代码必须在一个线程中

–传播机制:

事务传播行为类型说明
PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
**50.**SpringMVC的流程是怎样的?

01、用户发送出请求到前端控制器DispatcherServlet。

02、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。

03、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。

04、DispatcherServlet调用HandlerAdapter(处理器适配器)。

05、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。

06、Controller执行完成返回ModelAndView对象。

07、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。

08、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。

09、ViewReslover解析后返回具体View(视图)。

10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。

11、DispatcherServlet响应用户。
在这里插入图片描述

51. SpringBoot 是如何实现自动装配的?

1.主启动类上的@SpringBootApplication注解中包括了一个@EnableAutoConfiguration注解,它的作用是开启SpringBoot自动配置;

****2.****@EnableAutoConfiguration注解会使用@Impot导入一个选择器,

3.选择器会去加载classpath下的jar中的spring.factories文件,这个文件包含很多自动配置的类,使用的是权限定名;

4.其中有一个WebMvcAutoConfiguration ,就是对web的自动配置类 ;同样的道理,这个文件还包含DispatcherServletAutoConfiguration 就是对前端控制器的自动配置;DataSourceAutoConfiguration 数据源的自动配置;(通过bean+方法的方式,将DispatcherServlet注册到Spring容器中,就不需要自己配置了)等等。

5.这些自动配置类是通过定义bean的方式注册bean对象到spring中的,并且一般都含有默认的属性,所以不再需要我们关心他们的基本配置了。

52. Mybatis 的${} 和#{}有什么区别?

#{}是预编译处理,${}是字符串替换。

(1)mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值。

(2)mybatis在处理 时 , 就 是 把 {}时,就是把 {}替换成变量的值。

(3)使用#{}可以有效的防止SQL注入,提高系统安全性。原因在于:预编译机制。预编译完成之后,SQL的结构已经固定,即便用户输入非法参数,也不会对SQL的结构产生影响,从而避免了潜在的安全风险。

(4)预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。

操作,如果当前存在事务,就把当前事务挂起。 |
| PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
| PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |

**50.**SpringMVC的流程是怎样的?

01、用户发送出请求到前端控制器DispatcherServlet。

02、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。

03、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。

04、DispatcherServlet调用HandlerAdapter(处理器适配器)。

05、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。

06、Controller执行完成返回ModelAndView对象。

07、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。

08、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。

09、ViewReslover解析后返回具体View(视图)。

10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。

11、DispatcherServlet响应用户。

[外链图片转存中…(img-WRsTGn30-1634221213154)]

51. SpringBoot 是如何实现自动装配的?

1.主启动类上的@SpringBootApplication注解中包括了一个@EnableAutoConfiguration注解,它的作用是开启SpringBoot自动配置;

****2.****@EnableAutoConfiguration注解会使用@Impot导入一个选择器,

3.选择器会去加载classpath下的jar中的spring.factories文件,这个文件包含很多自动配置的类,使用的是权限定名;

4.其中有一个WebMvcAutoConfiguration ,就是对web的自动配置类 ;同样的道理,这个文件还包含DispatcherServletAutoConfiguration 就是对前端控制器的自动配置;DataSourceAutoConfiguration 数据源的自动配置;(通过bean+方法的方式,将DispatcherServlet注册到Spring容器中,就不需要自己配置了)等等。

5.这些自动配置类是通过定义bean的方式注册bean对象到spring中的,并且一般都含有默认的属性,所以不再需要我们关心他们的基本配置了。

52. Mybatis 的${} 和#{}有什么区别?

#{}是预编译处理,${}是字符串替换。

(1)mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值。

(2)mybatis在处理 时 , 就 是 把 {}时,就是把 {}替换成变量的值。

(3)使用#{}可以有效的防止SQL注入,提高系统安全性。原因在于:预编译机制。预编译完成之后,SQL的结构已经固定,即便用户输入非法参数,也不会对SQL的结构产生影响,从而避免了潜在的安全风险。

(4)预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值