vector java对象在内存空间中是如何增长的

1、Java程序是运行在Java虚拟机中的。()2、Java的源代码中定义几个类,编译结果就生成几个以.class为后缀的字节码文件。()3、Java程序里,创建新的类对象用关键字new,回收无用的类... 1、Java程序是运行在Java虚拟机中的。 ( )
2、Java的源代码中定义几个类,编译结果就生成几个以.class为后缀的字节码文件。 ( )
3、Java程序里,创建新的类对象用关键字new,回收无用的类对象使用关键字free。 ( )
4、子类继承父类后,如果出现子类的成员变量名和父类的成员变量名同名,那么子类就不继承父类的这个成员变量了。 ( )
5、构造函数用于创建类的实例对象,构造函数名应与类名相同,返回类型为void。 ( )
6、在异常处理中,若try中的代码可能产生多种异常则可以对应多个catch语句。 ( )
7、拥有abstract方法的类是抽象类,但抽象类中可以没有abstract方法。( )
8、一个类可以继承了多个接口。 ( )
9、接口中的方法一定是抽象方法,变量可以不赋初值。( )
10、通过继承,父类中的保护成员在派生类中不能被直接访问。( )

11、由继承性可知,程序中子类拥有的成员数目一定大于等于父类拥有的成员数目。 ( )


12、注释的作用是使程序在执行时在屏幕上显示//之后的内容。??????( )
13、Java语言中的数组元素下标总是从0开始,下标可以是整数或整型表达式。????( )
14、java异常处理中可以使用多个catch子句,此时应将高级别异常类的catch子句放在前面。( )
15、Java程序里,创建新的类对象用关键字new,回收无用的类对象使用关键字free。( )
16、创建Vector对象时构造函数给定的是其中可以包容的元素个数,使用中应注意不能超越这个数值。( )
17、静态初始化器是在其所属的类加载内存时由系统自动调用执行。???( )
20、在Java中对象可以赋值,只要使用赋值号(等号)即可,相当于生成了一个各属性与赋值对象相同的新对象。( )
21、无论Java源程序包含几个类的定义,若该源程序文件以B.java命名,编译后生成的都只有一个名为B的字节码文件。( )
22、即使一个类中未显式定义构造函数,也会有一个缺省的构造函数,缺省的构造函数是无参的,函数体为空。( )
23、Java的各种数据类型所占用的内存长度与具体软硬件环境有关。( )
24、在Java的方法中定义一个常量要用const关键字。( )
25、Java的类不允许多重继承,但接口支持多重继承。( )
26、所谓抽象类就是包含有抽象方法的类。( )
27、在异常处理中总是将可能产生异常的语句放在try块中,用catch子句去处理异常,而且一个try块之后只能对应一个catch语句。( )
28、程序员可以不必释放已创建的对象,因为Java有垃圾回收机制,内存回收程序可在指定的时间释放内存对象。( )
29、Java语言中的数组元素只能是基本数据类型而不能为对象类型。( )
30、构造函数用于创建类的实例对象,构造函数名应与类名相同,在类中必须定义构造函数,且只能定义一个构造函数。( )

可选中1个或多个下面的关键词,搜索相关资料。也可直接点“搜索资料”搜索整个问题。

3.前半句对,后半句错

依赖于引用判断的内存管理机制

Java中对内存对象的访问,使用的是引用的方式。
在Java代码中我们维护一个内存对象的引用变量,通过这个引用变量的值,我们可以访问到对应的内存地址中的内存对象空间。在Java程序中,这个引用变量本身既可以存放堆内存中,又可以放在代码栈的内存中(与基本数据类型相同)。GC线程会从代码栈中的引用变量开始跟踪,从而判定哪些内存是正在使用的。如果GC线程通过这种方式,无法跟踪到某一块堆内存,那么GC就认为这块内存将不再使用了(即代码中已经无法访问这块内存了)。

Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。

这种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精度行低(很难处理循环引用的问题),但执行效率很高。

通过这种有向图的内存管理方式,当一个内存对象失去了所有的引用之后,GC就可以将其回收。反过来说,如果这个对象还存在引用,那么它将不会被GC回收,哪怕是Java虚拟机抛出OutOfMemoryError。

