1,内存管理总结

java对内存的管理主要是分配和释放。

以关键字new为对象申请内存空间(基本类型除外),对象都在堆上分配空间。

对象的释放是有GC完成,当对象不再被引用的时候,将会被释放掉。

虚拟机栈:

1)线程私有的,生命周期与线程相同,为虚拟机执行JAVA方法服务

2)主要描述了Java方法执行的内存模型,当方法被执行时,创建一个栈帧,用于存储局部变量表,操作栈,动态链接,方法出口等信息。

3)当前大部分说的栈就是虚拟机栈,或者是虚拟机栈中的局部变量表

4)局部变量表:存放的编译器可知的各种基本数据类型,对象引用(可能是起始地址的引用指针,或者对象句柄)以及returnAddddress类型。在编译器即已完成分配。

5)两种异常,一种是如果线程请求的栈深度超过虚拟机允许的深度,则抛出StackOverflowError

一种是动态扩展中,无法获取足够的内存,则抛出OutOfMemoryError

本地方法栈:

1)作用与虚拟机栈相似,区别是本地方法栈是为虚拟机使用到的Native方法服务。

堆:

1)被所有线程共享的一块内存区域

2)此内存区域的唯一目的是存储对象实例。

3)垃圾收集管理器主要的管理区域

4)可以细分为:新生代和老生代,

再细致一些:Eden空间,From Survivor空间,To Survivor空间等。

5)内存物理上可以不连续,但是逻辑上必须是连续的。

6)可以设置为固定的,也可以设置为动态分配的。主要通过-Xmx和-Xms控制。

7)无法获取足够的内存,则抛出OutOfMemoryError

方法区(别名:Non-Heap非堆):

1)被所有线程共享。

2)存储被虚拟机加载的类信息、常量、静态变量、即时编译器变异后的代码等,即Class文件。

3)内存物理上可以不连续,可以不实现垃圾收集

4)这一区域的内存回收主要是针对常量池的

5)方法区的内存回收条件非常的严苛

6)无法获取足够的内存,则抛出OutOfMemoryError

程序计数器:

1)是一块较小的内存空间,主要用来做当前线程所执行的字节码的行号指示器。

2)每个线程的计数器都是私有的。

3)如果当前线程执行的是java方法,则计数器记录的是当前执行的虚拟机字节码指令的地址;

如果是native方法,那么计数器的值为空。

4)没有规定任何OutOfMemoryError情况的区域

运行时常量池:

1)用于存放编译期生成的各种字面量和符号引用

2)具备动态性,较多用的是String 类的intern()方法

3)无法获取足够的内存,则抛出OutOfMemoryError

直接内存:

1)堆外内存,不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域

2)可避免Java堆和native堆中的数据来回复制,从而提高效率

 

2,内存泄露总结

 

3,垃圾回收机制总结

对象已死:

1)常见算法引用计数法:当对象被引用一次,则计数加1,每当引用失效,则减1,如果为0,则被垃圾回收,但无法处理对象循环引用问题。

2)根搜索法:通过GC roots 的对象作为起始点,从这些起始点开始向下搜索,如果一个对象到GCroots没有任何的引用链,那么此对象不可用。可以用作GC roots的对象有以下几个:虚拟机栈(栈帧中的本地变量表)中的引用对象,方法区中的类静态属性引用的对象,方法区中的常量引用对象,本地方法栈中JNI(Native方法)的引用对象

对象至少要经过两次标记才会被回收。

对象可以通过finalize方法进行自救-即重新与引用链上的任何一个对象建立关联即可;

finalize方法只能被系统自动调用一次,所以二次自救会失败。

 

4,引用总结

1)强引用(Strong reference):

2)软引用(soft reference):

3)弱引用(weak reference):

4)虚引用(phantom reference):

 

5,垃圾回收算法

1)标记-清除算法:

分为两个阶段,标记,清除;首先标记处所有需要回收的对象,标记完成之后统一回收掉所有标记的对象;

缺点:

1、效率低

2、空间问题:会产生大量不连续的内存碎片;

2)复制算法:

将内存分成大小相等的两个块,当一块内存使用完了,将存活的对象复制到另一块内存当中,然后将已使用过得内存一次清理掉。

优化过之后,内存分为8:1:1,平时使用8+1,清理时,将8+1复制到另一个1当中。

3)标记-整理算法:

分为两个阶段:标记,整理;在回收过程中,将存活的对象向一段移动。

4)分代收集算法:

将对象存活的周期不同,将内存分为几块。

新生代适合复制算法;

老年代适合另外两种;

 

6,垃圾收集器

1)Serial收集器:单线程处理,必须暂停所有线程才能处理,知道处理完成。简单并且高效。使用复制算法。

2)parNew收集器:Serial收集器的多线程版本,其他都一样,代码公用很多。可与CMS收集器(jdk1.5中加入)配合工作。使用复制算法。

3)Parallel Scavenge收集器:并行多线程处理。采用复制算法。主要关注于控制CPU的吞吐量(用户代码运行时间/(用户代码运行时间 + 垃圾回收时间))。控制参数:控制最大垃圾手机停顿时间(-XX:MaxGCPauseMillis),直接设置吞吐量大小(-XX:GCTimeRatio),开关参数(-XX:+UseAdaptiveSizePolicy)又称GC自适应调节策略,会动态调整停顿时间和吞吐量。

