类加载器(ClassLoader):在JVM启动时或者在类运行时将需要的class加载到JVM中
执行引擎:负责执行class文件中包含的字节码指令
内存区(也叫运行时数据区):是在JVM运行的时候操作所分配的内存区。运行时内存区主要可以划分为5个区域
本地方法接口:主要是调用C或C++实现的本地方法及返回结果。
1) 方法区(Method Area):用于存储类结构信息的地方,包括常量池、静态变量、构造函数等。虽然JVM规范把方法区描述为堆的一个逻辑部分, 但它却有个别名non-heap(非堆),所以大家不要搞混淆了。方法区还包含一个运行时常量池
2) java堆(Heap):存储java实例或者对象的地方。这块是GC的主要区域。从存储的内容我们可以很容易知道,方法区和堆是被所有java线程共享的。
3) java栈(Stack):java栈总是和线程关联在一起,每当创建一个线程时,JVM就会为这个线程创建一个对应的java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储局部变量表、操作栈、方法返回值等。每一个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。所以java栈是现成私有的。
4) 程序计数器(PC Register):用于保存当前线程执行的内存地址。由于JVM程序是多线程执行的(线程轮流切换),所以为了保证线程切换回来后,还能恢复到原先状态,就需要一个独立的计数器,记录之前中断的地方,可见程序计数器也是线程私有的。
5) 本地方法栈(Native Method Stack):和java栈的作用差不多,只不过是为JVM使用到的native方法服务的。
垃圾收集器一般必须完成两件事:检测\回收
引用计数法: 给一个对象添加引用计数器,每当有个地方引用它,计数器就加1;引用失效就减1。
OK,问题来了,如果我有两个对象A和B,互相引用,
除此之外,没有其他任何对象引用它们
实际上这两个对象已经无法访问,即是我们说的垃圾对象。
但是互相引用,计数不为0,导致无法回收,所以还有另一种方法: 可达性分析算法: 以根集对象为起始点进行搜索,如果有对象不可达的话,即是垃圾对象。
这里的根集一般包括java栈中引用的对象、方法区常良池中引用的对象、本地方法中引用的对象等。 GC会检查堆中的所有对象是否会被这些根集对象引用,不能够被引用的对象就会被垃圾收集器下面说说回收,一般回收算法也有如下几种
标记-清除(Mark-sweep):
遍历GC root引用,递归标记(设置对象头中的标志位)对象。
标记时如果标志位表示已经标记过则可以跳过。
遍历对象有深度优先与广度优先两种算法,其搜索的步骤数一致,而深度优先的内存使用量更小,因此一般使用深度优先。
清除阶段将再次遍历堆,未标记的对象加入到空闲链表中,标记的对象则去除标记。
这个算法优点是简单
缺点是碎片化。
而且分配速度慢,每次分配需要遍历空闲链表。
因为做GC时需要将对象头进行标记,这将导致大量的数据发生复制。
当然STW(Stop-The-World)也很长,两个阶段均要遍历整个堆。
复制(Copying):
GC复制算法是利用From空间进行分配的。
当From空间被完全占满时,GC会将活动对象全部复制到To空间。
当复制完成后,该算法会把From空间和To空间互换,GC也就结束了。
From空间和To空间大小必须一致。这是为了保证能把From空间中的所有活动对象都收纳到To空间里。
它能在短时间内完成GC,也就是说其吞吐量优秀。、
而且不会发生碎片化
但是堆使用率低下,复制算法把堆分成二等分
通常只能利用其中一半来安排对象。也就是说只有一半堆能被使用,相比其他能使用整个堆的GC算法而言,这是GC复制算法的一个重大缺陷。
而且它不兼容保守式GC算法
标记-整理(Mark-Compact):
标记后,不直接清除,而是向一端移动,超过某阈值后,清除
整理也是遵循着一定顺序的
任意顺序:对象的移动方式和它们初始的对象排列及引用关系无关 线性顺序:将具有关联关系的对象排列在一起 滑动顺序:将对象“滑动”到堆的一端,从而“挤出”垃圾,可以保持对象在堆中原有的顺序而且还有不同的算法分支
双指针回收算法:实现简单且速度快,但会打乱对象的原有布局
针对某一块内存区域中的待整理的存活对象
先计算出如果所有存活对象都移动到一端之后的截止地址
然后将地址大于该截止地址的对象移动到截止地址以下的空间。
在算法的开始阶段,指针free指向区域始端,指针scan指向区域末端。
在第一次遍历过程中,回收器不断向前移动指针free,直到发现空隙。同时,指针scan由后往前移动,直到遇到存活对象。
然后在该对象的对象头部记录下转发地址。
如果指针free和scan交错,则该过程停止,否则变将指针scan遇到的对象移动到free所在的位置。
在第二次遍历中,回收器将指向存活对象边界外的指针更新为目标对象头部记录的转发地址,即对象的新地址。
该算法的整理质量取决于指针free所指向的空隙和指针scan所指向的对象大小的匹配度。
Lisp2算法(滑动回收算法):需要在对象头用一个额外的槽来保存迁移完的地址
在标记结束的第一次遍历堆时,回收器会计算出每个对象的最终转发地址,并保存在对象头的某一个域中。
计算转发地址需要3个参数,分别是待整理区域的起始地址,最终地址,目标区域的起始地址。
目标区域通常与待整理区域相同。指针scan扫描来源区域的所有(存活的和死亡的)对象,指针free指向目标区域的下一个空闲地址
如果scan扫描到的对象是存活的,那就意味着该对象最终会移动到指针free所在地址,然后将该地址写入到对象头域中,然后根据对象的大小向前移动指针free.
在第二次遍历时,回收器将使用对象头域中保存的转发地址来更新赋值器线程根以及被标记的对象,确保它们指向对象的新位置。
第3次遍历时,才将对象移动至转发地址。
引线整理算法:可以在不引入额外空间开销的情况下实现滑动整理,
但需要2次遍历堆,且遍历成本较高
单次遍历算法:滑动回收,实时计算出对象的转发地址而不需要额外的开销
首先是标记的过程,标记过程是基于位图的,每个位对应堆中的一个颗粒(即一个字)。
在标记过程中,如果发现存活对象,就设置该对象的第一个字节和最后一个字节在位图中对应的位(称为标记向量)。
回收器会在整理阶段时根据标记向量的分析计算出任意存活对象的大小。
该算法将堆分成大小相等的内存块(256字节和512字节)
分代收集算法:
不同的对象的生命周期是不一样的。
因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。
在Java程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比如Http请求中的 Session对象、线程、Socket连接,这类对象跟业务直接挂钩,因此生命周期比较长。
但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:String对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。
年轻代:所有新生成的对象首先都是放在年轻代的。
年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
年轻代分三个区。一个Eden区,两个Survivor区(一般而言)。
大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),
当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,
当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。
需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。
而且,Survivor区总有一个是空的。
同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),
这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。
年老代:在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。
因此,可以认为年老代中存放的都是一些生命周期较长的对象。
持久代:用于存放静态文件,如今Java类、方法等。
持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,
例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。
持久代大小通过-XX:MaxPermSize=进行设置。
什么情况下触发垃圾回收
由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。
GC有两种类型:Scavenge GC和Full GC。Scavenge GC
一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,
对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代
因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出
Full GC对整个堆进行整理,包括Young、Tenured和Perm。
Full GC因为需要对整个对进行回收,所以比ScavengeGC要慢,因此应该尽可能减少Full GC的次数。
在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。
有如下原因可能导致Full GC:· 年老代(Tenured)被写满· 持久代(Perm)被写满· System.gc()被显示调用上一次GC之后Heap的各域分配策略动态变化
强引用 强引用不会被GC回收,并且在java.lang.ref里也没有实际的对应类型,
平时工作接触的最多的就是强引用。 Object obj = new Object();
这里的obj引用便是一个强引用。
如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。
当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
软引用 如果一个对象只具有软引用,那就类似于可有可物的生活用品。
如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。
只要垃圾回收器没有回收它,该对象就可以被程序使用。
软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,
如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用 如果一个对象只具有弱引用,那就类似于可有可物的生活用品。
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回 收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
幽灵引用(虚引用) 虚引用主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用和弱引用的一个区别在于:
虚引用必须和引用队列 (ReferenceQueue)联合使用。
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。
如果程序发现某个虚引用已经被加入到引用队列,
那么就可以在所引用的对象的内存被回收之前采取必要的行动。
由于Object.finalize()方法的不安全性、低效性,常常使用虚引用完成对象回收前的资源释放工作。
一般Android开发当中,关注的是软引用和弱引用,跟在意质量就用软引用,更注重效率就使用弱引用。
final用于声明属性,方法和类,分别表示属性不可交变,方法不可覆盖,类不可继承。
finally是异常处理语句结构的一部分,表示总是执行。
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其他资源回收,例如关闭文件等。
其实并没太多关联:
final:java中的关键字,修饰符。
如果一个类被声明为final,就意味着它不能再派生出新的子类,不能作为父类被继承。
因此,一个类不能同时被声明为abstract抽象类的和final的类。
如果将变量或者方法声明为final,可以保证它们在使用中不被改变.
被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。
被声明final的方法只能使用,不能重载。
finally:java的一种异常处理机制。
finally是对Java异常处理模型的最佳补充。
finally结构使代码总会执行,而不管无异常发生。
使用finally可以维护对象的内部状态,并可以清理非内存资源。
特别是在关闭数据库连接这方面,如果程序员把数据库连接的close()方法放到finally中,就会大大降低程序出错的几率。
finalize:Java中的一个方法名。
Java技术使用finalize()方法在垃圾收集器将对象从内存中清除出去前做必要的清理工作
这个方法是由垃圾收集器在确定这个对象没被引用时对这个对象调用的。
它是在Object类中定义的,因此所的类都继承了它。
子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。
finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。