JavaSE常见面试题(基本语法&面向对象&JVM)

【1】JDK、JRE、JVM三者之间的关系?

  • JDK(开发工具)包含JRE(运行环境)包含JVM(虚拟机:class运行规范的实现)

【2】java语言特点

  1. Java是强类型语言、数据齐全
  2. Java是面向对象语言
  3. Java有完善异常处理机制,保证程序健全运行
  4. 支持泛型、注解
  5. 有完善的配置文件与反射机制,是实现的框架的前提
  6. 支持多线程,封装了良好线程api,线程池、juc等技术
  7. 有健全垃圾回收机制,自动回收无用的垃圾,有丰富的垃圾回收器处理垃圾回收
  8. 有严格标准的class运行规范,jvm就是一套规范实现

【3】八大基本数据类型以及存储单位

  • byte(1)short(2)int(4)long(8)float(4)double(8)char(2)boolean(1)

【4】标识符规则和规范

规则:

  • 由26个英文字母大小写,0-9,_或$组成
  • 数字不可以开头
  • 不可以使用关键字和保留字,但包含关键字和保留字
  • Java中严格区分大小写,长度无限制。
  • 标识符不能包含空格

规范:

  • 包名:多单词组成时所有字母都小写
  • 类名、接口名:多单词组成时,所有单词的首字母大写
  • 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写
  • 常量名:所有字母大写。多单词时每个单词用下划线连接

【5】float f = 3.4;是否正确?

  • 不正确。3.4时双精度数,将双精度型(double)赋值给浮点型(float)属于向下转型会造成精度损失,因此需要强制类型转换float f = (float)3.4;或者写成float f = 3.4F;

【6】short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?

  • 对于short s1 = 1; s1 = s1 + 1;由于1是int类型,因此s1 + 1运算结果也是int型,需要强制转换类型才能赋值给short类型。而short s1 = 1; s1 += 1;可以正确编译,因为s1 += 1;相当于s1 = (short)(s1 + 1);其中有隐含的强制类型转换

【7】Java有没有goto?

  • goto是Java中的保留字,在目前版本的Java中没有使用。(根据James Gosling(Java之父)编写的《The Java Programming Language》一书的附录给出了一个Java关键字列表,其中有goto和const,但是这两个是目前无法使用的关键字,因此有些地方将其称之为保留字,其实保留字这个词应该有更广泛的意思,因为熟悉C语言的程序员都知道,在系统类库中使用过的有特殊意义的单词或单词的组合被视为保留字)

【8】数据类型转换

引用类型转化

  • 向上转型         List list = new ArrayList();
  • 强制转换(向下转型)类型转换异常  List list = new ArrayList(); ArrayList arraylist = (ArrayList) list

基本数据类型

  • 自动转换
  • 强制转换,有可能会出现精度溢出现象

【9】&和&&的区别,|和||的区别,>>和>>>的区别

  • &按位与(支持位运算)和&&短路与(不支持位运算)
  • &条件两边左边是假的,右边照常执行,&&条件两边左边是假的右边不执行
  • |按位或(支持位运算)和||短路或(不支持位运算)
  • |条件两边左边是真的右边照常执行,||条件两边是真的右边不执行
  • >>带符号右移动,正数移动完一定是正数,负数移动完一定是负数,>>>无符号右移动,结果一定是正数

【10】用最有效率的方法计算2乘以8

2<<3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)

补充:我们编写的类重写HashCode方法时,可能会看到如下所示代码,其实我们不太理解为什么使用这样的乘法运算来产生哈希码(散列码),而且为什么这个数是个素数,为什么通常选择31这个数?

选择31是因为可以用移位和减法运算来代替乘法,从而得到更好的性能。说到这里你可能已经想到了:31 * num 等价于(num << 5) - num ,左移5位相当于乘以2的5次方再减去自身就相当于乘以31,现在的JVM都能自动完成这个优化。

【11】谈谈你了解的编码

常见的编码有ASCII,GBK,Unicode,UTF-8

  • ASCII码支持英美字母的编码
  • GBK国家标准扩展码
  • Unicode万国码,它是一个规范,它只制定了各国各种字符对应的01表示,但没有实现这些01表示在计算机中如何存储。
  • UTF-8是Unicode的一种最好的实现方式(目前这么认为)

【12】char型变量中能不能存贮一个中文汉字,为什么?

