《Java虚拟机》必知必会——十四个问题总结(内存模型+GC)

来源:互联网 发布:socket网络编程视频 编辑:程序博客网 时间:2024/06/04 19:26

一、Java概述

1、Java相较于PHP、C#、Ruby等一样很优秀的编程语言的优势是什么?
(1)体系结构中立,跨平台性能优越。Java程序依赖于JVM运行,javac编译器编译Java程序为平台通用的字节码文件(.class),再由JVM与不同操作系统匹配,装载字节码并解释(也有可能是编译,会在第三个问题中说到)为机器指令执行。
(2)安全性优越。通过JVM与宿主环境隔离,且Java的语法也一定程度上保障了安全,如废弃指针操作、自动内存管理、异常处理机制等。
(3)多线程。防止单线程阻塞导致程序崩溃,分发任务,提高执行效率。
(4)分布式。支持分布式,提高应用系统性能。
(5)丰富的第三方开源组件。Spring、Struts、Hibernate、Mybatis、Quartz等等等等。

2、字节码是什么?.class字节码文件是什么?
(1)字节码是包含Java内部指令集、符号集以及一些辅助信息的能够被JVM识别并解释运行的符号序列。字节码内部不包含任何分隔符区分段落,且不同长度数据都会构造成n个8位字节单位表示。
(2).class里存放的就是Java程序编译后的字节码,包含了类版本信息、字段、方法、接口等描述信息以及常量池表,一组8位字节单位的字节流组成了一个字节码文件。

3、JVM是什么?HotSpot虚拟机有什么特点?
JVM全称Java Visual Machine,Java虚拟机。是Java程序的运行环境,主要负责装载字节码文件,并解释或编译成对应平台的机器指令执行。
我们使用最多的是JDK缺省自带的HotSpot虚拟机,使用解释器加编译期并存架构方案。一开始的时候使用解释器,使编译未结束时就可以解释字节码为本地机器指令执行,提高效率。编译器用在HotSpot的热点探索功能上,在存在频繁调用的方法或循环次数较多的代码时,就会把这类代码块标记为“热点代码”,通过内嵌的双重JIT(Just in time compiler)将字节码直接编译成对应机器指令,以提高效率。

二、Java内存模型

1、PC计数器
线程私有,用于记录当前线程正在执行字节码的地址,如果执行的是native本地方法,PC计数器为空。

2、Java栈
线程私有,也叫作Java虚拟机栈,用于存储栈帧,栈帧的入栈出栈过程即方法调用到执行结束的过程。栈帧中主要存放方法执行所需的局部变量表(包括局部变量的声明数据类型、对象引用等)、操作数栈、方法出口等信息。

3、本地方法栈
与Java栈功能类似,只是用于存储native本地方法的相关信息。

4、Java堆
线程公用,用于存放对象实例,包括数组,也叫GC区,是GC主要工作的区域。也正是如此,由于GC频率过快与效率不高,堆区的可能成为JVM性能瓶颈,于是考虑到性能,堆区不再是对象内存分配的唯一选择。这里就涉及到了对象的逃逸分析与栈上分配。
逃逸分析就是用来分析对象的作用域是否在方法内部,当方法返回了当前类实例对象、方法中为当前类成员变量赋值、方法中引用当前类成员变量的值时就会发生逃逸,依然在堆上分配内存。但当对象的作用域就在方法内时,比如在方法内创建了该类的实例,没有返回、没有引用,则这种情况就直接在Java栈上分配内存,随着栈帧的出栈释放空间,减轻了堆区GC的压力。

5、方法区
线程公用,存储了每一个Java类的结构信息,比如:字段、各种方法的字节码内容数据、运行时常量池等。方法区也被称为永久带。一般没有显示要求,GC只对方法区中的常量池回收以及类型卸载。

6、运行时常量池
属于方法区的一部分,类加载器将类的字节码文件加载如JVM中后,会把字节码文件中的常量池表转化为运行时常量池。

