Java面试题(基础综合)

一、Java基础

1.有哪些排序,列举一种?

冒泡排序:遍历n,每次将邻近的较大的数跟较小的数交换。

/**
     * 冒泡排序
     * 思想:从头到尾遍历一遍,两两比较,
     *      遇到不满足条件的就进行交换,
     *      n-1轮遍历下来,数组就有序了。
     * 特点:冒泡排序也是广义上的选择排序,
     *      每次遍历,都能够找到满足条件的一个元素,
     *      可以用来解决TopN类问题。
     *
     * @param list
     */
public static void Bubble_Sort(int list[]){
        int temp;
        for(int i=list.length;i>1;i--){
            for(int j=0;j<i-1;j++){
                if(list[j]>list[j+1]) {
                    temp = list[j];
                    list[j] = list[j + 1];
                    list[j + 1] = temp;
                }
            }
        }
    }

快速排序:定义一个标准,大于标准的放一边,小于标准的方另一边,再递归
/**
* 快速排序
* 思想:1.从待排序元素中任取一个基准,通常是第一个元素,
* 2.将待选元素进行区分,将比基准大的元素放在它右边,比它小的放在它左边。
* 3.对左右两个分区重复以上步骤,完成排序。
* 特点:快排是冒泡排序的改进版,也是最好的一种内排序,
* 数组有序度越低,算法越有优势,
* 当数组有序度越高时,优先使用简单排序。
* @param list
*/
public static void Quick_Sort(int list[],int _left,int _right) {

    int left = _left;
    int right = _right;
    int temp;
    if (left <= right) {
        temp = list[left];
        while (left != right) {

            while (right > left && list[right] >= temp)
                right--;
            list[left] = list[right];
            while (left < right && list[left] <= temp)
                left++;
            list[right] = list[left];
        }
        list[right] = temp; //基准元素归位
        Quick_Sort(list, _left, left - 1);
        Quick_Sort(list, right + 1, _right);
    }
}

简单插入排序(插入排序):定义第一个元素为排序好的元素,取下一个元素,与排序排序好的元素比较,也就是往前比较,遇到小于的就插入到前面。

/**
     * 简单插入排序
     * 思想:需要两个指针,一个指向已经排序好元素的队尾,
     *      一个用来遍历后面无序的元素,
     *      依次往前比较,遇到不满足顺序的就进行交换,
     *      直到遇到满足条件的,就跳出循环,flag往后移一位。
     * @param list
     */
    public static void Insert_Sort(int list[]){
        int flag;
        int temp;
        for(int i=2;i<list.length;i++){
            flag = i;
            for(int j=i-1;j>=0;j--){
                if(list[j]>list[i]){
                    temp = list[i];
                    list[i] = list[j];
                    list[j] = temp;
                    i--;//不满足顺序,两只指针同时后移
                }else{
                    break;
                }
            }
            i=flag;
        }
    }


希尔排序(插入排序的改进版):将整个序列分割成若干子序列,进行插入排序,每次根据子序列的长度进行k次排序, 每趟完成,重新分割成长度为m的子序列,再次进行排序, 最后分割成长度为1,的子序列, 它与简单插入排序不同在于会优先比较远处的元素, 这种操作会增加比较的次数,但是会降低交换的次数,这种排序是第一个突破O(n^2)的排序算法。