char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个char类型占2个字节(16比特),所以放一个中文是没问题的。

补充:使用Unicode意味着字符在JVM内部和外部有不同的表现形式,在JVM内部都是Unicode,当这个字符被从JVM内部转移到内部时(例如存入文件系统中),需要进行编码转换。所以Java中有字节流和字符流,以及字符流和字节流之间进行转换的转换流,如InputStreamReader和OutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务。

【13】在Java中,如何跳出当前的多重嵌套循环?

在最外层循环前加一个标记如A,然后用break A;可以跳出多重循环。(java中支持带标签的break和continue语句,作用有点类似于C和C++中的goto语句,但是就像避免使用goto一样,应该避免使用带标签的break和continue,因为它不会让你的程序变得更优雅,很多时候甚至有相反的作用,所以这种语法其实不是很好)

【14】流程控制关键字

break、continue;break可以用于switch和循环结构,用于当前结束switch或循环结构。break配合标记符跳出指定循环结构,continue用于跳出本次循环执行,直接执行下次循环。

【15】switch支持的数据类型

支持byte、short、int、char、String(jdk7)、枚举

【16】while和do...while的区别

while先判断条件再执行,do...while限制性再判断条件是否成立,最少执行一次

【17】数组中有没有length()方法?String有没有length()方法?

数组没有length()方法,有length的属性。String有length()方法。JavaScript中,获得字符串的长度是通过length属性得到的,这一点容易和Java混淆。

【18】面向过程和面向对象的区别

面向过程以函数为单位,以过程为重心。面向对象以类为单位,有严格的类、接口、抽象类的继承实现接口,以结果为重心。

【19】面向对象的特征有哪些方面?

面向对象的特征主要有以下几个方面:

抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。

封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只想外界提供最简单的编程接口。

多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事情:

  1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法);
  2. 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同表现出不同的行为)。

【20】成员变量和局部变量的区别

局部变量在栈内存,成员变量在堆内存;局部变量必须初始化,成员变量有默认值;局部变量作用域在当前方法中,成员变量作用域在整个类中。

【21】阐述静态变量和实例变量的区别

静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现多个对象共享内存。

补充:在Java开发中,上下文类和工具类中通常有大量的静态成员。

【22】简述java中的值传递

方法的传递始终传递的是值的副本。基本类型传递的是数值的副本,引用类型传递的是地址的副本。String类型按基本类型处理

【23】简述this与super关键字

this可以出现在普通方法和构造方法中,不可以出现在static修饰的代码块中,this.属性,this.方法this()构造方法不能在普通方法中调用,this()构造方法只能出现在构造方法第一行

super代表父类引用的属性或方法或构造器,super.属性,super.方法;super()构造方法必须写在构造起第一行。

【24】重载与重写的区别

重载同一个类中,方法名相同,参数个数或列表不同。和修饰符,返回类型,抛出异常都无关。

重写是发生子父类中,子类方法和父类方法名相同,子类重写父类的方法。子类的访问权限大于等于父类;返回值必须一致;参数列表一致;抛出异常小于等于父类异常。private、static、final修饰的方法不能重写。

【25】构造器(construtor)是否课可被重写(override)?

构造器不能被继承,因此不能被重写,但可以被重载。

【26】理解JavaBean、pojo、bo、vo、dto等

JavaBean是可重用组件:要满足如下要求:

  1. 私有字段对应共有的setter和getter方法
  2. 无参数public修饰的构造器
  3. 必须是publc修饰的类
  • pojo、po、bo、vo、dto都是JavaBean的一种,都是可重用组件,只是各自功能不同
  • po对应的数据库表的一一映射关系
  • vo对应的是页面展示的字段一一映射
  • bo对应的是一套业务(service)层的组件
  • pojo是负责各个JavaBean的数据格式转换,例如po转vo,vo转po
  • dto属于数据传输对象,主要是视图层与业务层之间的数据传输,多数情况下,dto内部的数据来自多个表

【27】访问权限修饰符

private 默认 protected public

【28】==与equals区别

==既能比较基本数据类型又能比较引用类型;基本类型比较值,引用类型比较地址

equals只能比较引用类型;默认比较的是内存地址,重写后比较的是内容

【29】简述toString、HashCode方法

toString默认是对象的地址的字符串表示,重写后是对象内容的字符串表示

