Java中数组、对象及其内存管理、回收

  • Post author:
  • Post category:java





这几天看李刚写的《Java程序员的基本修养》一书,挺有感触的,以前对数组、对象都是拿来主义,但对里面的内存分配都不大关心,而一般互联网公司在笔试题部分或多或少都会涉及此方面的知识,下面就我近期的学习进行总结。




从问题答案的方法入手来理解:






1、Java中声明并创建数组过程中,内存是如何分配的?







先看看下面的代码:


<pre name="code" class="html">public class Test 
{
    public static void main(String[] args) {
        int[] iArr;
        iArr = new int[]{2, 5, -12, 20};
    }
}





静态初始化iArr数组后的存储示意图如上图所示。





先看看引用类型变量和对象的区别:声明的对象是在堆内存中初始化的, 真正用来存储数据的。不能直接访问。




引用类型变量是保存在栈当中的,一个用来引用堆中对象的符号而已(指针)。



所以在声明数组过程中,变量保存在栈中,创建并进行初始化时,数组元素是保存在堆中,数组通过引用指针指向数组元素。






2、Java数组初始化一共有哪几种方式?





1)静态初始化:在初始化数组时为数组每个元素赋值;




2)数组初始化时,程序员只指定数组的长度,由系统为每个元素赋初值,对于对象变量初始化为null,对于基本数据类型初始化为0或是false





3、基本类型数组和引用类型数组间,在初始化时内在分配机制有什么区别?





对于基本类型数组的初始化,程序直接先为数组分配内存空间,再将数组元素的值存入对应的内存里。




对于引用类型数组的数组元素依然是引用类型,因此数组元素里存储的还是引用,它指向另一块内存,该内在里存储了该引用变量所引用的对象。





4、static关键字:





static作用是将实例成员变为类成员,只能修饰类里的成员,不能修饰外部类,不能修饰局部变量、局部内部类。每次创建Java对象时都要为实例变量分配内在空间,并对实例变量执行初始化,而类变量则只需要初始化一次。


(1)对于实例变量初始化有三种:


1)定义实例变量时指定初始化值;

int age = 0;


2)非静态初始化块中对实例变量指定初始值;

int age;
{
	age = 0;
}


3)构造器中对实例变量指定初始值。


(2)对于类变量的初始化:


1)定义类变量时指定初始值;


static int count = 2;


2)静态初始化块中对类变量指定初始值;

static int count;
static {
     count = 2;
}




5、子类继承的成员变量和继承方法的区别:





当通过引用变量来访问它所引用对象的实例变量时,该实例变量的值取决于声明该对象的时所用的类型;




当通过引用变量来访问它所引用对象的方法时,该方法总是表现出它们实际类型的行为。




6、final关键字:





final可以修饰变量,被final修饰的变量被赋初始值后,不能对它重新赋值;




final可以修饰方法,被final修饰的方法被重写;




final可以修饰类,被final修饰的类不能派生子类;


对于final变量,不管它是类变量、实例变量还是局部变量,只要定义该变量时使用了final修饰符,并在定义变量时指定了初始值,且该初始值可以编译时就被确定下来,那么final变量本质上已经不再是变量,而是一个直接量。当成宏变量。


public class Test {
    final static String str1;
    final static String str2 = "Java";
    static{
        str1 = "Java";
    }
    public static void main(String[] args){
        System.out.println(str1+str1 == "JavaJava");//1
        System.out.println(str2+str2 == "JavaJava");//2
    }
}

上面的程序只有第2条才会输出true,因为系统会对str2执行“宏替换”;



7、JVM在保时决定回收一个Java对象所占用的内在?

对垃圾回收机制来说,判断一个对象是否可回收的标准在于该对象是否被引用。



8、JVM会不会漏掉回收某些Java对象,使之造成内存泄漏?

内存泄漏:程序运行过程中会不断分配内存空间,那些不再使用的内在空间应该即时被回收,从而保证系统可以再次使用这些内存,如果存在无用的内存没有被回收回来,那就是内存泄漏。

会,在Java中,如果程序中有一些Java对象,它们处于可达状态,但程序以后永远不会再访问它们,那么它们所占用的内存空间也不会被回收,它们所占用的空间也会产生内存泄漏。



9、JVM能否对不同的Java对象占用的内存区分对待、回收?

Java语言对象中有四种引用:

1)软引用SoftReference

需通过SoftReference类来实现,当一个对象只具有软引用时,它有可能被垃圾回收机制回收;

当系统内存空间充足时,软引用与强引用没有太大区别;当系统内存空间不足时,被软引用所引用的Java对象可以被垃圾回收机制回收,从而避免系统内存不足的异常。

2)虚引用 PhantomReference

软引用和弱引用可单独使用,但虚引用不能单独使用,虚引用的主要作用就是跟踪对象被垃圾回收的状态,程序可以通过检查与虚引用关联的引用队更中是否已经包含指定的虚引用,从而了解虚引用所引用的对象是否即将被回收。它必须和引用队列(ReferenceQueue)联合使用。

3)弱引用WeakReference

和软引用有点相似,弱引用所引用对象的生存期更短,引用级别更低。对于只有弱引用的对象,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所战胜的内存。

4)强引用StrongReference

创建一个对象,并把这个对象赋给一个引用变量;

被强引用所引用的Java对象绝不会被垃圾回收机制回收,即使系统内存非常紧张,即使有些Java对象以后永远都不会被用到;



10、常见垃圾回收机制的实现细节是怎么样的?

垃圾回收机制主要完成两件事:

1)跟踪并监控每个Java对象,当某个对象处于不可达状态时,回收该对象所占用的内存;

2)清理内存分配、回收过程中产生的内存碎片;



11、编程中内存管理一些注意事项:

1)尽量使用直接量

尽量不采用new的方式来创建对象,而应该直接采用直接量来创建

如程序需要”hello”字符串,如果采用String str = new String(“hello”);

此时程序创建了一个缓存在字符串池中的”hello“字符串,str所引用的String对象底层还包含一个char[]数组,这个char[]数组里依次放了h、e、l、l、o等字符。

2)使用StringBuilder和StringBuffer进行字符串连接

如果使用多个String对象进行字符串连接运算,在运行时将生成大量的临时字符串,这些字符串会保存在内存中从而导致程序性能下降。

3)尽早释放无用对象的引用

4)尽量少用静态变量

某个对象被static变量所引用,垃圾回收机制通常不会回收这个对象所占的内存。

5)避免在经常调用的方法、循环中创建Java对象

6)缓存经常使用的对象

7)尽量不要使用finalize方法

在一个对象失去引用之后,垃圾回收器准备回收该对象之前,垃圾回收机制会先调用该对象的finalize()方法来执行资 源


垃圾回收器本身已严重制约应用程序性能的情况,如果再选用finalize,会使性能更差


8)考虑合用SoftReference





版权声明:本文为smileiam原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。