一般来说内存泄漏有两种情况。一种情况如在C/C++语言中的,在堆中的分配的内存,在没有将其释放掉的时候,就将所有能访问这块内存的方式都删掉(如指针重新赋值),也就是说这块内存区域不可达;
另一种情况则是在内存对象已经不需要的时候,还仍然保留着这块内存和它的访问方式(引用),也就是在有向图中仍然可达,但是对象已经不再需要。

Java中GC会很好的解决第一种情况,但是第二种情况需要我们在编码中注意。

可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。

可以这么理解,对于C++,程序员需要自己管理边和顶点,而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。

在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;

其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

一般来说是长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收。

一个典型的内存泄露实例:

* 此时,所有的Object对象都没有被释放,因为变量v引用这些对象

我们循环申请Object对象,并将所申请的对象放入一个Vector中,如果我们仅仅释放引用本身,那么Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。

因此,如果对象加入到Vector后,还必须从Vector中删除,最简单的方法就是将Vector对象设置为null。

在Java程序中容易发生内存泄露的场景:

1.集合类,集合类仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用
这个集合类如果仅仅是局部变量,根本不会造成内存泄露,在方法栈退出后就没有引用了会被jvm正常回收,
而如果这个集合类是全局性的变量(比如类中的静态属性,全局性的map等即有静态引用或final一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减,因此提供这样的删除机制或者定期清除策略非常必要。

不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露,考虑下面的例子:

显然B采用singleton模式,他持有一个A对象的引用,而这个A类的对象将不能被回收,想象下如果A是个比较大的对象或者集合类型会发生什么情况。
所以在Java开发过程中和代码复审的时候要重点关注那些长生命周期对象:全局性的集合、单例模式的使用、类的static变量等等。
在不使用某对象时,显式地将此对象赋空,遵循谁创建谁释放的原则,减少内存泄漏发生的机会。

Java中的几种引用方式

在此之前我们介绍的内容中所使用的引用都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

SoftReference 类的一个典型用途就是用于内存敏感的高速缓存。SoftReference 的原理是:在保持对对象的引用时保证在 JVM 报告内存不足情况之前将清除所有的软引用。关键之处在于,垃圾收集器在运行时可能会(也可能不会)释放软可及对象。对象是否被释放取决于垃圾收集器的算法 以及垃圾收集器运行时可用的内存数量。

WeakReference 类的一个典型用途就是规范化映射(canonicalized mapping)。另外,对于那些生存期相对较长而且重新创建的开销也不高的对象来说,弱引用也比较有用。关键之处在于,垃圾收集器运行时如果碰到了弱可及对象,将释放 WeakReference 引用的对象。然而,请注意,垃圾收集器可能要运行多次才能找到并释放弱可及对象。

    Java的一个重要特性就是通过垃圾收集器(GC)自动管理内存的回收,而不需要程序员自己来释放内存。理论上Java中所有不会再被利用的对象所占用的内存,都可以被GC回收,但是Java也存在内存泄露,但它的表现与C++不同。

JAVA 中的内存管理

    要了解Java中的内存泄露,首先就得知道Java中的内存是如何管理的。

    在Java程序中,我们通常使用new为对象分配内存,而这些内存空间都在堆(Heap)上。

  1. //...此时,obj2是可以被清理的

    在有向图中,我们叫作obj1是可达的,obj2就是不可达的,显然不可达的可以被清理。

    内存的释放,也即清理那些不可达的对象,是由GC决定和执行的,所以GC会监控每一个对象的状态,包括申请、引用、被引用和赋值等。释放对象的根本原则就是对象不会再被使用

  •     另一个是给对象赋予了新值,这样重新分配了内存空间。

    通常,会认为在堆上分配对象的代价比较大,但是GC却优化了这一操作:C++中,在堆上分配一块内存,会查找一块适用的内存加以分配,如果对象销毁,这块内存就可以重用;而Java中,就想一条长的带子,每分配一个新的对象,Java的“堆指针”就向后移动到尚未分配的区域。所以,Java分配内存的效率,可与C++媲美。

    但是这种工作方式有一个问题:如果频繁的申请内存,资源将会耗尽。这时GC就介入了进来,它会回收空间,并使堆中的对象排列更紧凑。这样,就始终会有足够大的内存空间可以分配。

    gc清理时的引用计数方式:当引用连接至新对象时,引用计数+1;当某个引用离开作用域或被设置为null时,引用计数-1,GC发现这个计数为0时,就回收其占用的内存。这个开销会在引用程序的整个生命周期发生,并且不能处理循环引用的情况。所以这种方式只是用来说明GC的工作方式,而不会被任何一种Java虚拟机应用。

    多数GC采用一种自适应的清理方式(加上其他附加的用于提升速度的技术),主要依据是找出任何“活”的对象,然后采用“自适应的、分代的、停止-复制、标记-清理”式的垃圾回收器。具体不介绍太多,这不是本文重点。

JAVA 中的内存泄露

    Java中的内存泄露,广义并通俗的说,就是:不再会被使用的对象的内存不能被回收,就是内存泄露。

    在C++中,所有被分配了内存的对象,不再使用后,都必须程序员手动的释放他们。所以,每个类,都会含有一个析构函数,作用就是完成清理工作,如果我们忘记了某些对象的释放,就会造成内存泄露。

    但是在Java中,我们不用(也没办法)自己释放内存,无用的对象由GC自动清理,这也极大的简化了我们的编程工作。但,实际有时候一些不再会被使用的对象,在GC看来不能被释放,就会造成内存泄露。

    我们知道,对象都是有生命周期的,有的长,有的短,如果长生命周期的对象持有短生命周期的引用,就很可能会出现内存泄露。我们举一个简单的例子:

这里的object实例,其实我们期望它只作用于method1()方法中,且其他地方不会再用到它,但是,当method1()方法执行完成后,object对象所分配的内存不会马上被认为是可以被释放的对象,只有在Simple类创建的对象被释放后才会被释放,严格的说,这就是一种内存泄露。解决方法就是将object作为method1()方法中的局部变量。当然,如果一定要这么写,可以改为这样:

    到这里,Java的内存泄露应该都比较清楚了。下面再进一步说明:

  •     在堆中的分配的内存,在没有将其释放掉的时候,就将所有能访问这块内存的方式都删掉(如指针重新赋值),这是针对c++等语言的,Java中的GC会帮我们处理这种情况,所以我们无需关心。
  •     在内存对象明明已经不需要的时候,还仍然保留着这块内存和它的访问方式(引用),这是所有语言都有可能会出现的内存泄漏方式。编程时如果不小心,我们很容易发生这种情况,如果不太严重,可能就只是短暂的内存泄露。

一些容易发生内存泄露的例子和解决方法

    像上面例子中的情况很容易发生,也是我们最容易忽略并引发内存泄露的情况,解决的原则就是尽量减小对象的作用域(比如android studio中,上面的代码就会发出警告,并给出的建议是将类的成员变量改写为方法内的局部变量)以及手动设置null值。

    至于作用域,需要在我们编写代码时多注意;null值的手动设置,我们可以看一下Java容器LinkedList源码(可参考:)的删除指定节点的内部方法:

  1. //删除指定节点并返回被删除的元素值
  2. //获取当前值和前后节点
  3. first = next; //如果前一个节点为空(如当前节点为首节点),后一个节点成为新的首节点
  4. prev.next = next;//如果前一个节点不为空,那么他先后指向当前的下一个节点
  5. last = prev; //如果后一个节点为空(如当前节点为尾节点),当前节点前一个成为新的尾节点
  6. next.prev = prev;//如果后一个节点不为空,后一个节点向前指向当前的前一个节点

    除了修改节点间的关联关系,我们还要做的就是赋值为null的操作,不管GC何时会开始清理,我们都应及时的将无用的对象标记为可被清理的对象。

    我们知道Java容器ArrayList是数组实现的(可参考:),如果我们要为其写一个pop()(弹出)方法,可能会是这样:

    写法很简洁,但这里却会造成内存溢出:elementData[size-1]依然持有E类型对象的引用,并且暂时不能被GC回收。我们可以如下修改:

    我们写代码并不能一味的追求简洁,首要是保证其正确性。

    在很多文章中可能看到一个如下内存泄露例子:

    可能很多人一开始并不理解,下面我们将上面的代码完整一下就好理解了:

    这里内存泄露指的是在对vector操作完成之后,执行下面与vector无关的代码时,如果发生了GC操作,这一系列的object是没法被回收的,而此处的内存泄露可能是短暂的,因为在整个method()方法执行完成后,那些对象还是可以被回收。这里要解决很简单,手动赋值为null即可:

  1. //...与v无关的其他操作

    上面Vector已经过时了,不过只是使用老的例子来做内存泄露的介绍。我们使用容器时很容易发生内存泄露,就如上面的例子,不过上例中,容器时方法内的局部变量,造成的内存泄漏影响可能不算很大(但我们也应该避免),但是,如果这个容器作为一个类的成员变量,甚至是一个静态(static)的成员变量时,就要更加注意内存泄露了。

    下面也是一种使用容器时可能会发生的错误:

    如果足够了解Java的容器,上面的错误是不可能发生的。这里也推荐一篇本人介绍Java容器的文章:...

    容器Set只存放唯一的元素,是通过对象的equals()方法来比较的,但是Java中所有类都直接或间接继承至Object类,Object类的equals()方法比较的是对象的地址,上例中,就会一直添加元素直到内存溢出。

    所以,上例严格的说是容器的错误使用导致的内存溢出。

    就Set而言,remove()方法也是通过equals()方法来删除匹配的元素的,如果一个对象确实提供了正确的equals()方法,但是切记不要在修改这个对象后使用remove(Object o),这也可能会发生内存泄露。

    比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,以及使用其他框架的时候,除非其显式的调用了其close()方法(或类似方法)将其连接关闭,否则是不会自动被GC回收的。其实原因依然是长生命周期对象持有短生命周期对象的引用。

    SessionFactory就是一个长生命周期的对象,而session相对是个短生命周期的对象,但是框架这么设计是合理的:它并不清楚我们要使用session到多久,于是只能提供一个方法让我们自己决定何时不再使用。

    因为在close()方法调用之前,可能会抛出异常而导致方法不能被调用,我们通常使用try语言,然后再finally语句中执行close()等清理工作:

    单例模式,很多时候我们可以把它的生命周期与整个程序的生命周期看做差不多的,所以是一个长生命周期的对象。如果这个对象持有其他对象的引用,也很容易发生内存泄露。

    其实原理依然是一样的,只是出现的方式不一样而已。

    对于程序员来说,GC基本是透明的,不可见的。运行GC的函数是System.gc(),调用后启动垃圾回收器开始清理。

    但是根据Java语言规范定义, 该函数不保证JVM的垃圾收集器一定会执行。因为,不同的JVM实现者可能使用不同的算法管理GC。通常,GC的线程的优先级别较低。

JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不希望GC突然中断应用程序执行而进行垃圾回收,那么我们需要调整GC的参数,让GC能够通过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。

    Java编程思想中是这么解释的:一旦GC准备好释放对象所占用的的存储空间,将先调用其finalize()方法,并在下一次GC回收动作发生时,才会真正回收对象占用的内存,所以一些清理工作,我们可以放到finalize()中。

    该方法的一个重要的用途是:当在java中调用非java代码(如c和c++)时,在这些非java代码中可能会用到相应的申请内存的操作(如c的malloc()函数),而在这些非java代码中并没有有效的释放这些内存,就可以使用finalize()方法,并在里面调用本地方法的free()等函数。

    不过有时候,该方法也有一定的用处:

    如果存在一系列对象,对象中有一个状态为false,如果我们已经处理过这个对象,状态会变为true,为了避免有被遗漏而没有处理的对象,就可以使用finalize()方法:

  1. //...一些处理操作

    但是从很多方面了解,该方法都是被推荐不要使用的,并被认为是多余的。


    总的来说,内存泄露问题,还是编码不认真导致的,我们并不能责怪JVM没有更合理的清理。

我要回帖

更多关于 vector java 的文章

 

随机推荐