三、Java垃圾回收机制

1、常见的标记可用对象的算法有哪些?
(1)引用计数法:每个对象都创建一个私有的引用计数器,当该对象被其他对象引用时(出现在等号右边),引用计数器加1;当不再引用时,引用计数器减一;当引用计数器为0时,对象即可被回收。这种方式存在着当两个对象互相引用时,二者引用计数器值都不为0无法被回收的问题;
(2)根搜索算法:JVM一般使用的标记算法,把对象的引用看作图结构,由根节点集合出发,不可达的节点即可回收,其中根节点集合包含的如下5种元素:
1、Java栈中的对象引用;
2、本地方法栈中的对象引用;
3、运行时常量池的对象引用;
4、方法区中静态属性的对象引用;
5、所有Class对象;

2、常见的垃圾回收算法有哪些?JVM使用哪种?
(1)标记-清除算法:分两个阶段执行,第一个阶段标记可用对象,第二个阶段清除垃圾对象;这个方法很基础简单,但效率低下,而且会产生内存碎片(不连续的内存空间),无法再次分配给较大对象。
(2)复制算法:被广泛用于新生代对象的回收。将内存分为两个区域,新对象都分配在一个区域中,回收时将可用对象连续复制到另一个区域,回收完成后,新对象分配在有对象的区域,循环往复。这种算法不会产生内存碎片,且效率较高,但因为同时只有一个区域有效,会导致内存利用率不高。
(3)标记-整理算法:被应用于老年代对象的回收。这种算法与标记清除算法类似,第一个阶段标记可用对象,第二个阶段将可用对象移动到一段连续的内存上,解决了标记-清除算法会产生内存碎片的缺点。
(4)分代回收算法:在HotSpot虚拟机中,基于分代的特点(堆内存可进一步分为年轻代、老年代,老年代存放存活时间较长的对象),JVM GC使用分代回收算法。
年轻代使用复制算法:分为一个较大的Eden区与两个较小的、等大小的Survivor区(From Space与To Space),比例一般是8:1:1。新对象都分配在Eden区,当GC发生时(新生代的GC一般叫做Minor GC),将Eden区与From区中的可用对象复制到To区中,From Space与To Space互换名称,循环方法。直到发生如下两种情况,对象进入老年代:
1’ From区内的对象已经达到存活代数阀值(经过GC的次数达到设定值),GC时不会进入To区中,直接移动至老年代;
2’ 在回收Eden区与From区后,超出To区可容纳范围,则直接将存活对象移动至老年代。

这里写图片描述

老年代使用标记-整理算法:当老年代满的时候,会触发Full GC(新生代与老年代一起进行GC)。

3、常见的垃圾回收器有哪些?有什么特点?适合应用与什么场景?
(1)Serial收集器
年轻代采用复制算法、串行回收、与“Stop the world”机制(GC时停止其他一切工作),适用于单核CPU环境,绝对不推荐应用于服务器端。
Serial提供了老年代的回收器Serial Old,采用标记-整理算法,其他特性与新生代一致。
Serial+Serial Old适合客户端场景。
(2)ParNew收集器
相当于Serial的多线程版本,并行回收,年轻代同样采用复制算法与“Stop the world”机制,适用于多核CPU、低延迟环境,推荐应用于服务器场景。
(3)Parallel收集器
与ParNew类似,复制算法、并行回收、“Stop the world”机制,但是与ParNew不同,Parallel可以控制程序吞吐量大小,也被称为吞吐量优先的垃圾收集器。
与Serial类似,Parallel也有老年代版本,Parallel Old,同样采用标记整理-算法。
Parallel+Parallel Old非常适用于服务器场景。
(4)CMS收集器
与Parallel的高吞吐对应,CMS就是为高并发、低延时而生的。采用标记-清除算法、并行回收、“Stop the world”。因为采用了标记-清除算法,会产生大量内存碎片,要慎重使用。
(5)G1收集器
是一款基于并行、并发、低延时、暂停时间可控的区域化分代式垃圾回收器。
具有革命意义的设计,放弃了堆区年轻代、老年代的划分方案,而是将堆区或分成约2048个大小相同的独立Region块。
详情可参看ImportNew博文http://www.importnew.com/15311.html

