Jvm

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

Java语言是一种具有动态性的解释型语言,类(class)只有被加载到JVM后才能运行。当运行指定程序时,JVM会将编译生成的.class文件按照需求和一定的规则加载到内存中,并组织成为一个完整的Java应用程序。这个加载过程是由类加载器完成,具体来说,就是由ClassLoader和它的子类来实现的。类加载器本身也是一个类,其实质是把类文件从硬盘读取到内存中。类的加载方式分为隐式加载和显示加载。隐式加载指的是程序在使用new等方式创建对象时,会隐式地调用类的加载器把对应的类加载到JVM中。显示加载指的是通过直接调用class.forName()方法来把所需的类加载到JVM中。任何一个工程项目都是由许多类组成的,当程序启动时,只把需要的类加载到JVM中,其他类只有被使用到的时候才会被加载,采用这种方法一方面可以加快加载速度,另一方面可以节约程序运行时对内存的开销。此外,在Java语言中,每个类或接口都对应一个.class文件,这些文件可以被看成是一个个可以被动态加载的单元,因此当只有部分类被修改时,只需要重新编译变化的类即可,而不需要重新编译所有文件,因此加快了编译速度。在Java语言中,类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(例如基类)完全加载到JVM中,至于其他类,则在需要的时候才加载。

类加载的主要步骤:

1)装载。根据查找路径找到相应的class文件,然后导入。

2)链接。链接又可分为3个小步:

3)检查,检查待加载的class文件的正确性。

4)准备,给类中的静态变量分配存储空间。

5)解析,将符号引用转换为直接引用(这一步可选)

6)初始化。对静态变量和静态代码块执行初始化工作。

Java 中会存在内存泄漏吗,请简单描述?

在Java语言中,判断一个内存空间是否符合垃圾回的标准有两个:

第一:给对象赋予了空值null,以后再没有被使用过;

第二:给对象赋予了新值,重新分配了内存空间。

一般来讲,内存泄漏主要有两种情况:

一是在堆中申请了空间没有被释放;

二是对象已不再被使用,但还仍然在内存中保留着。

垃圾回收机制的引入可以有效地解决第一种情况;而对于第二种情况,垃圾回收机制则无法保证不再使用的对象会被释放。因此Java语言中的内存泄漏主要指的第二种情况。

Java语言中,容易引起内存泄漏的原因有很多,主要有以下几个方面的内容:

(1)静态集合类,例如HashMap和Vector。如果这些容器为静态的,由于它们的声明周期与程序一致,那么容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。

(2)各种连接,例如数据库的连接、网络连接以及IO连接等。

(3)监听器。在Java语言中,往往会使用到监听器。通常一个应用中会用到多个监听器,但在释放对象的同时往往没有相应的删除监听器,这也可能导致内存泄漏。

(4)变量不合理的作用域。一般而言,如果一个变量定义的作用域大于其使用范围,很有可能会造成内存泄漏,另一方面如果没有及时地把对象设置为Null,很有可能会导致内存泄漏的放生,如下:

