一、堆和栈的区别
1、存放的数据不同
堆中存放对象,需要手动释放内存。(垃圾回收机制)
栈中存放局部变量,内存的释放是系统控制实现的。(局部变量的存活时间是这个函数调用完之后)
2、空间大小不一样
堆的空间大,栈的空间小
二、垃圾回收机制(主要针对堆和方法区)
1、使用目的:避免发生内存泄漏和内存溢出的情况,使得系统能更好地使用内存。(在JVM中,程序计数器是唯一一个没有规定任何OOM的区域)
2、垃圾回收机制的对象:堆和方法区中的对象。
3、垃圾回收机制什么时候进行:垃圾回收机制运行不确定,由JVM确定,在运行时是间歇执行的。
4、垃圾回收机制如何确定对象?(可达性分析)
垃圾回收机制有个Root对象(栈(包括虚拟机栈和本地方法栈)中的引用对象,方法区中的静态变量、常量的引用对象),在对对象进行可达性分析时就是判断对象与Root之间是否有直接或间接引用。根据相关的引用判断对象是否会被回收,如果判断对象应该被回收,则查看对象是否重写了finalize()方法或是是否已经调用过finalize()方法,若对象没有重写方法,或者已经调用过方法了,则回收对象;否则,对象进去队列中,等待执行自己的finalize()方法。
{ a、垃圾回收机制中的引用
强引用:只要引用存在,垃圾回收机制就不会回收。
软引用:用来描述一些有用,但不是必须的引用,在系统将要发生内存溢出异常之前,把弱引用对象作为第二次垃圾回收的对象。它起到一个缓存作用,当内存足够时,通过弱引用查询数据,当内存不足时,自动删除这部分数据,并且从真实数据来源查询数据。
弱引用:在下一次垃圾回收机制启动时,一定会被删除的对象。用于查看弱引用监控对象是否被标记为即将回收的垃圾(isEnQueued())
虚引用:对象被回收时收到一个系统通知。用于检测对象是否已经从内存中删除。
b、final、finally、finalize的区别:
final是一个修饰符,final修饰的类不能被继承,因此不能与抽象类同时使用。final修饰的变量不可变,final修饰的方法不可重写。(子类可以使用父类的final方法,但是不能重写)
finally是在异常处理之后要执行的清除任务,例如垃圾回收之类的。
finalize是垃圾回收机制在回收对象之前要做的操作,对象可能在执行finalize中“起死回生”。
}
5、垃圾处理方法:标记-清除,标记-整理,复制
标记-清除:标记阶段:通过可达性分析,标记与Root相连的对象,未标记的为垃圾对象。
清除阶段:将未标记的对象清除
标记-整理:标记阶段:通过可达性分析,标记与Root相连的对象(有直接引用和间接引用),未标记的为垃圾对象。
整理阶段:将所有标记了的对象整理压缩到内存的一段,之后清除边界所有空间
复制:将内存空间分为两部分,使用其中一部分,在垃圾回收时,将使用部分的存活对象复制到未使用的空间中,并清除使用部分的空间。
三者的区别:时间复杂度:标记-清除>标记-整理>复制
内存利用率:标记-整理=标记-清除>复制(只使用一半)
内存整齐度:标记-整理=复制>标记清除
垃圾回收机制有哪些?(老年代、新生代)
针对老年代和新生代使用Full GC,老年代回收使用标记-整理/标记-清除的方式。(标记-整理:单线程、多线程收集器。标记-清除:CMS(并行))
针对新生代使用Minor GC,使用复制算法,如果空间不够(对象较大),可以直接进入老年代。
分代回收:G1回收器(并行)
{ 单线程是指:垃圾回收线程只有一个在运行。
多线程是指:垃圾回收线程有多个在运行。
串行、并行:用户程序与垃圾收集器交替执行-串行。用户程序与垃圾收集器同时执行-并行。(Parallel Scavenge(并行清除)、CMS、G1)
新生代:Serial(单线程)、ParNew(多线程)、Parallel Scavenge(并行多线程。目的:达到一个可控制的吞吐量。其他垃圾收集器主要关注缩短用户程序停顿时间。缩短用户程序停顿时间是以牺牲新生代空间和吞吐量为代价的,新生代空间少,垃圾回收频繁,用户程序停顿时间整体变多,吞吐量变小(吞吐量:用户程序运行时间在整体程序运行时间所占比重))
老年代:Serial Old 、Parallel Old、CMS(使用标记-清除的垃圾处理方法,目的:用户程序停顿时间小(则吞吐量小)。四大步骤:1、初始标记(标记与Root直接联系的)2、并发标记(在运行时标记与Root有关的对象)3、重新标记(修改由于用户程序运行而发生变化的对象)4、并发清除)
分代回收:G1(整体:标记-整理,局部:复制)目的:可预测的停顿时间。将堆分成大小独立的区域。将原来的整个空间划分成小空间,可以针对小空间进行垃圾回收。系统针对每个空间大小和回收该空间所停顿的时间会维护一个列表,可以根据这个列表选择要回收的区域。此外,每一个区域都有一个Rememberbe Set,用来记录区域中对象的引用对象所在的区域。四大步骤:1、初始标记 2、并发标记 3、最终标记:程序运行过程中,将对象的引用变化记录在线程的Rememberbe Set Logs中,最终标记阶段负责将Rememberbe Set Logs放入到Rememberbe Set中。筛选回收:根据列表选择符合自己要求的区域进行回收。
}
三、有内存回收策略(垃圾回收机制)就必定有内存分配策略(堆)
1、大多数情况,新生代在Eden上分配,如果空间不够,会发生Minor GC。
2、大对象、长期存活的对象直接进入老年代。
3、对象年龄动态判断。对象达到某一存活时间就进入老年代,这个时间可以动态改变。当空间中,某一存活时间的对象空间多于总空间的一半,则将这一空间的对象放入老年代。
4、空间分配担保。如果在进行Minor GC之前,老年代的最大可用连续空间大于新生代所以对象的总空间,则执行。若不大于,就要检查HandlePromotionFailure是否允许担保,如果允许,就查看老年代最大连续空间是否大于以前晋升到老年代的对象的平均大小,如果大于,则执行,如果小于或者不允许担保,执行Full GC。
(引起Full GC的条件:不允许空间担保、老年代空间不足、CMS使用标记-清除算法,会造成空间碎片,导致空间不足)
四、在new一个对象时,需要在分配内存之前,先进行类的加载
java中的所有类需要类加载器加载到JVM中,才可以运行。
类的加载过程:加载:JVM获取相关信息,并在内存中生成一个该类的对象,作为数据访问的入口
验证:保证Class文件的字节流中的信息符合虚拟机的要求,不会损害虚拟机的安全
准备:为类变量(static修饰的变量)分配内存(方法区内存)并设置初始值。(注:实例变量在对象实例化时,随着对象一起分配到堆中)
解析:将常量池的符号引用,变为直接引用的过程。(将语义信息与具体操作相连接)(实现JAVA动态绑定,可以将解析放在初始化之后)
初始化:真正执行类中定义的java程序。初始化发生时机:主动引用(new)、被动引用(引用方式不会触发初始化,子类引用父类的静态字段,子类不会初始化)
两个类相等:类本身相等,还是用同一类加载器加载。
类加载器的分类:启动类加载器(负责将lib目录下的类加载到虚拟机内存中)、
扩展类加载器(负责将lib/ext目录下的文件加载到内存中)、
应用程序类加载器(负责将自己的java代码编译成class文件加载到内存中)
类加载器之间的层次关系:双亲委派模型(一个类加载器首先将类加载请求发给父类加载器,只有当父类加载器无法加载,自己才加载,避免了类的重复加载)
三、内存泄漏、内存溢出定义,什么原因导致的?
1)所谓内存溢出(OOM)就是,程序申请内存空间,但是得不到足够的空间。
内存泄漏就是:程序申请内存空间之后,无法释放已申请的内存空间,例如有老年代指向这个对象,则在对象使用完之后,无法释放该对象的空间,出现内存泄漏。
2)原因:使用静态集合类hashmap Set Vector,HashMap中的对象为空,不能回收对象,必须将HashMap设置为空,才能回收对象
监听器,在释放连接时要删除监听器。
物理连接:数据库与网络的连接需要显示关闭。数据库连接一般使用DataSource.getConnection(),需要使用close()关闭。
内部类与外部模块的引用
单例模式,单例的存活周期是jvm整个生命周期,如果它引用一个短生命的对象,则这个对象无法释放空间,会发生内存泄漏。