java面试题(三)

简介: 1、Java 中会存在内存泄漏吗,请简单描述。 答: 理论上 Java 因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是 Java 被 广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无 用但可达的对象,这些对象不能被 GC 回收,因此也会导致内存泄露的发生。例如 Hibernate 的 Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收 这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close) 或清空(flush)一级缓存就可能导致内存泄露。下面例子中的代码也会导致内存泄露。import java.util.A

1、Java 中会存在内存泄漏吗,请简单描述。

答:

理论上 Java 因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是 Java 被

广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无

用但可达的对象,这些对象不能被 GC 回收,因此也会导致内存泄露的发生。例如

Hibernate 的 Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收

这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)

或清空(flush)一级缓存就可能导致内存泄露。

下面例子中的代码也会导致内存泄露。

import java.util.Arrays;

import java.util.EmptyStackException;

public class MyStack<T> {

   private T[] elements;

   private int size = 0;

   private static final int INIT_CAPACITY = 16;

   public MyStack() {

       elements = (T[]) new Object[INIT_CAPACITY];

   }

   public void push(T elem) {

       ensureCapacity();

       elements[size++] = elem;

   }

   public T pop() {

       if(size == 0)

           throw new EmptyStackException();

       return elements[--size];

   }

   private void ensureCapacity() {

       if(elements.length == size) {

           elements = Arrays.copyOf(elements, 2 * size + 1);

       }

   }

}


上面的代码实现了一个栈(先进后出(FILO))结构,乍看之下似乎没有什么明

显的问题,它甚至可以通过你编写的各种单元测试。然而其中的 pop 方法却存在

内存泄露的问题,当我们用 pop 方法弹出栈中的对象时,该对象不会被当作垃圾

回收,即使使用栈的程序不再引用这些对象,因为栈内部维护着对这些对象的过

期引用(obsolete reference)。在支持垃圾回收的语言中,内存泄露是很隐蔽的,

这种内存泄露其实就是无意识的对象保持。如果一个对象引用被无意识的保留起

来了,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其他对象,

即使这样的对象只有少数几个,也可能会导致很多的对象被排除在垃圾回收之外,

从而对性能造成重大影响,极端情况下会引发 Disk Paging(物理内存与硬盘的虚

拟内存交换数据),甚至造成 OutOfMemoryError。

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

答:

都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛

盾的。本地方法是由本地代码(如 C 代码)实现的方法,而抽象方法是没有实现

的,也是矛盾的。synchronized 和方法的实现细节有关,抽象方法不涉及实现细

节,因此也是相互矛盾的。

3、阐述静态变量和实例变量的区别。

静态变量是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的

任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷

贝;实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。

静态变量可以实现让多个对象共享内存。

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

答:

不可以,静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,在

调用静态方法时可能对象并没有被初始化。

5、如何实现对象克隆?

答:

有两种方式:

1). 实现 Cloneable 接口并重写 Object 类中的 clone()方法;

2). 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,代码如下。

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

import java.io.Serializable;

public class MyUtil {

   private MyUtil() {

       throw new AssertionError();

   }

   @SuppressWarnings("unchecked")

   public static <T extends Serializable> T clone(T obj) throws

           Exception {

       ByteArrayOutputStream bout = new ByteArrayOutputStream();

       ObjectOutputStream oos = new ObjectOutputStream(bout);

       oos.writeObject(obj);

       ByteArrayInputStream bin = new

               ByteArrayInputStream(bout.toByteArray());

       ObjectInputStream ois = new ObjectInputStream(bin);

       return (T) ois.readObject();

// 说明:调用 ByteArrayInputStream 或 ByteArrayOutputStream

       对象的 close 方法没有任何意义

// 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这

       一点不同于对外部资源(如文件流)的释放

   }

}

测试方法

import java.io.Serializable;

/**

* 人类

* @author 骆昊

*

*/

class Person implements Serializable {

   private static final long serialVersionUID = -9102017020286042305L;

   private String name; // 姓名

   private int age; // 年龄

   private Car car; // 座驾

   public Person(String name, int age, Car car) {

       this.name = name;

       this.age = age;

       this.car = car;

   }

   public String getName() {

       return name;

   }

   public void setName(String name) {

       this.name = name;

   }

   public int getAge() {

       return age;

   }