4)Serial Old收集器:Serial收集起的老年代版本。单线程,使用“标记-整理”算法。主要用于Client模式下的虚拟机。

另外,用于Server模式下,一个是在jdk1.5之前,与Parallel Scavenge收集器搭配使用,另一个是作为CMS收集器的后备方案,在并发收集发生Concurrent Mode Failure时使用。

5)Parallel Old收集器:是Parallel Scavenge收集器老年代的版本。使用多线程,采用“标记-整理”算法。JDK1.6开始使用。

6)CMS收集器:以获取最短回收停顿时间为目标。主要用于互联网站或B/S系统的服务端上。采用“标记-清除”算法。

处理包括四步:

1,初始标记:需要暂停所有线程(stop the world)。标记能与GC Roots直接关联的对象。速度快。

2,并发标记:进行GC Roots tracing(GC寻根)的过程。

3,重新标记:需要暂停所有线程(stop the world)。修正并发标记期间用户代码运行导致的标记变动。停顿时间稍长。

4,并发清除:清除回收垃圾,可与用户代码同时进行

缺点:

1,对CPU资源很敏感。因为并发设计都对CPU资源敏感。默认线程数为(CPU数量 + 3)/ 4。CPU越多越好。提供的CMS变种收集器“增量式并发收集器”,采用抢占式来模拟多任务机制,即GC与用户程序交替运行,防止GC占用时间过长。(目前不提倡使用)

2,无法处理浮动垃圾。原因是并发清理阶段,用户代码同时运行会产生新的垃圾。因此需要预留一部分内存空间用于并发收集时使用。默认情况下,老年代使用了68%时,会激活CMD收集器,可以通过调整参数(-XX:CMSInitiatingOccupancyFraciton)进行设置。如果内存不足,则会出现“Concurrent Mode Failure”失败,这是会启动后背预案,即临时启用Serial Old收集器,这是停顿时间就比较长了。

3,产生大量空间碎片。原因是由于“标记-清除”算法。可配置开关参数(-XX:+UseCMSCompartAtFullCollection),用于在Full GC服务之后,提供一个碎片整理过程,无法并发,因此会停顿时间会变长。另一个参数(-XX:CMSFullGCsBeforeCompaction),表示执行N次不压缩的Full GC只有,进行一次压缩的。

7)G1收集器:当前最前沿成果,jdk1.7中可能使用。基于“标记-整理”算法。可精确控制停顿。将java堆分成多个独立区域,并跟踪里面的垃圾堆积程度,维护一个优先列表,根据要求,优先回收垃圾最多的区域。

 

7,内存分配

1)对象优先在Eden分配:

2)大对象直接进入老年代:即需要大量连续内存的对象,比如很长的字符串或数组。参数(-XX:PratenureSizeThreshold)用于控制大于这个值得对象直接进入老年代。

3)长期存活的对象进入老年代:通过对象年龄(Age)计数器记录。从eden出生,进过minor GC之后存活,并被survivor容纳,那么将对象移到survivor中并将年龄设为1。没经过一次Minor GC,年龄加一,当到达一定年龄(默认15岁),则晋升为老年代。可通过参数(-XX:MaxTenuringThreshold)配置。

4)动态对象年龄判定:当Surviver空间中相同年龄的对象总和大于Surviver的一半,那么就将大于等于这些对象年龄的对象计入老年代。

5)空间分配担保:

 

8,java bin下工具

1)jps:查看进程

2)jstat:虚拟机统计信息监视工具

3)jinfo:java配置信息工具

4)jmap:java内存映射工具

5)jhat:虚拟机堆转储快照分析工具

6)jstack:java堆栈跟踪工具

 

9,虚拟机类加载机制

从类加载到虚拟机内存开始,从虚拟机内存卸载后结束。包括七个阶段:加载,验证,准备,解析,初始化,使用,和卸载。其中验证,准备和解析统称为连接(Linking)

1)加载(Loading):交由虚拟机的具体实现自由把控。加载完成之后,加载过程需要完成三件事情

1、通过一个类的全限定名来获取定义此类的二进制字节流;

2、将字节流代表的静态存储结构转化为方法区的运行是数据结构;

3、Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

2)验证(Verification):确认Class文件中的字节流符合当前虚拟机要求,并且不会危害虚拟机自身的安全。

3)准备(Preparation):

4)解析(Resolution):

5)初始化(Init):规定有四种情况必须立即对类进行初始化。

1、使用new关键字实例化对象;读取或设置一个类得静态字段,以及调用一个类的静态方法的时候;

2、使用java.lang.reflect包的方法对类进行发射调用的时候;

3、初始化一个类的时候,发现其父类还没有初始化,则必须先触发父类初始化;

4、虚拟机启动时,用户需要指定一个要执行的主类(main方法在的类),虚拟机先初始化主类。

6)使用(Using):

7)卸载(Unloading):

 

10,虚拟机加载器:

1,变量加载器:

2,方法加载器:

3,类加载器:判断两个类是否相等,包括equals()方法,isAssignableFrom()方法,isInstance()方法的返回结果,以及instanceof关键字做对象所属关系判断等。必须是相同类加载器。