4、GC的优化方案?
基本的原则就是尽可能地减少垃圾和减少GC过程中的开销。其中需要注意,JVM进行次GC的频率很高,但因为Minor GC占用时间极短,所以对系统产生的影响不大。更值得关注的是Full GC的触发条,具体措施包括以下几个方面:
(1)不要显式调用System.gc()
调用System.gc()也仅仅是一个请求(建议)。JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。但即便这样,很多情况下它会触发Full GC,也即增加了间歇性停顿的次数。
(2)尽量减少临时对象的使用
临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,也就减少了Full GC的概率。
(3)对象不用时最好显式置为Null
一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。
(4)尽量使用StringBuffer,而不用String来累加字符串
由于String是常量,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
(5)能用基本类型如Int,Long,就不用Integer,Long对象
基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。
(6)尽量少用静态对象变量
静态变量属于全局变量,不会被GC回收,它们会一直占用内存。
(7)分散对象创建或删除的时间
集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行Full GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。

5、Java即使有了GC也会出现的内存泄漏情况?举例说明。
1.静态集合类像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着。

Static Vector v = new Vector();
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}

在这个例子中,代码栈中存在Vector对象的引用v和Object对象的引用o。在For循环中,我们不断的生成新的对象,然后将其添加到Vector对象中,之后将o引用置空。问题是当o引用被置空后,如果发生GC,我们创建的Object对象是否能够被GC回收呢?答案是否定的。因为,GC在跟踪代码栈中的引用时,会发现v引用,而继续往下跟踪,就会发现v引用指向的内存空间中又存在指向Object对象的引用。也就是说尽管o引用已经被置空,但是Object对象仍然存在其他的引用,是可以被访问到的,所以GC无法将其释放掉。如果在此循环之后,Object对象对程序已经没有任何作用,那么我们就认为此Java程序发生了内存泄漏。

2.各种连接,数据库连接,网络连接,IO连接等没有显示调用close关闭,不被GC回收导致内存泄露。

3.监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 亲戚赖在家里住怎么办 食堂饭菜味道差该怎么办 被监视居住公安打电话睡着了怎么办 鱼缺氧浮上水面怎么办 车载低音炮有电流声怎么办 925纯银变黑了怎么办 银子放久了变黑怎么办 高铁票网上售空怎么办 高铁票出票失败怎么办 高铁票名字打错怎么办 高铁票姓名错了怎么办 高铁票弄丢了怎么办 用过的车票丢了怎么办 高铁票被水洗了怎么办 沈阳地铁卡丢了怎么办 火车票没写检票口怎么办 吃鸡听的脚步声距离太近怎么办 检票时车票丢了怎么办 高铁出站没检票怎么办 高铁来不及取票怎么办 被发现假的增值税发票怎么办 高铁票身份证验证失败怎么办 网上订的火车票查不到怎么办 已经参加工作想学个本科证怎么办 火车晚点耽误下一趟列车怎么办 门外装监控没有预留电线怎么办 框架柱主筋柱顶预留长度不够怎么办 遇到很嚣张的人怎么办 在地板砖上铺木地板卧室门怎么办 宝宝打预防针的本子丢了怎么办 宝宝打预防针本子丢了怎么办 打疫苗的本子丢了怎么办 麦客收割机麦秸里加麦粒怎么办 高铁乘务员身高不够怎么办 坐火车买到站票怎么办 买上车补票原票怎么办? 买的商务座补票怎么办 12306账号被别人登录怎么办 飞机不提供餐食怎么办 12306退票支付宝失败怎么办 12306重复支付怎么办支付宝