   public void setAge(int age) {

       this.age = age;

   }

   public Car getCar() {

       return car;

   }

   public void setCar(Car car) {

       this.car = car;

   }

   @Override

   public String toString() {

       return "Person [name=" + name + ", age=" + age + ", car=" +

               car + "]";

   }

}

/**

* 小汽车类

* @author 骆昊

*

*/

class Car implements Serializable {

   private static final long serialVersionUID = -5713945027627603702L;

   private String brand; // 品牌

   private int maxSpeed; // 最高时速

   public Car(String brand, int maxSpeed) {

       this.brand = brand;

       this.maxSpeed = maxSpeed;

   }

   public String getBrand() {

       return brand;

   }

   public void setBrand(String brand) {

       this.brand = brand;

   }

   public int getMaxSpeed() {

       return maxSpeed;

   }

   public void setMaxSpeed(int maxSpeed) {

       this.maxSpeed = maxSpeed;

   }

   @Override

   public String toString() {

       return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed +

               "]";

   }

}

class CloneTest {

   public static void main(String[] args) {

       try {

           Person p1 = new Person("Hao LUO", 33, new Car("Benz",

                   300));

           Person p2 = MyUtil.clone(p1); // 深度克隆

           p2.getCar().setBrand("BYD");

// 修改克隆的 Person 对象 p2 关联的汽车对象的品牌属性

// 原来的 Person 对象 p1 关联的汽车不会受到任何影响

// 因为在克隆 Person 对象时其关联的汽车对象也被克隆了

           System.out.println(p1);

       } catch (Exception e) {

           e.printStackTrace();

       }

   }

}

注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛

型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,

不是在运行时抛出异常,这种是方案明显优于使用 Object 类的 clone 方法克隆对

象。让问题在编译的时候暴露出来总是好过把问题留到运行时。

6、GC 是什么?为什么要有 GC?

答:

GC 是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误

的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动

监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放

已分配内存的显示操作方法。Java 程序员不用担心内存管理,因为垃圾收集器会

自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或

Runtime.getRuntime().gc() ,但 JVM 可以屏蔽掉显示的垃圾回收调用。

垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通

常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死

亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回

收器对某个对象或所有对象进行垃圾回收。在 Java 诞生初期,垃圾回收是 Java

最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过

境迁,如今 Java 的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常

觉得 iOS 的系统比 Android 系统有更好的用户体验,其中一个深层次的原因就在

于 Android 系统中垃圾回收的不可预知性。

补充:垃圾回收机制有很多种,包括:分代复制垃圾回收、标记垃圾回收、增量

垃圾回收等方式。标准的 Java 进程既有栈又有堆。栈保存了原始型局部变量,堆

保存了要创建的对象。Java 平台对堆内存回收和再利用的基本算法被称为标记和

清除,但是 Java 对其进行了改进,采用“分代式垃圾收集”。这种方法会跟 Java

对象的生命周期将堆内存划分为不同的区域,在垃圾收集过程中,可能会将对象

移动到不同区域:

伊甸园(Eden):这是对象最初诞生的区域,并且对大多数对象来说,

这里是它们唯一存在过的区域。

幸存者乐园(Survivor):从伊甸园幸存下来的对象会被挪到这里。

终身颐养园(Tenured):这是足够老的幸存对象的归宿。年轻代收集

(Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进终身

颐养园时,就会触发一次完全收集(Major-GC),这里可能还会牵扯到压缩,

以便为大对象腾出足够的空间。

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

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

-Xmn — 堆中年轻代的大小  

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

-XX:+PrintGCDetails — 打印 GC 的细节第 244 页 共 485 页

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

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

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

-XX:PrintTenuringDistribution — 设置每次新生代 GC 后输出幸存者

乐园中对象年龄的分布

-XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:设置老

年代阀值的初始值和最大值

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

7、Anonymous Inner Class(匿名内部类)是否可以继承它 类?是否可以实现接口?

答:

可以继承其他类或实现其他接口,在 Swing 编程和 Android 开发中常用此方式来

实现事件监听和回调。

8、内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?

答:

一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。

9、Java 中的 final 关键字有哪些用法?

(1)修饰类:表示该类不能被继承;(2)修饰方法:表示方法不能被重写;(3)修饰变

量:表示变量只能一次赋值以后值不能被修改(常量)。

10、日期和时间:

如何取得年月日、小时分钟秒?

如何取得从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的毫秒数?

如何取得某月的最后一天?

如何格式化日期?

答:

问题 1:创建 java.util.Calendar 实例,调用其 get()方法传入不同的参数即可获

得参数所对应的值。Java 8 中可以使用 java.time.LocalDateTimel 来获取,代码

如下所示。

public class DateTimeTest {

   public static void main(String[] args) {

       Calendar cal = Calendar.getInstance();

       System.out.println(cal.get(Calendar.YEAR));

       System.out.println(cal.get(Calendar.MONTH)); // 0 - 11

       System.out.println(cal.get(Calendar.DATE));

       System.out.println(cal.get(Calendar.HOUR_OF_DAY));

       System.out.println(cal.get(Calendar.MINUTE));

       System.out.println(cal.get(Calendar.SECOND));

// Java 8

       LocalDateTime dt = LocalDateTime.now();

       System.out.println(dt.getYear());

       System.out.println(dt.getMonthValue()); // 1 - 12

       System.out.println(dt.getDayOfMonth());

       System.out.println(dt.getHour());

       System.out.println(dt.getMinute());

       System.out.println(dt.getSecond());

   }

}

Calendar.getInstance().getTimeInMillis();

System.currentTimeMillis();

Clock.systemDefaultZone().millis(); // Java 8

Calendar time = Calendar.getInstance();

time.getActualMaximum(Calendar.DAY_OF_MONTH);

import java.text.SimpleDateFormat;

import java.time.LocalDate;

import java.time.format.DateTimeFormatter;

import java.util.Date;

class DateFormatTest {

   public static void main(String[] args) {

       SimpleDateFormat oldFormatter = new

               SimpleDateFormat("yyyy/MM/dd");

       Date date1 = new Date();

       System.out.println(oldFormatter.format(date1));

// Java 8

       DateTimeFormatter newFormatter =

               DateTimeFormatter.ofPattern("yyyy/MM/dd");

       LocalDate date2 = LocalDate.now();

       System.out.println(date2.format(newFormatter));

   }

}

相关文章
|
2天前
|
安全 算法 Java
java多线程面试题2019整理
java多线程面试题2019整理
|
2天前
|
算法 Java 调度
《面试专题-----经典高频面试题收集四》解锁 Java 面试的关键:深度解析并发编程进阶篇高频经典面试题(第四篇)
《面试专题-----经典高频面试题收集四》解锁 Java 面试的关键:深度解析并发编程进阶篇高频经典面试题(第四篇)
7 0
|
1天前
|
缓存 Java 数据库连接
java面试题目 强引用、软引用、弱引用、幻象引用有什么区别?具体使用场景是什么?
【6月更文挑战第28天】在 Java 中,理解和正确使用各种引用类型(强引用、软引用、弱引用、幻象引用)对有效的内存管理和垃圾回收至关重要。下面我们详细解读这些引用类型的区别及其具体使用场景。
10 3
|
9天前
|
缓存 安全 Java
Java线程面试题含答案
Java线程面试题含答案
|
2天前
|
负载均衡 NoSQL Dubbo
java分布式面试快问快答
java分布式面试快问快答
10 0
|
2天前
|
XML Java 数据库连接
面试必备!Java核心技术100+面试题
面试必备!Java核心技术100+面试题
|
3天前
|
SQL 网络协议 Java
技术经验分享:Java不会或做错的面试题总结
技术经验分享:Java不会或做错的面试题总结
11 0
|
3天前
|
安全 Java 中间件
《面试专题-----经典高频面试题收集一》解锁 Java 面试的关键:深度解析常见高频经典面试题(第一篇)
《面试专题-----经典高频面试题收集一》解锁 Java 面试的关键:深度解析常见高频经典面试题(第一篇)
10 0
|
3天前
|
安全 Java API
《面试专题-----经典高频面试题收集三》解锁 Java 面试的关键:深度解析并发编程基础篇高频经典面试题(第三篇)
《面试专题-----经典高频面试题收集三》解锁 Java 面试的关键:深度解析并发编程基础篇高频经典面试题(第三篇)
7 0
|
3天前
|
Java
Java面试题整理附答案【基础篇】
Java面试题整理附答案【基础篇】