Java中实现多态的机制是什么?
靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动
态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变
量的类型中定义的方法。
阐述静态变量和实例变量的区别?
静态变量: 是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不
管创建多少个对象,静态变量在内存中有且仅有一个拷贝;
实例变量: 必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对
象共享内存。
为什么要用 clone?
需要两个或两个以上完全的对象,可以通过clone()方法来实现,也是最高效的手段,是由于简单的赋值语句不能满足需求。
new 一个对象的过程和 clone 一个对象的过程区别
new操作符的本意是分配内存。程序执行到new操作符时,首先去看new操作符后面的类型,因为知道了类型,
才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用
操纵这个对象。 clone在第一步是和new相似的,都是分配内存,调用clone方法时,分配的内存和原对象(即调
用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域,填充完成之后,clone方法返
回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。
深拷贝和浅拷贝
简单来说就是一个对象中存在有引用类型的变量,如果拷贝时只是对该变量的引用拷贝了一份则是浅拷贝,相反则是深拷贝。
如果想要深拷贝一个对象,这个对象必须要实现Cloneable接口,实现clone
方法,并且在 clone 方法内部,把该对象引用的其他对象也要 clone 一份,这就要求这个被引用的对象必须也要实现Cloneable接口并且实现clone方法。
接口和抽象类总结:
抽象类可以没有抽象方法。
可以定义具体的方法和变量。
可以有静态方法和成员变量。
可以有private,public,protected和default等修饰。
可以定义构造器。
接口:
不能有构造器和静态方法。
成员变量必须是常量。
必须都是抽象方法。
只能够被public所修饰。
相同点:
1.都不能被实例化。
2.可以将抽象类和接口类型作为引用类型。
3.一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。
面向对象的三大特征:
(描述 举例 作用)
封装:隐藏方法的实现的细节,能够是实现模块化编程,提高代码的复用程度。
继承:子类可以通过继承拥有父类的属性和方法,接口可以实现多继承。
多态:同一种类下的对象,有不同的特征。
试述Forward和Redirect的区别
(1)转发(Forword)是服务器行为,重定向(Redirect)是客户端行为
**转发**:可以通过HttpServletRequest对象的getRequestDispatcher()方法链式调用forward()方法实现,如
request.getRequestDispatcher().forward()。
**重定向**:是利用服务器返回的状态吗来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通
过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302,则浏览器会
到新的网址重新请求该资源。转发可以通过HttpServletResponse对象的sendRedirect()方法实现。
(2)地址栏显示: 转发是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来, 然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址. 重定向是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL。
(3)数据共享: 转发:转发页面和转发到的页面可以共享request里面的数据. 重定向:不能共享数据。
(4)实际运用: 转发:一般用于用户登陆的时候,根据角色转发到相应的模块. 重定向:一般用于用户注销登陆时返回主页面和跳转到其它的网站等。
(5)效率: 转发:高. 重定向:低。
简述nginx
Nginx是一款轻量级的web服务器(反向代理服务器,负载均衡服务器,动静分离服务器)。
(1)反向代理
a.正向代理(vpn):为客户端做代理,相当于客户端中介。代理客户端去访问服务器。
(客户端->正向代理)->服务器,例如,(用户->VPN)->google,VPN就是用户的正向代理。
b.反向代理(nginx/apache):为服务器做代理,相当于服务端中介,代理服务器接受客户端请求。
客户端-> (反向代理->服务端) ,就如,客户端 -> (nginx-> tomcat),nginx就是tomcat的反向代理服务器。
假设:买房的人是客户端,卖方的人是服务端:
zs想买房,zs找了一个朋友ls帮忙去买,那么ls就是zs的正向代理服务区),(zs->ls)->卖房方
zl有一套房打算卖掉,zl找了一个朋友sq去卖,那么sq就是反向代理服务器,买房方->(sq->zl)
(2)负载均衡
分流客户端请求,均衡集群服务端压力。 Nginx支持的weight轮询(默认)、ip_hash、fair、url_hash四种负载均衡
调度算法。
(3)动静分离
分离静态请求和动态请求,将动态请求发送给web服务器,并给静态请求做缓存(或cdn加速)。
客户端请求 -> 静态请求/动态请求
静态请求:静态缓存/cdn加速
动态请求: tomcat(web服务器)
Nginx优点:
(1)高并发连接: 官方测试Nginx能够支撑5万并发连接,实际生产环境中可以支撑2~4万并发连接数。
(2)Nginx为开源软件,成本低廉
(3)稳定性高:用于反向代理时,非常稳定
(4)支持热部署能够在不间断服务的情况下,进行维护。
线程打印
问题:请用两种以上方法实现“多线程交替打印123123123...”
建立三个线程,第一个线程打印1、第二个线程打印2、第三个线程打印3;要求三个线程交替打印,123123123123……
方法一
package interview;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//多线程交替打印123123123...(两种以上方法实现)
锁方式一: lock.lock();/unlock() : await() signal()
锁方式二: synchronized :wait() notify
public class LoopPrint {
int num = 1;
//只有一个变量,因此只需要一把锁。但需要通知三个线程,因此需要三个“条件通知”
Lock lock = new ReentrantLock();//一把锁
//三个线程 的通知
Condition condition1 = lock.newCondition();//通知线程1打印
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
/*
num : 打印的数字1/2/3
线程1: 判断num是否为1,如果不是,则等待;如果是,则打印,打印完后 [通知]线程2
线程2:判断num是否为2,如果不是,则等待;如果是,则打印,打印完后 [通知]线程3
线程3:判断num是否为3,如果不是,则等待;如果是,则打印,打印完后 [通知]线程1
123123123...
*/
public static void main(String[] args) {
LoopPrint print = new LoopPrint() ;
//打印1的线程
new Thread( ()-> {
while(true){
print.print1();
}
} ).start(); //lambda表达式
//打印2的线程
new Thread( ()-> {
while(true){
print.print2();
}
} ).start();; //lambda表达式
//打印3的线程
new Thread( ()-> {
while(true){
print.print3();
}
} ).start();; //lambda表达式
}
//打印“1”
public void print1() {
//a b c -> num
lock.lock();
try {
//线程1: 判断num是否为1,如果不是,则等待;如果是,则打印,打印完后 [通知]线程2
if (num != 1) {
condition1.await();
}
System.out.println(1);
num = 2 ;
//通知线程2
condition2.signal(); // signal()相当于notify() ,await()相当于wait()
}catch (Exception e){
System.out.println(e);
}finally {
lock.unlock();
}
}
//打印“2”
public void print2() {
//a b c -> num
lock.lock();
try {
if (num != 2) {
condition2.await();
}
System.out.println(2);
num =3 ;
//通知线程3
condition3.signal(); // signal()相当于notify() ,await()相当于wait()
}catch (Exception e){
System.out.println(e);
}finally {
lock.unlock();
}
}
public void print3() {
//a b c -> num
lock.lock();
try {
if (num != 3) {
condition3.await();
}
System.out.println(3);
num = 1;
//通知线程1
condition1.signal(); // signal()相当于notify() ,await()相当于wait()
}catch (Exception e){
System.out.println(e);
}finally {
lock.unlock();
}
}
}
方法二
package interview;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
//1 2 3
//三个信号量
Semaphore sem1 = new Semaphore(1);
Semaphore sem2 = new Semaphore(0);
Semaphore sem3 = new Semaphore(0);
public static void main(String[] args) {
SemaphoreDemo sem = new SemaphoreDemo();
new Thread( ()-> sem.print1() ).start();
new Thread( ()-> sem.print2() ).start();
new Thread( ()-> sem.print3() ).start();
}
public void print1(){
print("1",sem1,sem2 ) ;
}
public void print2(){
print("2",sem2,sem3 ) ;
}
public void print3(){
print("3",sem3,sem1 ) ;
}
/*
value:当前值 1 2 3?
current:当前谁用于许可证
next:下一个谁那许可证
*/
private void print(String value , Semaphore current,Semaphore next ){
while(true){
try {
current.acquire();//当前信号量 获取许可证
System.out.println( Thread.currentThread().getName()+"***"+ value);
Thread.sleep(1000);
next.release();//将许可证传递给下一个
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
如何给final修饰成员变量的初始化赋值?
这个问题有两种情况。
情况一:没有static
只有final,没有static时,既可以通过=直接赋值,也可以通过构造方法赋值,如下。
a.通过=直接赋值
final int num = 10 ;
b.通过构造方法给final赋初值(注意:所有的构造方法 都必须给final变量赋值)
public class FinalDemo01 {
final int num ;//final修饰的变量,没有默认值
public FinalDemo(){
this.num = 10 ;
}
public FinalDemo(int num ){
this.num = num ;
}
//构造方法的作用? 实例化对象new(任意一个构造方法都可以实现)
public static void main(String[] args) {
// FinalDemo demo = new FinalDemo();
// System.out.println(demo.num);
new FinalDemo(10);
}
}
情况二:有static
static final int num = 10 ;
既有final,又有static时,只能通过=初始化值,不能同构方法赋值。 总体是因为static变量是在构造方法之前执行的,具体的原因见以下三点:
1)、static修饰的变量,执行时机:类加载过程中执行,并且会被初始化为 默认值0 null ..
class A{
static int a = 10 ; //a ->0 -> 10
}
2)、final修饰的变量,执行时机:运行时被初始化(直接赋值,也可以通过构造方法赋值)
3)、static final修饰的变量,执行时机: 在javac时(编译)生成ConstantValue属性,在类加载的过程中根据ConstantValue属性值为该字段赋值。ConstantValue属性值 没有默认值,必须显示的通过=赋值。
String相关问题
1、String 和 StringBuffer、StringBuilder 的区别是什么?String 为什么是不可变的?
可变性
常见错误回答1:
String类是final修饰的,因此String中的字符串不可变。实际上,final修饰的类,只能保证该类不能被继承,与字符串是否改变没有关系;
常见错误回答2:
在String源码中,定义存储字符串的源码是private final char value[] , 由于value[]是final修饰的,因此因此String中的字符串不可变。实际上,此时final修饰的是引用类型,只能保证引用的对象地址不能改变,但对象的内容(即字符串的内容)是可以改变的。
正确回答:
String类的不可变性实际在于作者的精心设计。例如,如果让你设计一个getXxx(String name)方法,你既可以设计成以下形式:
String getXxx(String name){
return name ;
}
也可以设计成以下形式:
String getXxx(String name){
return new Other(name) ;
}
如果设计成形式一,那么取到的值就是输入值本身;如果设计成形式二,取到的值就是一个新对象。简言之,如何设计一个方法的返回值,归根节点还是“看作者心情”。String不可变的原因也是一样的,是由于编写String的作者精心设计,所以导致了String类的不可变性。
如果要刨根问底,研究“作者的心情,究竟是如何的?”,即到底是如何设计成String类的不可变性的,也可以参阅《Effective Java》中 “使可变性最小化” 中对 “设计不可变类” 的解释,具体如下:
不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并且在对象的整个生命周期内固定不变。为了使类不可变,要遵循下面五条规则:
- 不要提供任何会修改对象状态的方法。
- 保证类不会被扩展。 一般的做法是让这个类称为 final 的,防止子类化,破坏该类的不可变行为。
- 使所有的域都是 final 的。
- 使所有的域都成为私有的。 防止客户端获得访问被域引用的可变对象的权限,并防止客户端直接修改这些对象。
- 确保对于任何可变性组件的互斥访问。 如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用。
在 Java 平台类库中,包含许多不可变类,例如 String , 基本类型的包装类,BigInteger, BigDecimal 等等。综上所述,不可变类具有一些显著的通用特征:类本身是 final 修饰的;所有的域几乎都是私有 final 的;不会对外暴露可以修改对象属性的方法。通过查阅 String 的源码,可以清晰的看到这些特征。
2、是否可以继承String
String类是final类,不可以被继承。继承 String 本身就是一个错误的行为,对 String 类型最好的重用方式是关联关系(Has-A)和依赖关系(UseA)而不是继承关系(Is-A)。
3、String、 StringBuffer、 StringBuilder 的区别?
1)、可变不可变
String:字符串常量,在修改时不会改变自身;若修改,等于重新生成新的字符串对象。
StringBuffer:在修改时会改变对象自身,每次操作都是对 StringBuffer 对象本身进行修改,不是生成新的对
象;使用场景:对字符串经常改变情况下,主要方法: append(), insert()等。
2) 、线程是否安全
String:对象定义后不可变,线程安全。
StringBuffer:是线程安全的(对调用方法加入同步锁),执行效率较慢,适用于多线程下操作字符串缓冲区
大量数据。
StringBuilder:是线程不安全的,适用于单线程下操作字符串缓冲区大量数据。
3) 、共同点
StringBuilder 与 StringBuffer 有公共父类 AbstractStringBuilder(抽象类)
StringBuilder、 StringBuffer 的方法都会调用 AbstractStringBuilder 中的公共方法,如 super.append(...)。
只是 StringBuffer 会在方法上加 synchronized 关键字,进行同步。最后,如果程序不是多线程的,那么使用
StringBuilder 效率高于 StringBuffer。
TCP三次握手和四次握手
原因:任何数据传输都无法保证绝对的可靠性。
三次连接一般是指建立连接。
三次握手:
第一次、客户端向服务端发送请求,询问服务端是否接收到请求。
第二次、服务端向客户端回信,已接收到请求,你是否可以接收我的回信。
第三次、客户端回信可以接收到。
这里syn是指同步号一般是个标识号,seq是序列号,ack则是上一次的seq+1。
四次连接一般是指关闭连接。(多了一次:服务端在收到客户端的关闭请求后,不能立刻关闭,因此还需要处理最后一次请求的数据,请求全部数据处理完毕之后,才能再向客户端发出关闭请求。)
equals和hashcode
1、==和equals()的区别
1)基本类型的变量是没有equals()方法的。如果比较的两个对象是基本类型的变量==则就是比较的两个变量的内容。
2)如果比较的是两个引用类型的对象,且没有重写equals()方法是则无论是==还是equals()方法都是比较的两个对象的地址值。
3)如果比较的是两个引用类型的对象,且重写了equals()并且符合要求则比较的是两个对象的内容是否相同。
2、举例说明,如何重写equals()方法?
public class Person { //如果含有其他引用类型的属性则也需要重写
private String name;
private int age ;
public Person() {
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
...
//约定: 如果name和age相同,则返回true
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
//name age
if(obj instanceof Person){
Person per = (Person)obj ;
//用传入的per,和当前对象this比较
if(this.name.equals(per.getName()) && this.age==per.getAge() ){
return true ;
}
}
return false;
}
}
3、重写equals方法为什么要重写hashcode方法?
1)两个对象相等(引用地址),则hashcode一定相同。
2)如果两个对象的hashcod相同,这俩对象不一定相同。
3)两个对象不等(引用地址),hashcode不一定不等(可能会出现地址冲突)。
4)如果两个对象的hashcod不同,这俩对象一定不同。
4、总结
1).根据hashcode 快速定位 (提高效率,避免了在大量集合中 由于遍历带来的效率问题)。
2).根据equals判断内容是否相同 (判断 正确性)。
3).如果要判断元素的内容 是否相同,就要重写hashcode和equals()。
HashSet和HashMap的区别
通过查看HashSet的源码我们可以发现:HashSet的底层就是new了一个HashMap对象。
public HashSet() {
map = new HashMap<>();
}
//所有hashset中增加的Value 都是一个常量值 PRESENT (new Object())
public boolean add(E e) {
//当HashMap第一次向集合中增加元素时,没有相同的key则会返回空,否则会返回替换掉的那个元素的value,所以这
//里返回空即增加成功。
return map.put(e, PRESENT)==null;
}
对于HashSet: 在增加元素时,如果集合中不存在,则返回true ;如果已经存在则返回false。
对于HashMap: 在增加元素时,如果key已经存在,则将该key对应的value值覆盖掉。
map.put(key,value)返回值:是在本次增加元素之前,key对应的value值、
set.put()返回值:是之前是否已经存在。如果不存在:返回true;如果存在,返回false
二者差异的根本原因,是因为二者的研究对象不一致:map.put 第二次 会覆盖掉一次 相同key对应的“value”值; set.add()第二次 会直接忽略掉,忽略的是key.
内部类和匿名内部类
public class MyOuterClass {
int i=10;
class MyInnerClass{
int i=20;
public void myMethod(){
int i=30;
System.out.println(i); // 30
System.out.println(this.i); //20
System.out.println(MyOuterClass.this.i); //10 外部类.this.成员变量 (仅适用于非static修饰的内部类)
}
}
public static void main(String[] args) {
// MyInnerClass innerClass=new MyInnerClass();
MyOuterClass.MyInnerClass innerClass=new MyOuterClass().new MyInnerClass();
innerClass.myMethod();
}
}
输出如下图所示:
此处的内部类主要注意几点:
1、非静态关键字修饰的内部类,需要通过外部类的调用进行实例化,不能直接实例化,否则再编译时会报错。(被静态关键字修饰的则可以。)
2、这里主要注意调用该变量的对象是谁,即this这个对象是属于哪个类即是哪个类的变量被调用了。
匿名内部类:隐藏的继承了一个类(可以是普通类、抽象类,不能是被final修饰的类),或者实现了一个接口。
首先请看下面这一段代码:
public class Demo01 {
//String str = “zs”
public static boolean empty(String str){
return str.isEmpty() ;
}
public static long empty2(Date date){
return date.getTime(); //2020 - 09-19 12:22:55 - 1970.-1-1 0:0:0.000
}
public static void main(String[] args) {
System.out.println(empty( new String() )) ;
System.out.println(empty( new String(){} )) ; //这里提示final修饰的类不能被继承
System.out.println( empty2(new Date()) );
empty2( new Date(){} );
}
}
当我们给new String()后加一个{}时会提示这里的final修饰的类不能被继承。其实这里加上一个{}后就变成了继承String类的子类,只不过这个子类是没有名字的,而String类是被final修饰的所以不能够被继承。
关于内部类的使用:
常常使用一个方法,使其返回值为某个类或接口的对象。而这个类或接口在方法内部创建
//常常使用一个方法,使其返回值为某个类或接口的对象。而这个类或接口在方法内部创建
//方式一、不使用匿名内部类
public Comparable getComparable(){
//1、创建一个实现Comparable接口的类:局部内部类
class MyComparable implements Comparable{
@Override
public int compareTo(Object o) {
return 0;
}
}
return new MyComparable();
}
//常常使用一个方法,使其返回值为某个类或接口的对象。而这个类或接口在方法内部创建
//方式二、使用匿名内部类
public Comparable getComparable01(){
//1、创建一个实现Comparable接口的类:局部内部类
return new Comparable(){
@Override
public int compareTo(Object o) {
return 0;
}
};
}
包装类的自动拆箱和装箱
int Integer
byte Byte
short Short
long Long
float Float
double Double
boolean Boolean
这个知识点涉及到的面试主要有两个地方:
1、自动拆箱和装箱时什么意思。
如下所示:装箱就是可以实现将普通类型到变量自动转换成与它对应的引用类型的变量,拆箱则相反.
Integer i = 10 ; //自动装箱 : 包装类 = 基本类型
int j = i ;//自动拆箱 : 基本类型 = 包装类
2、在自动装箱时当int类型的变量超过它的范围时装箱时会发生什么情况。
默认范围:以Integer为例,在自动装箱时,如果判断 整数值的范围在[-128,127]之间,则直接使用 整形常量池中的值;
如果不在此范围,则会new 一个新的Integer();
这里面试时主要会考如下代码:
Integer n1 = 100 ; //Integer = int ;int ->Integer, 自动装箱
Integer n2 = 100 ;
Integer n3 = 1000 ; //Integer n3 = new Integer(1000) ;
Integer n4 = 1000 ; //Integer n4 = new Integer(1000) ;
System.out.println(n1 == n2);
System.out.println(n3 == n4);
第一个输出为true , 第二个输出为false。
泛型
List<Object> objs = new ArrayList<Object> (); 对
List<Object> objs = new ArrayList<String> ();错
List<?> objs = new ArrayList<String> ();对
List<? extends Object> objs = new ArrayList<String> ();对
List<? extends String> objs = new ArrayList<String> ();对,在考虑泛型时,不用考虑 final
为什么对于一个public及final修饰的变量,一般建议声明为static?
public class A{
public final static int num = 10 ;
}
如果没有static修饰:假设在10个类中要使用num变量,就必须在这10个类中先实例化A的对象,然后通过“对象.num”来使用num变量,因此至少需要new10次。
加上static可以节约内存。static是共享变量,因此只会在内存中 拥有该变量的一份存储,之后就可以直接通过“类名.变量” 使用(例如 “A.num”),而不用先new后使用。
由于final修饰的变量不可改变,因此不用考虑并发问题。
InterfaceA是接口, InterfaceA []a = new InterfaceA[2];是否正确?
正确。
考点是对“对象数组”的理解。 {x,x,x,x,x,x},在对象数组中 实际存放的不是对象本身,而放的是对象引用的地址。
Java中IO
按照流的方向:输入流(inputStream)和输出流(outputStream)。
按照实现功能分:节点流(可以从或向一个特定的地方(节点)读写数据。如 FileReader)和处理流(是对一个
已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。处理流的构造方法总是要
带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。)
按照处理数据的单位: 字节流和字符流。字节流继承于 InputStream 和 OutputStream,字符流继承于
InputStreamReader 和 OutputStreamWriter。
类什么时候被初始化?
1)创建类的实例,也就是 new 一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName("com.lyj.load"))
5)初始化一个类的子类(会首先初始化子类的父类)
6) JVM 启动时标明的启动类,即文件名和类名相同的那个类
只有这 6 中情况才会导致类的类的初始化。
类的初始化步骤:
1)如果这个类还没有被加载和链接,那先进行加载和链接
2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一
次),那就初始化直接的父类(不适用于接口)
3)加入类中存在初始化语句(如 static 变量和 static 块),那就依次执行这些初始化语句。
既然有 GC 机制,为什么还会有内存泄露的情况
理论上 Java 因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是 Java 被广泛使用于服务器端编程的一
个重要原因)。然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被 GC 回收,因此也会导致内存泄
露的发生。
例如 hibernate 的 Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中
可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。
下面例子中的代码也会导致内存泄露。
import java.util.Arrays;
2. import java.util.EmptyStackException;
3. public class MyStack<T> {
4. private T[] elements;
5. private int size = 0;
6. private static final int INIT_CAPACITY = 16;
7. public MyStack() {
8. elements = (T[]) new Object[INIT_CAPACITY];
9. }
10. public void push(T elem) {
11. ensureCapacity();
12. elements[size++] = elem;
13. }
14. public T pop() {
15. if(size == 0)throw new EmptyStackException();
16. return elements[--size];
17. }
18. private void ensureCapacity() {
19. if(elements.length == size) {
20. elements = Arrays.copyOf(elements, 2 * size + 1);
21. }
22. }
23. }
上面的代码实现了一个栈(先进后出(FILO))结构,乍看之下似乎没有什么明显的问题,它甚至可以通过你编
写的各种单元测试。然而其中的 pop 方法却存在内存泄露的问题,当我们用 pop 方法弹出栈中的对象时,该对象不
会被当作垃圾回收,即使使用栈的程序不再引用这些对象,因为栈内部维护着对这些对象的过期引用( obsolete
reference)。在支持垃圾回收的语言中,内存泄露是很隐蔽的,这种内存泄露其实就是无意识的对象保持。如果一个
对象引用被无意识的保留起来了,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其他对象,即使这样
的对象只有少数几个,也可能会导致很多的对象被排除在垃圾回收之外,从而对性能造成重大影响,极端情况下会引
发 Disk Paging (物理内存与硬盘的虚拟内存交换数据),甚至造成 OutOfMemoryError。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。