class Server{

private String msg;

public void receiveMsg(){

readFromNet();//从网络接收数据保存在msg中

saveDB()//把msg保存到数据库中

}

从上面的代码中,通过readFromNet()方法接收的消息保存在变量msg中,然后调用saveDB()方法把msg的内容保存到数据库中,此时msg已经没用了,但是由于msg的声明周期与对象的声明周期相同,此时msg还不能被回收,因此造成了内存泄漏。对于这个问题,有如下两种解决方案:第一种方法,由于msg的作用范围只在receiveMsg()方法内,因此可以把msg定义为这个方法的局部变量,当方法结束后,msg的声明周期就会结束,此时垃圾回收器就会可以回收msg的内容了;第二种方法,在使用完msg后就设置为null,这样垃圾回收器也会自动回收msg内容所占用的内存空间。

(5)单例模式可能会造成内存泄漏

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

1.JVM的gc概述

gc(garbage collection):即垃圾收集,是指JVM用于释放那些不再使用的对象所占用的内存。java语言并不要求JVM有gc,也没有规定gc如何工作。不过常用的JVM都有gc,而且大多数gc都使用类似的算法管理内存和执行收集操作。在充分理解了垃圾收集算法和执行过程后,才能有效的优化它的性能。有些垃圾收集专用于特殊的应用程序。比如,实时应用程序主要是为了避免垃圾收集中断,而大多数OLTP应用程序则注重整体效率。理解了应用程序的工作负荷和JVM支持的垃圾收集算法,便可以进行优化配置垃圾收集器。垃圾收集的目的在于清除不再使用的对象,gc通过确定对象是否被活动对象引用来确定是否收集该对象。gc首先要判断该对象是否是时候可以收集,引用计数和对象引用遍历是两种常用的方法。

·引用计数

引用计数存储对特定对象的所有引用数,也就是说,当应用程序创建引用以及引用超出范围时,JVM必须适当增减引用数。当某对象的引用数为0时,便可以进行垃圾收集。

·对象引用遍历

早期的JVM使用引用计数,现在大多数JVM采用对象引用遍历。对象引用遍历从一组对象开始,沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,gc必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。下一步,gc要删除不可到达的对象。删除时,有些gc只是简单的扫描堆栈,删除未标记的对象,并释放它们的内存以生成新的对象,这叫做清除(sweeping)。这种方法的问题在于内存会分成好多小段,而它们不足以用于新的对象,但是组合起来却很大。因此,许多gc可以重新组织内存中的对象,并进行压缩(compact),形成可利用的空间。为此,gc需要停止其他的活动。这种方法意味着所有与应用程序相关的工作停止,只有gc运行。结果,在响应期间增减了许多混杂请求。另外,更复杂的 gc不断增加或同时运行以减少或者清除应用程序的中断。有的gc使用单线程完成这项工作,有的则采用多线程以增加效率。 Java语言没有提供释放已分配内存的显示操作方法。 程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。

2.几种垃圾回收机制

·标记-清除收集器

这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。

·标记-压缩收集器

有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。

·复制收集器

这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,JVM生成的新对象则放在另一半空间中。gc运行时,它把可到达对象复制到另一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。

·增量收集器

增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。

·分代收集器

这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。JVM生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。

·并发收集器

并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。

·并行收集器

并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多CPU机器上使用多线程技术可以显著的提高java应用程序的可扩展性。

GC的工作原理

一个优秀的Java程序员必须了解GC的工作原理、如何优化GC的性能、如何和GC进行有效的交互,因为有一些应用程序对性能要求较高,例如嵌入式系统、实时系统等。只有全面提升内存的管理效率,才能提高整个应用程序的性能。 本篇文章首先简单介绍GC的工作原理,然后再对GC的几个关键问题进行深入探讨,最后提出些Java程序设计建议,从GC角度提高Java程序的性能。

GC的基本原理

Java的内存管理实际上就是对象的管理,其中包括对象的分配和释放,对于程序员来说,分配对象使用new

关键字;释放对象时,只要将对象所有引用赋值为null,让程序不能够再访问到这个对象,我们称该对象为"不可达的".GC将负责回收所有"不可达"对象的内存空间。对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象,通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。但是,为了保证GC能够区别平台实现的问题,Java规范标准对GC的很多行为都没有进行严格的规定。例如,对于采用什么类型的回收算法、什么时候进行回收等重要问题都没有明确的规定。因此,不同的JVM的实现者往往有不同的实现算法。这也给Java程序员开发带

来很多不确定性。本文研究了几个和GC工作相关的问题,努力减少这种不确定性给Java程序带来的负面影响。

增量式GC( Incremental GC )

GC在JVM中通常由一个或一组进程来实现,它本身也和用户程序一样占用heap空间,运行时也占用CPU,当GC进程运行时,应用程序停止运行。因此,当GC运行时间较长时,用户能够感到Java程序的停顿,另一方面,如果GC运行时间太短,可能对象回收率太低,这意味着还有很多应该回收的对象没有被回收,仍然占用大量内存。因此,在设计GC的时候,就必须在停顿时间和回收率之间进行权衡。 一个好的GC实现允许用户定义自己所需要的设置,例如内存有限的设备,对内存的使用量非常敏感,希望GC能够准确的回收内存,它并不在意程序速度的放慢,另外一些实时网络游戏,就不能够允许程序有长时间的

中断。 增量式GC就是通过一定的回收算法,把一个长时间的中断,划分为很多个小的中断,通过这种方式减少GC对用户程序的影响。虽然,增量式GC在整体性能上可能不如普通GC的效率高,但是它能够减少程序的最长停顿时间。Sun JDK提供的HotSpot JVM就能支持增量式GC。HotSpot JVM缺省GC方式为不使用增量GC,为了启动增量GC,我们必须在运行Java程序时增加-Xincgc的参数。HotSpot JVM增量式GC,实现是采用Train GC算法,它的基本想法:将堆中的所有对象按照创建和使用情况进行分组(分层),将使用频繁和具有相关性的对象放在一队中,随着程序的运行,不断对组进行调整,当GC运行时,它总是先回收最老的(最近很少访问的)对象,如果整组都为可回收对象,GC将整组回收,这样,每次GC运行只回收一定比例的不可达对象,保证程序的顺畅运行。

finalize()函数

finalize是位于Object类的一个思路方法,该思路方法的访问修饰符为protected,由于所有类为Object的

子类,因此用户类很容易访问到这个思路方法。由于,finalize函数没有自动实现链式调用,我们必须手动实现,因此finalize函数的最后一个语句通常是super.finalize()。通过这种方式,我们可以实现从下到上实现

finalize的调用,即先释放自己的资源,然后再释放父类的资源。根据Java语言规范标准,JVM保证调用finalize函数之前,这个对象是不可达的,但是,JVM不保证这个函数一定会被调用。另外,规范标准还保证finalize函数最多运行一次。很多Java初学者会认为这个思路方法类似和C++中的析构函数,将很多对象、资源的释放都放在这一函数里面。其实,这不是一种很好的方式,原因如下:其一,GC为了能够支持finalize函数,要对覆盖这个函数的对象作很多附加的工作;其二,在finalize运行完成之后,该对象可能变成可达的,GC还要再检查一次该对象是否是可达的,因此,使用finalize会降低GC的运行性能;其三,由于GC调用finalize的时间是不确定的,因此通过这种方式释放资源也是不确定的。

通常,finalize用于一些不容易控制,并且非常重要资源的释放,例如一些I/O操作、数据连接等,这些资

源的释放对整个应用程序是非常关键的。在这种情况下,程序员应该以通过程序本身管理(包括释放)这些资源为主,以finalize函数释放资源方式为辅,形成一种双保险的管理机制,而不应该仅仅依靠finalize来释放资源。程序如何和GC进行交互(不懂...)Java2增强了内存管理功能,增加了一个java.lang.ref包,其中定义了3种引用类。这3种引用类分别为SoftReference、WeakReference和PhantomReference.通过使用这些引用类,程序员可以在一定程度和GC进行交互,以便改善GC的工作效率。这些引用类的引用强度介于可达对象和不可达对象之间。

一些Java编码的建议

根据GC的工作原理,我们可以通过一些窍门技巧和方式,让GC运行更加有效率,更加符合应用程序的要求。

以下就是一些程序设计的几点建议:

1、最基本的建议就是尽早释放无用对象的引用。大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域(scope)后自动设置为null。我们在使用这种方式时候,必须特别注意一些复杂的对象图,例如数组、队列、树、图等,这些对象之间有相互引用,关系较为复杂。对于这类对象,GC回收它们一般效率较低。如果程序允许,尽早将不用的引用对象赋为null。这样可以加速GC的工作。

2、尽量少用finalize函数。Finalize函数是Java提供给程序员一个释放对象或资源的机会,但是,它会加大GC的工作量,因此尽量少采用finalize方式回收资源。

3、注意集合数据类型,包括数组、树、图、链表等数据结构,这些数据结构对GC来说回收更为复杂。另外,注意一些全局的变量,以及静态变量,这些变量往往容易引起悬挂对象(dangling reference),造成内存浪费。

4、当程序有一定的等待时间,程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范标准并不保证GC一定会执行,此时使用增量式GC可以缩短Java程序的暂停时间。

Java的内存模型以及GC算法?

http://blog.csdn.net/kingofworld/article/details/17718587

jvm性能调优都做了什么?