/**
     * 希尔排序
     * 思想:希尔排序又叫缩小增量排序,待排序序列有n个元素,
     *      首先去以这个正数increment(<n),
     *      然后作为间隔,将所有元素分为m个大小为2的子序列,
     *      每个子序列分别排序,达到部分有序,
     *      然后缩小increment,重复上述步骤,直到increment = 1
     * 理解:希尔排序,与冒泡排序有很多相似之处,只是这个是一个基于插入排序的排序
     *      希尔排序较与冒泡排序的优势之处在于:
     *          冒泡排序:遍历n遍,共比较[n*(n-1)]/2次,每次都是比较相邻元素,
     *          希尔排序:遍历的次数与间隔设置有关,每趟比较次数与间隔有关,初始几轮,能够比较间隔较远的元素。
     * 优点:冒泡排序就像是暴力算法,比较次数与队列无关。
     *      希尔排序会根据n设置间隔,好处表现在两个地方:
     *      1.初始几轮比较,间隔较大,碰到不满足条件的,能够直接将两元素进行交换,而冒泡需要一个个往前比较交换。
     *      2.在初始的几轮比较之后,能够使序列基本有序。这达到了两个目的:
     *           1.初始几轮遍历,比较次数少,效率高。
     *           2.最后几轮,由于序列已基本有序,比较次数多,但交换次数降低了。
     * 问题:间隔设置,对此算法的影响较大,此算法需要额外的n空间,用于数据的插入操作。
     *      当间隔没设置好的话,不能达到排序的目的。
     *      当间隔简单的设置为单调递减,那么希尔排序的复杂度将于冒泡排序差不多,
     *          只不过,冒泡排序处理基本有序序列效率高,而希尔排序处理基本不有序的效率更高。
     *
     * @param list
     */
    public static void Shell_Sort(int list[]){
        int temp;
        int j = 0;
        //for(int increment = list.length/3+1;increment>=1;increment=increment/3+1){
        for(int increment = list.length-1;increment>=1;increment--){
            for(int n = 0;n<list.length-increment;n++){
                if(list[n]>list[n+increment]){
                    temp = list[n];
                    list[n] = list[n+increment];
                    list[n+increment] = temp;
                    j++;
                }
            }
            if (increment==1)break;
        }
    }


简单选择排序:从待排序序列中找到最小的元素,放到序列起始端,找到第二小的放到最小的后面。

/**
     * 简单选择排序
     * 思想:从无序的队列中,选择出其中最大的元素,依次放到对头
     *      以致达到排序的目的
     * 实现:定义一个flag,指向已排序好的队列的队尾。
     *      另一个指针依次往后遍历,保存遍历到的最大元素,
     *      每次遍历完成后,将其与队尾后的元素进行交换。
     * @param list
     */
    public static void Select_Sort(int list[]){
        int min = 100;
        int temp,flag=0;
        int i,j;
        for(i =0;i<list.length;i++){
            for(j=i;j<list.length;j++){
                if(min>list[j]){
                    min = list[j];
                    flag = j;
                }
            }
            temp = list[flag];
            list[flag] = list[i];
            list[i] = temp;

            min = 100;
            show_sort(list);
        }
    }