HashCode默认是根据真实内存地址经过计算得出的哈希值,满足相同的地址,哈希值相同,不同的地址,哈希值尽量不同,相同容易引起哈希冲突。

HashCode重写后满足equals返回true,HashCode值一样,equals返回false,HashCode值尽量不一样,一样就产生了哈希冲突。

哈希冲突是指,像一个map集合中存入数据,数据不同,哈希值相同,map集合的数组下标是根据哈希值随机的,导致同一个位置随机到了两个不同的对象,这种冲突叫做哈希冲突,也叫哈希碰撞。HashMap采用链表结构解决冲突的。

【30】简述static关键字作用

被static修饰的成员,代表着静态成员,多个对象共享的成员,属于类,直接类名.调用,随着类的加载而加载

static可以修饰成员变量,修饰成员方法,修饰内部类,修饰代码块

【31】final、fianlly、finalize的区别

final:修饰符(关键字)有三种用法:如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承。将变量声明为final,可以保证他们在使用中不被改变,被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为final的方法同样只能使用,不能在子类中被重写。

finally:通常放在try...catch...的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中

finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作

public class Demo1 {
    public static void main(String[] args) {
        User user = new User("张三");
        user = null;
        System.gc();//主动调用垃圾回收器
        System.out.println("退出");
    }
}
class User{
    private String name;

    public User(String name) {
        this.name = name;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("销毁前释放资源");
        super.finalize();
    }
}

【32】异常体系

           throwable

                |

exception        error

        |

编译时异常   运行时异常

【33】异常的两种处理方式

处理异常的两种方式是try....catch...finally与throws;try...catch...finally直接处理异常,throws将异常交给调用者处理

【34】throw与throws的区别

throw写在方法体内,throws写在方法头,throw代表手动抛出异常;throws代表该方法可能出现的系统异常。

(关键点:位置区别,功能区别)

【35】简单介绍一下枚举的使用

枚举的本质还是类,枚举都自动继承了Enum类,不能再去继承其他类,枚举可以实现接口;定义枚举用enum关键字,枚举常量用逗号隔开,分号结束,每个枚举值都是根据当前类的私有构造器创建的静态常量

(关键点:枚举的本质,如何定义枚举)

【36】int和Integer有什么区别

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

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

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

(关键点:为什么会有Integer)

【37】解释内存中的栈(stack)、堆(heap)、和方法区(method area)的用法

通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场(栈帧)保存都适用JVM的栈空间;而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代回收集算法,所以对空间还可以细分为新生代和老年代,再具体一点可以分为Eden(新生代区也叫伊甸园区)、Survivor(幸存区)(又可分为From Survivor和To Survivor)、Tenured(老年代);方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、“hello”和常量都是放在常量池中,常量池是方法区的一部分。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverFlowError(栈内存溢出),而堆和常量池空间不足则会引发OutOfMemoryError(堆空间溢出)

String str = new String("hello");上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而"hello"这个字面量是放在方法区的。

补充:运行时常量池相当于Class文件常量池具有动态性,Java语言并不要求常量一定只有编译期间才能产生,运行期间也可以将新的常量放在池中,String类的intern()方法就是这样的。

【38】两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对

不对,如果两个对象x和y满足x.equals(y) == true,他们的哈希码(hash code)应当相同。Java对于equals方法和hashcode方法是这样规定的

如果两个对象相同(equals方法返回true),那么他们的hash code值一定要相同

如果两个对象的hash code相同,它们并不一定相同。当然,你也未必按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。

(关键点:不对,正确的做法,为什么这么做)

补充:关于equals和hashCode方法,很多Java程序员都知道,但是equals方法和hashCode方法还有注意一下几点:

equals方法的:首先equals方法必须满足自反性(x.equals(x)必须返回true)、对称性(x.equals(y)返回true时,y.equals(x)页也必须返回true)、传递性(x.equals(y)和y.equals(z)都返回true时,x.equals(z)也必须返回true)和一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值),而对于任何非null值的引用x,x.equals(null)必须返回false。实现高质量的equals方法的诀窍包括:

  1. 使用==操作符检查“参数是否为这个对象的引用”
  2. 使用instanceof操作符检查“参数是否为正确的类型”
  3. 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配
  4. 编写完equals方法后,问自己它是否满足对称性、传递行、一致性
  5. 重写equals时总是要重写hashcode
  6. 不要将equals方法参数中的Object对象替换为其他的类型,在重写时不要忘掉@Override注解