 JVM性能调优:  http://www.cnblogs.com/chen77716/archive/2010/06/26/2130807.html

 深入理解JVM性能调优:  http://www.open-open.com/lib/view/open1334729637702.html

介绍JVM中7个区域,然后把每个区域可能造成内存的溢出的情况说明。

http://www.codeceo.com/article/jvm-memory-overflow.html

介绍GC 和GC Root不正常引用。

http://blog.csdn.net/fenglibing/article/details/8928927

数组多大放在 JVM 老年代。(不只是设置 PretenureSizeThreshold ,问通常多大,没做过一问便知)

大对象直接进入老年代: http://book.51cto.com/art/201107/278927.htm

Java整型数组的最大长度到底有多长: http://blog.csdn.net/mayumin/article/details/5904974

老年代中数组的访问方式

JVM的内存分配及运行机制: http://www.cnblogs.com/200911/p/3922704.html

没有被GC回收,在不在老年代都通过数组引用访问.

GC算法,永久代对象如何GC,GC有环怎么处理。

  1) Java的GC机制及算法: http://blog.chinaunix.net/uid-7374279-id-4489100.html

   Java GC基本算法: http://www.blogjava.net/showsun/archive/2011/07/21/354745.html

  2) Java内存区域和GC机制: http://www.cnblogs.com/hnrainll/archive/2013/11/06/3410042.html

  永久代的回收有两种:常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收。对于

无用的类进行回收,必须保证3点:

    (1) 类的所有实例都已经被回收;

    (2) 加载类的ClassLoader已经被回收;

    (3) 类对象的Class对象没有被引用(即没有通过反射引用该类的地方).

  3) 基于引用对象遍历的垃圾回收器可以处理循环引用,只要是涉及到的对象不能从GC Roots强引用可到达,垃圾回收器都会进行清理来释放内存。

谁会被GC,什么时候 GC?

(1) 超出了作用域或引用计数为空的对象;从gc root开始搜索找不到的对象,而且经过一次标记、清理,仍然没有复活的对象。

(2) 程序员不能具体控制时间,系统在不可预测的时间调用System.gc()函数的时候;当然可以通过调优,用

NewRatio控制newObject和oldObject的比例,用MaxTenuringThreshold 控制进入oldObject的次数,使得

oldObject 存储空间延迟达到full gc,从而使得计时器引发gc时间延迟OOM的时间延迟,以延长对象生存期。

如果想不被GC怎么办?

只要存在某对象的强引用,就不会被GC回收。

如果想在GC中生存1次怎么办?

想在对象生命周期中至少被GC一次后存活,最简单的方法是重写Object的finalize()。

 

你可能感兴趣的:(Java面试题)