`堆排序:堆排序分为创建堆的过程,和查找元素的过程,创建堆可以使用数组或者完全二叉树。
1.构建大顶堆/小顶堆。
2.查询数据,堆排序适合只查找最大几个元素或者最小几个元素。

class TreeNode{

    int val;
     TreeNode left;
     TreeNode right;

     public TreeNode(int val) {
         this.val = val;
     }
 }
/**
* 堆排序
  * 思想:根据需要创建大顶堆或者小顶堆,堆就是平衡查找二叉树
  */
 public static void Stack_Sort(int list[]){
     Min_Stack(list);
 }
 /**
  * 根据队列创建一个小顶堆
  * 思想:创建一个根最小的平衡二叉树,每条路径长度之差越小,算法的时间复杂度越靠近(logn)
  */
 private static void Min_Stack(int list[]){

     TreeNode root = new TreeNode(list[0]);
     TreeNode r = root;
     for(int i = 1;i<=list.length;i++ ){

         while (r!=null){
             if(r.val>list[i]) {
                 r = r.left;
             }
             if(r.val<list[i]){
                 r = r.right;
             }
         }
         TreeNode node = new TreeNode(list[i]);
         r.val = list[i];
         r = root;
     }
 }

二路归并:将序列分成两组,每组再次进行分组,分到不可再分,在进行比较,合并起来。

/**
     * 二路归并
     * 思想:采用分治法,将已排序好的元素得到完全有序的序列。
     */
    public static void Merge_Sort(int list[]){

        int []temp = new int[list.length];
        sort(list,0,list.length-1,temp);
    }
    /**
     * 递归
     */
    private static void sort(int list[] ,int start,int end,int temp[]){

        if(start<end) {
            int mid = (start + end) / 2;
            sort(list, start, mid,temp);
            sort(list, mid + 1, end,temp);
            merge(list, start, mid, end,temp);
        }
    }
    /**
     * 归并算法
     * 思想:将已排序好的两个序列进行合并
     *
     */
    private static void merge(int list[],int start,int mid,int end,int temp[]){
        int i= start;
        int j = mid+1;
        int t = 0;
        while(i<=mid && j<=end){
            if(list[i]<list[j]){
                temp[t++] = list[i++];
            }
            else{
                temp[t++] = list[j++];
            }
        }
        while(i<=mid){
            temp[t++] = list[i++];
        }
        while (j<=end){
            temp[t++] = list[j++];
        }
        t=0;
        //将元素拷贝到原数组
        while (start <= end){
            list[start++] = temp[t++];
        }
    }

2.equals和==,hashCode()和equals()

hashCode()与equals()的相关规定:

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

==与equals的区别

==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同
==是指对内存地址进行比较 equals()是对字符串的内容进行比较
==指引用是否相同 equals()指的是值是否相同
3.String、StringBuffer、StringBuilder

Java 平台提供了两种类型的字符串:String 和 StringBuffer/StringBuilder,它们都可以储存和操作字符串,区别
如下。
1)String 是只读字符串,也就意味着 String 引用的字符串内容是不能被改变的。
2)StringBuffer/StringBuilder 表示的字符串对象可以直接进行修改。
3)StringBuilder 是 Java5 中引入的,它和 StringBuffer 的方法完全相同,区别在于它是在单线程环境下使用的,
因为它的所有方法都没有被 synchronized 修饰,因此它的效率理论上也比 StringBuffer 要高。

4.接口和继承区别?
  • 实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
  • 构造函数:抽象类可以有构造函数;接口不能有。
  • main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。
  • 实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
  • 访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。
5.集合了解吗?常用集合,arrayList和linkedList

LinkedList:基于链表实现,每一个元素存储本身内存地址的同时还存储
下一个元素的地址。链表增删快,查找慢;
ArrayList:基于数组;每次增删都要创建新的数组,但数组有索引。数组
增删慢,查找快
Vector:基于数组,线程安全的,效率低

6.final可以修饰的类、方法、变量

1)final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。
2)finally:异常处理语句结构的一部分,表示总是执行。
3)finalize:Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法
提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法
被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对
象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用。

7.hashset和hashmap

HashSet:底层是由HashMap实现的,能够存放不重复的数据,HashSet的值存放在HashMap的key上。
HashMap:数组和链表的结合体,允许null值和null键,不保证映射顺序,特别是不保证该顺序永恒不变。当我们往HashMap put值的时候,首先根据key的hashCode重新计算hash值,然后根据hash值计算元素的下标,如果该位置有元素,那么该位置的元素就会以链表的形式存在,新加入的放在链头,如果该位置没有元素,就直接将该元素放到数组的该位置上。
从jdk1.8之后对hashMap进行了优化,如果链表的节点超过8个之后,该链表就会转成红黑树提高查询效率。

8.了解哪些设计模型,平时怎么用?

设计模式的六大原则
• 开闭原则(Open Closed Principle,OCP)
• 里氏代换原则(Liskov Substitution Principle,LSP)
• 依赖倒转原则(Dependency Inversion Principle,DIP)
• 接口隔离原则(Interface Segregation Principle,ISP)
• 合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
• 最小知识原则(Principle of Least Knowledge,PLK,也叫迪米特法则)
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模
式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合
模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
单例模式
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
工厂模式
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使
其创建过程延迟到子类进行。
主要解决:主要解决接口选择的问题。
何时使用:我们明确地计划不同条件下创建不同实例时。
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
关键代码:创建过程在其子类执行。
装饰者模式
意图:装饰者模式通过组合的方式扩展对象的特性,这种方式允许我们在任何时候对对象
的功能进行扩展甚至是运行时扩展。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态
特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用: 在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,同时继承装饰者模式。。
关键代码:1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承Component 类,具体扩展类重写父类方法。

9.object方法(clone,getClass,toString,finalize,equals,hashCode,wait,notify,notifyAll)
10.get和post区别?

Get 请求方式:地址栏里会显示我们提交的数据(不安全),并且地址栏中支持提交少量数据,请求的数据存在请求行中
Post 请求方式:地址栏里不显示我们提交的数据信息(相对安全),可以提交大量数据,请求的数据存在请求正文中

11.Java创建线程的方式、start()和run()的区别?
12.wati和nodify

二、数据库

1.union和union all,union和or
2.mysql和oracle如何限制查询数量

Oracle 中使用 rownum 来进行分页, 这个是效率最好的分页方法,hibernate 也
是使用 rownum 来进行 oralce 分页的
select * from
( select rownum r,a from tabName where rownum <= 20 )
where r > 10
mysql中使用limit来进行分页

3.索引的优缺点,哪里该建索引、哪里不该建索引,什么时候失效?

索引中包含由表或视图一列或多列生成的键,这些键存储在btree中,可以快速有效的查询与键值有关的行。

优点:

第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
第二,可以大大加快 数据的检索速度。
第三,可以加速表和表之间的连接。
第四,在使用分组和排序 子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

缺点:

第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。

哪里需要建立索引:

第一:在经常需要搜索的列上,可以加快搜索的速度;
第二:在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;
第三:在经常用在连接的列上,这 些列主要是一些外键,可以加快连接的速度;
第四:在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的;
第五:在经常需要排序的列上创 建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;
第六:在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。

哪里不需要创建索引:

第一,对于那些在查询中很少使用或者参考的列不应该创建索引。这是因 为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
第二,对于那 些只有很少数据值的列也不应该增加索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
第三,对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。
第四,当修改性能远远大于检索性能时,不应该创建索 引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因 此,当修改性能远远大于检索性能时,不应该创建索引。

索引失效:

第一:如果条件中有or,即使其中有条件带索引也不会使用(这就是为什么尽量少使用or的原因)(注意:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引)
第二:对于多列索引,不是使用的第一部分(不符合最左前缀原则),则不会使用索引,例子如下:
如果select * from key1=1 and key2= 2;则建立组合索引(key1,key2);
select * from key1 = 1;组合索引有效;
select * from key1 = 1 and key2= 2;组合索引有效;
select * from key2 = 2;组合索引失效;不符合最左前缀原则
第三:like查询是以%开头
第四:如果列类型是字符串,那一定要在条件中使用引号引起来,否则不会使用索引
第五:如果mysql估计使用全表扫描比使用索引快,则不使用索引

4.讲讲理解的事务?

1. 应用场景:存在并发数据访问时才需要事务
2. ACID 四大特性
a) 原子性(Atomicity): 整个事务中的所有操作,要么全部完成,要么全部不完成,
不可能停滞在中间某个环节。任何一项操作的失败都会导致整个事务的失败。
b) 一致性(Correspondence): 在事务开始之前和事务结束以后,数据库的完整性约
束没有被破坏。
c) 隔离性(Isolation): 并发执行的事务彼此无法看到对方的中间状态。
d) 持久性(Durability): 在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
3. 事务中的问题与隔离级别 ➢ 问题
脏读: 一个事务读取到另一个事务未提交的数据;
不可重复读:一个事务中两次查询的数据不一致 --> 一个事务读到了另一个
事务 已经提交数据(update 操作)
虚读(幻读):一个事务中两次查询的数据不一致 --> 一个事务读到了另一个
事务 已经提交数据(insert 操作) ➢ 隔离级别(安全从低到高,性能从高到低)
读未提交:也叫脏读,是事务可以读取其它事务未提交的数据。–> 未解决任何问题;
读已提交:在事务未提交之前所做的修改其它事务是不可见的–> 解决脏读问题;
可重复读:保证同一个事务中的多次相同的查询的结果是一致的–> 解决脏读,不可重复读问题;
可串行化: 保证读取的范围内没有新的数据插入,比如事务第一次查询得到某个范围的数据,第二次查询也同样得到了相同范围的数据,中间没有新的数据插入到该范围中–> 解决脏读,不可重复读,虚读(幻读)问题;
常用数据库默认隔离级别:
MySQL: 可重复读
Oracle: 读已提交
SQLServer: 读已提交
4. 如何进行事务管理
Connection 提供了事务处理的方法,通过调用 setAutoCommit(false)可以设置手动提交事务;当事务完成后用 commit()显式提交事务;如果在事务处理过程中发生异常则通过 rollback()进行事务回滚。除此之外,从 JDBC 3.0 中还引入了 Savepoint(保存点)的概念,允许通过代码设置保存点并让事务回滚到指定的保存点

5.分表分库分区
6.数据库语句优化
  1. 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order
    by 涉及的列上建立索引。
  2. 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放
    弃使用索引而进行全表扫描
  3. 应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引
    而进行全表扫描。
  4. 应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使
    用索引而进行全表扫描
  5. in 和 not in 也要慎用,否则会导致全表扫描
  6. 索引并不是越多越好,索引固然可 以提高相应的 select 的效率,但同时
    也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建
    索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好
    不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要
  7. 查询结果不要用*来查询所有字段,要明确指明结果字段
  8. 根据查询条件,建立索引,如果查询条件不止一个时,使用组合索引
  9. 在查询条件表达式的左侧尽量不要使用函数,否则索引失效
  10. 如果有 like 话,尽量避免%xxx%两侧都有%的条件,单侧%可以使用索引,多侧不可以
  11. 建立索引时字段不能有 null 值

三、jvm

1.内存模型(虚拟机栈、堆、方法区、程序计数器、本地方法栈)

程序计数器(Program Counter Register)

记录了程序执行的字节码的行号和指令

虚拟机栈(Cirtual Machine Stacks)

存储基本数据类型(boolean、byte、、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身。可能是一个对象起始地址的引用指针,也可能指向代表一个对象的句柄或者此对象的相关位置)和returnAddress类型(指向了一条字节码指令的地址)。

本地方法栈(Native Method Stack)

与java虚拟机栈作用是相似的,他们之间的区别是java虚拟机栈用来执行java方法(也就是字节码),而本地方法栈则为虚拟使用到的native(用来修饰可供其他语言调用的方法,如操作操作系统底层服务的方法)服务

堆(Heap)

存放对象实例及数组

方法区(Method Area)

存储虚拟机加在的类信息、常量、静态变量、即时编译器编译后的代码等数据

运行时常量池(RuntimelyConstant Pool)

方法区的一部分,当前区域用于存储编译器生成的各种字面常量和符号引用,这部分内容在类加载后进入方法区的运行时常量池中存放。 运行期间也可能会将常量放到池中

2.什么时候会GC,GC新生代,老年代,永久代如何工作

年轻代

主要是用来存放新生的对象。新生代又细分为 Eden区、SurvivorFrom区、SurvivorTo区
新创建的对象都会被分配到Eden区(如果该对象占用内存非常大,则直接分配到老年代区), 当Eden区内存不够的时候就会触发MinorGC(Survivor满不会引发MinorGC,而是将对象移动到老年代中),
在Minor GC开始的时候,对象只会存在于Eden区和Survivor from区,Survivor to区是空的。
Minor GC操作后,Eden区如果仍然存活(判断的标准是被引用了,通过GC root进行可达性判断)的对象,将会被移到Survivor To区。而From区中,对象在Survivor区中每熬过一次Minor GC,年龄就会+1岁,当年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置,默认是15)的对象会被移动到年老代中,否则对象会被复制到“To”区。经过这次GC后,Eden区和From区已经被清空。
“From”区和“To”区互换角色,原Survivor To成为下一次GC时的Survivor From区, 总之,GC后,都会保证Survivor To区是空的。

老年代

在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到老年代中,而且大对象(占用大量连续内存空间的java对象如很长的字符串及数组)直接进入老年代。
当survivor空间不够用时,需要依赖老年代进行分配担保。

(元空间)

在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间,Metaspace)的区域所取代。
主要存放Class和Meta的信息,Class在被加载的时候被放入永久代。 它和存放对象的堆区域不同,GC(Garbage Collection)不会在主程序运行期对永久代进行清理,所以如果你的应用程序会加载很多Class的话,就很可能出现PermGen space错误。

MinorGC:是指清理新生代
MajorGC:是指清理老年代(很多MajorGC是由MinorGC触发的)
FullGC:是指清理整个堆空间包括年轻代和永久代
3.常见的垃圾回收算法有哪些?原理?
  • 常见的垃圾回收算法有:引用计数法、标记清除法、标记压缩法、复制算法、分代算法 等。

引用计数法
原理

假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失败 时,对象A的引用计数器就-1,如果对象A的计数器的值为0,就说明对象A没有引用了, 可以被回收。
优缺点

优点:

实时性较高,无需等到内存不够的时候,才开始回收,运行时根据对象的计数器是否 为0,就可以直接回收。
在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报 outofmember 错误。
区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象。

缺点:

每次对象被引用时,都需要去更新计数器,有一点时间开销。
浪费CPU资源,即使内存够用,仍然在运行时进行计数器的统计。
无法解决循环引用问题。(大的缺点)

标记清除法

标记清除算法,是将垃圾回收分为2个阶段,分别是标记和清除。

标记:从根节点开始标记引用的对象。
清除:未被标记引用的对象就是垃圾对象,可以被清理。

优缺点

可以看到,标记清除算法解决了引用计数算法中的循环引用的问题,没有从root节点引用的对象都会被回收。
同样,标记清除算法也是有缺点的:

效率较低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用而言这个体验是非常差的。
通过标记清除算法清理出来的内存,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。

标记压缩算法

标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的清理未标记的对象,而是将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决了碎片化的问题。
优缺点

优缺点同标记清除算法,解决了标记清除算法的碎片化的问题,同时,标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响。
复制算法

复制算法的核心就是,将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制到另一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾的回收。

如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之,则不适合。

4.如何判断一个对象是否应该被回收?

在面试中经常会碰到这样一个问题(事实上笔者也碰到过):如何判断一个对象已经死去?
很容易想到的一个答案是:对一个对象添加引用计数器。每当有地方引用它时,计数器值加 1;当引用失效时,计数器值减 1.而当计数器的值为 0 时这个对象就不会再被使用,判断为已死。是不是简单又直观。然而,很遗憾。这种做法是错误的!为什么是错的呢?事实上,用引用计数法确实在大部分情况下是一个不错的解决方案,而在实际的应用中也有不少案例,但它却无法解决对象之间的循环引用问题。比如对象 A 中有一个字段指向了对象 B,而对象 B 中也有一个字段指向了对象 A,而事实上他们俩都不再使用,但计数器的值永远都不可能为 0,也就不会被回收,然后就发生了内存泄露。
所以,正确的做法应该是怎样呢?
在 Java,C#等语言中,比较主流的判定一个对象已死的方法是:可达性分析(Reachability Analysis).
所有生成的对象都是一个称为"GC Roots"的根的子树。从 GC Roots 开始向下搜索,搜索所经过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链可以到达时,就称这个对象是不可达的(不可引用的),也就是可以被 GC 回收了。无论是引用计数器还是可达性分析,判定对象是否存活都与引用有关!那么,如何定义对象的引用呢?
我们希望给出这样一类描述:当内存空间还够时,能够保存在内存中;如果进行了垃圾回收之后内存空间仍旧非常紧张,则可以抛弃这些对象。所以根据不同的需求,给出如下四种引用,根据引用类型的不同,GC 回收时也会有不同的操作:
1)强引用(Strong Reference):Object obj = new Object();只要强引用还存在,GC 永远不会回收掉被引用的对象。
2)软引用(Soft Reference):描述一些还有用但非必需的对象。在系统将会发生内存溢出之前,会把这些对象列入
回收范围进行二次回收(即系统将会发生内存溢出了,才会对他们进行回收。
3)弱引用(Weak Reference):程度比软引用还要弱一些。这些对象只能生存到下次 GC 之前。当 GC 工作时,无论内存是否足够都会将其回收(即只要进行 GC,就会对他们进行回收。)
4)虚引用(Phantom Reference):一个对象是否存在虚引用,完全不会对其生存时间构成影响。
关于方法区中需要回收的是一些废弃的常量和无用的类。
1.废弃的常量的回收。这里看引用计数就可以了。没有对象引用该常量就可以放心的回收了。
2.无用的类的回收。什么是无用的类呢?
A.该类所有的实例都已经被回收。也就是 Java 堆中不存在该类的任何实例;
B.加载该类的 ClassLoader 已经被回收;
C.该类对应的 java.lang.Class 对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
总而言之:

对于堆中的对象,主要用可达性分析判断一个对象是否还存在引用,如果该对象没有任何引用就应该被回收。而根据我们实际对引用的不同需求,又分成了 4 中引用,每种引用的回收机制也是不同的。对于方法区中的常量和类,当一个常量没有任何对象引用它,它就可以被回收了。而对于类,如果可以判定它为无用类,就可以被回收了。

5.解决过什么生产问题,怎么处理,用什么工具定位生产问题?

四、SpringBoot基础

1.Spring和SpringBoot有什么不同?

spring:

Spring框架为开发Java应用程序提供了全面的基础架构支持。它包含一些很好的功能,如依赖注入和开箱即用的模块,如:
Spring JDBC 、Spring MVC 、Spring Security、 Spring AOP 、Spring ORM 、Spring Test

springboot:

Spring Boot基本上是Spring框架的扩展,它消除了设置Spring应用程序所需的XML配置

Spring Boot的特点:

1:创建独立的spring应用。
2:嵌入Tomcat, Jetty Undertow 而且不需要部署他们。
3:提供的“starters” poms来简化Maven配置
4:尽可能自动配置spring应用。
5:提供生产指标,健壮检查和外部化配置
6:绝对没有代码生成和XML配置要求

五、安全漏洞

1.你了解的javaWeb项目的安全漏洞有哪些?原理?

1、SQL注入攻击

解决方法:

数据库安全通信包括SQL注入攻击的防范、安全设置、异常信息处理三个方面。

1.服务端Filter对访问者输入的字符进行过滤检验,但是攻击者经常把危险字符潜藏在用户输入的有效字符中完 成过滤检验。

2.通过正则表达式对页面的文本框输入的数据进行限制可以减少过滤检验存在的漏洞。

3.使用prepareStatment预编译sql语句

2、XSS跨站脚本攻击

解决方法:

1).输入过滤。对用户的所有输入数据进行检测,比如过滤其中的“<”、“>”、“/”等可能导致脚本注入的特殊字符,或者过滤“script”、“javascript”等脚本关键字,或者对输入数据的长度进行限制等等。同时,我们也要考虑用户可能绕开ASCII码,使用十六进制编码来输入脚本。因此,对用户输入的十六进制编码,我们也要进行相应的过滤。只要能够严格检测每一处交互点,保证对所有用户可能的输入都进行检测和XSS过滤,就能够有效地阻止XSS攻击。

2).输出编码。通过前面对XSS攻击的分析,我们可以看到,之所以会产生XSS攻击,就是因为Web应用程序将用户的输入直接嵌入到某个页面当中,作为该页面的HTML代码的一部分。因此,当Web应用程序将用户的输入数据输出到目标页面中时,只要用HtmlEncoder等工具先对这些数据进行编码,然后再输出到目标页面中。这样,如果用户输入一些HTML的脚本,也会被当成普通的文字,而不会成为目标页面HTML代码的一部分得到执行.
3、CSRF跨站请求伪造漏洞防护

解决方案:

配置FILTER拦截用户所有请求(POST/GET),对用户请求Referer头URL进行合法性校验。

4、URL链接注入漏洞防护

解决方案:

配置FILTER拦截用户所有请求,对用户参数进行关键字符校验进行合法性校验。
5、会话COOKIE中缺少HttpOnly防护

解决方案:

配置filter拦截器,将服务器端返回请求,向所有会话cookie中添加“HttpOnly”属性。

6、点击劫持漏洞(Clickjacking)防护

解决方案:

配置FILTER拦截器,在服务器端返回请求中,使用一个HTTP头“X-Frame-Options”值为SAMEORIGIN-同源策略 ,则frame页面的地址只能为同源域名下面的页面,防止点击劫持漏洞发生。

7、HTTP host 头攻击漏洞

解决方案:

配置FILTER拦截器,对请求输入HOST头信息进行信息安全性校验,防止HOST头信息被恶意篡改利用。

8、越权访问漏洞防护

解决方案:

配置FILTER拦截器,对请求所有URL进行拦截,对于需要进行授权的URL进行权限校验,防止用户越权访问系统资源。
9.弱口令漏洞

解决方案:最好使用至少6位的数字、字母及特殊字符组合作为密码。数据库不要存储明文密码,应存储MD5加密后的密文,由于目前普通的MD5加密已经可以被破解,最好可以多重MD5加密,或者多种加密方式叠加组合。

校验密码不能与用户名相同,修改密码时不能使用前五次或上次密码
增加验证码登录,增加暴力破解的难度
增加用户锁定机制
10.JSP页面抛出的异常可能暴露程序信息。

解决方案:自定义一个Exception,将异常信息包装起来不要抛到页面上。
11.本地缓存漏洞

解决方案:配置filter对存放敏感信息的页面限制页面缓存。如:

httpResponse.setHeader(“Cache-Control”,“no-cache”);

httpResponse.setHeader(“Cache-Control”,“no-store”);

httpResponse.setDateHeader(“Expires”,0);

httpResponse.setHeader(“Pragma”,“no-cache”);
12.文件上传漏洞。

解决方案:文件上传时在前台对文件后缀名进行验证,为避免通过特殊手段绕过了前端验证,在文件 保存时再进行一次验证,即前后台同时验证的道理。

13.Java WEB容器默认配置漏洞。

解决方案:最好删除,如需要使用它来管理维护,可更改其默认路径,口令及密码。
14.敏感信息泄露

解决方案:建议统一处理错误页面,将错误信息存储在日志中。
15.用户名密码未加密传输

解决方案:1.使用可逆的加密算法,在客户端使用js同时加密用户名和密码,在后台解密进行登录操作。(有风 险)2.使用不可逆加密算法在前台加密密码(只是密码),当然在数据库里存储的密码也是使用相同 算法加密的(安全性能较高)
16.未设置跨站注入过滤器

解决方案:在系统中设置拦截器,对sql语句和js语句进行拦截,具体需要拦截的词汇如下:

(\b(alert|iframe|frame|ascii|script|and|exec|execute|insert|select|delete|update|count|drop|chr|mid|master|truncate|delay|waitfor|char|declare|sitename|netuser|xp_cmdshell|or|like’|exec|execute|insert|create|”+”table|from|grant|use|group_concat|column_name|information_schema.columns|table_schema|union|where|”+”reg|between|having|groupby|groupby|waitfor|waitingfor|#|cast|convert|window|local|location|”+”–|like)\b)。

17.短信轰炸

解决方案:建议对发送的短信验证码进行频率限制,一段时间内仅仅发送多少条短信。
18.注释信息泄露

解决方案:及时删除注释代码,有价值代码在本地备份。
19.URL跳转

解决方案:改变传值方式,可以在前台传入对应type,根据type跳转到页面

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

康梓潼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值