【39】当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是址传递?

是值传递。Java语言的调用只支持参数的值传递。当这个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对象引用的改变是不会影响到调用者的。C++和C#中可以通过穿引用或输出参数来改变传入的参数的值。在C#中可以编写如下所示代码,但是在Java中却做不到

using System;
namespace CS01 {
    class Program {
        public static void swap(ref int x, ref int y) {
            int temp = x;
            x = y;
            y = temp;
        }
        public static void Main (string[] args) {
            int a = 5, b = 10;
            swap (ref a, ref b);
            // a = 10, b = 5;
            Console.WriteLine ("a = {0}, b = {1}", a, b);
        }
    }
}

说明:Java中没有传引用实在是非常的不方便,这一点在Java8中仍然没有得到改进,正是如此在Java编写的代码中才会出现大量的Wrapper类(将需要通过方法调用修改的引用置于一个Wrapper类中,再将Wrapper对象传入方法),这样的做法只会让代码变得臃肿,尤其是让C和C++转型为Java程序员的开发者无法容忍

【40】描述一下JVM加载class文件的原理机制?

JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、链接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入一个.class文件,然后产生所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入链接阶段,这一阶段包括验证、准备(为静态变量分配内容并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:

  1. 如果存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
  2. 如果类中存在初始化语句,就依次执行这些初始化语句

类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(ClassLoader的子类)。从Java2开始,类加载过程采取了双亲委派机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的BootStrap是根加载器,其他的加载器有一切仅有一个父类加载器。类的加载首先请求符加载器加载,符加载器无能为力时由其子类加载器自行加载。JVM不会向Java程序提供对BootStrap的引用。下面是关于几个类加载器的说明:

  • BootStrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar)
  • Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是BootStrap;
  • System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载类的默认父加载类。

【41】请介绍一下JVM内存图

JVM内存分为类加载阶段和类运行阶段;

类加载阶段主要由类加载器完成,请参照40题

类运行参与的内存主要包含:堆、栈、方法区、请参照37题

【42】抽象类和接口有什么异同?

抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是private、默认、protected、public的,而接口中的成员全部都是public的。抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。

补充:jdk8中,接口中可以有静态方法、默认方法

【43】Java中会存在内存泄漏吗,请简单简述

理论上Java因为有垃圾回收机制(GC)不会存在内存泄漏问题(这也是Java被广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄漏的发生。例如hibernate的Session(一级缓存)还有mybatis的SqlSessionFactory中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)可能导致内存泄漏。

这里可以了解一下可达性分析,如果跟GCROOT没关系就会被回收,下面是GCRoot对象

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。(可以理解为引用栈帧中的本地变量表的所有对象)
  2. 方法区中静态属性引用的对象(可以理解为引用方法区改静态属性的所有对象)
  3. 方法区中常量引用的对象(可以理解为引用防区中常量的所有对象)
  4. 本地方法栈中(Native方法)引用的对象(可以理解为:引用Native方法的所有对象)

【44】抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时synchronized修饰?

都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized和方法的实现细节有关,抽象方法不设计实现细节,因此也是互相矛盾的。

【45】是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用?

不可以,静态方法还能访问静态成员,因为非静态方法的调用要先创建对象,在调用静态方法时可能并没有被初始化。

【46】一个“.java”源文件中是否可以包含多个类(不是内部类)?有什么限制?

可以,但一个源文件中最多只能有一个公开类(public class)而且文件名必须和公开类的类名完全保持一致。

【47】数据类型之间的转换:如何将字符串转换为基本数据类型?如何将基本数据类型转换为字符串?

调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)即可返回相应基本类型;一种方法是将基本数据类型与空字符串("")连接(+)即可获得其所对应的字符串;另一种方法是调用String类中的VlaueOf()方法返回相应字符串

【48】Error和Exception有什么区别?

Error表示系统级的错误后劲儿程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不能指望程序能处理这样的情况;Exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序正常运行,从不会放生的情况。

【49】try{}里面有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会执行,什么时候被执行,在return前还是后?

会执行,在方法调用者前执行

注意:在fianlly中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕后在向调用者返回其值,然而如果在finally中修改了返回值,又不会影响return返回结果,因为在执行try语句时,已经记录了return值。显然,在finally中返回或者修改返回者会对程序造成很大的困扰。

【50】列出一些你常见的运行时异常

  • ArithmeticException(算术异常)

  • ClassCastException (类转换异常)

  • IllegalArgumentException (非法参数异常)

  • IndexOutOfBoundsException (下标越界异常)

  • NullPointerException (空指针异常)

  • SecurityException (安全异常)

【51】简述Java中四种引用对象方式

强引用:在Java中常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。

软引用:软引用需要用Solft Reference类来实现,对于只有软引用的对象来说,当系统内存足够时他不会被回收,当系统内存空间不足时他会被回收。软引用通常用在对内存敏感的程序中

弱引用:弱引用需要用WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,总会回收该对象占用的内存

虚引用:虚引用需要PhantomReference类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。

【52】GC是什么,为什么要有GC?

GC是垃圾收集的意思,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的现实操作方法。Java程序员不用担心内存管理,因为垃圾收集器自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc或Runtime.getRuntime.gc(),但JVM可以屏蔽掉显示的垃圾回收调用。

【53】简述GC垃圾回收机制?

Java对从GC的角度可以细分为:新生代和老年代区。其中新生代又分为伊甸园区、幸存区Form,幸存区to

新生代:是用来存放新生的对象。一般占据堆的1/3空间。由于频繁创建对象,所以新生代会频繁出发MinorGC(轻量级垃圾回收)进行垃圾回收。新生代又分为Eden区、ServivorFrom、ServivorTo三个区。三个区域有不同职责:

  • Eden区:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收
  • ServivorFrom:记录上一次GC的幸存者,并作为这一次GC的扫描者。
  • ServivorTo:保留了一次MinorGC过程中的幸存者

整个MinorGC采用复制算法,具体过程如下:

1、eden、servivorFrom复制到servivorTo,年龄+1

首先,把Eden和ServivorFrom区域中存活的对象复制到ServivorTo区域(如果有对象的年龄以及达到了老年的标准,则复制到老年代区),同时把这些对象的年龄+1(如果ServivorTo不够位置了就放到老年区)

2、清空eden、servivorFrom

然后清空Eden和ServivorFrom中的对象

3、ServivorTo和ServivorFrom互换

最后,ServivorTo和ServivorFrom互换,原ServivorTo成为下一次GC时的ServivorFrom区

老年代

主要存放应用程序中生命周期长的内存对象。老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC(重量级)前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。MajorGC采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出OOM(Out Of Memory)异常

永久代

指内存的永久保存区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域,它和存放实例的区域不同,GC不会在主程序运行期间对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常

然而在垃圾回收过程中如何判断对象是否可以被回收呢?Java使用了可达性分析的方法。通过一系列的“GCRoots”对象作为起点搜索。如果在“GCRoots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过标记过程。两次标记后仍然是可回收对象,则面临回收。

GC roots对象包含:

  1. 虚拟机栈(栈帧的本地变量表)中引用的对象

  2. 方法区中静态属性引用的对象

  3. 方法区中常量属性引用的对象

  4. 本地方法栈中JNI(native修饰方法)引用的对象

与垃圾回收相关的 JVM 参数:

  • -Xms / -Xmx — 堆的初始大小 / 堆的最大大小

  • -Xmn — 堆中年轻代的大小

  • -XX:-DisableExplicitGC — 让 System.gc()不产生任何作用

  • -XX:+PrintGCDetails — 打印 GC 的细节

  • -XX:+PrintGCDateStamps — 打印 GC 操作的时间戳

  • -XX:NewSize / XX:MaxNewSize — 设置新生代大小/新生代最大大小

  • -XX:NewRatio — 可以设置老生代和新生代的比例

  • -XX:PrintTenuringDistribution — 设置每次新生代 GC 后输出幸存者乐园中对象年龄的分布

  • -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:设置老年代阀值的初始值和最大值

  • -XX:TargetSurvivorRatio:设置幸存区的目标使用率

【54】简述标记清除算法、复制算法、标记整理算法?

标记清除算法:最基础的垃圾回收算法,分两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。如图

从图中我们就可以发现,该算法最大的问题是内存碎片化严重,后续可能发生对象不能找到可利用空间的问题。

复制算法:为了解决Mark-Sweep算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的凉快。每次只使用其中一块,当这一块内存存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉,如图:

这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话,Copying算法的效率会大大降低

标记整理算法:

结合了以上两个算法,为了避免缺陷而提出。标记阶段和Mark-Sweep算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。如图:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值