java为什么要做垃圾回收
在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象;而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。垃圾回收能自动释放内存空间,减轻编程的负担,JVM的一个系统级线程会自动释放该内存块。垃圾回收意味着程序不再需要的对象是"无用信息",这些信息将被丢弃。当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。事实上,除了释放没用的对象,垃圾回收也可以清除内存记录碎片。由于创建对象和垃圾回收器释放丢弃对象所占的内存空间,内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存洞。碎片整理将所占用的堆内存移到堆的一端,JVM将整理出的内存分配给新的对象。
jvm内存模型
堆栈分开设计是为什么呢?
栈存储了处理逻辑、堆存储了具体的数据,这样隔离设计更为清晰
堆与栈分离,使得堆可以被多个栈(线程)共享。另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。
栈保存了上下文的信息,因此只能向上增长;而堆是动态分配
1.栈区JVM Stack
线程私有,生命周期与线程相同。每个方法执行的时候都会创建一个栈帧(stack frame)用于存放基本数据类型和堆中对象的引用、局部变量表、操作栈、动态链接、方法出口。栈中一个对象只对应了一个4btye的引用。
2.本地方法栈Native Stack
本地方法栈区,与jvm stack类似,不过此区域是为调用本地方法服务的(栈的空间大小远远小于堆)
3.堆(Heap)
存放对象实例、数组等,所有的对象的内存都在这里分配。垃圾回收主要就是作用于这里的。
4.方法区
方法区与Java堆一样,是各个线程共享的区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译(JIT)后的代码等数据。
虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。
5.程序计数器
这里记录了线程执行的字节码的行号,在分支、循环、跳转、异常、线程恢复等都依赖这个计数器。
堆内存分配机制
Young Generation年轻代
Eden伊甸园区
Survivor Space两个存活区
Old Generation老年代
jvm垃圾回收机制
要了解回收机制前需要了解一下概念
分配原则:
对象优先在Eden区域分配
大对象直接进入老年代
长期存活的对应将进入老年代
class、常量等信息jvm直接加载进方法区
GC定义:
活对象:被引用的对象
垃圾对应:不再被引用的对象
垃圾回收是发现活对象和回收垃圾对象的过程
GC的作用:
分配内存
确保被引用对象不被回收
回收不可达对象
java引用类型:
强引用-Object o = new Object();如果不释放引用是不会被回收的
软引用-在内存不足要发生一出时进行回收,适合cache使用
弱引用-比软引用更弱,无论内run是否足够都会被回收
虚引用-无法通过引用获取对象实力,也不会影响对象生命周期,只是希望垃圾回收时收到一个系统通知
jvm内存申请过程
1.绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡,Eden区是连续的内存空间,因此在其上分配内存极快
2.当Eden区满的时候,执行MinorGC,将消亡的对象清理,并将剩余的对象复制到一个存货去Survivor0
3.此后,每次Eden满了,就执行一次MinorGC,将消亡的对象清理,并将剩余的对象都添加到Survivor0
4.当Survivor0也满的时候,将其中仍然存活的对象直接复制到Survivor1,以后Eden执行MinorGC后,就将剩余的对象添加到SUrvivor1
5.当两个存活区切换了几次(HotSpot虚拟机默认15次,用-XX:MaxTenuringThreshold控制,大于该值进入老年代,但这只是个最大值,并不代表一定是这个值)之后,仍然存活的对象将复制到老年代
6.大对象直接进入老年代
7.当Old区的空间不够时,JVM会在Old区进行FULLGC
8.垃圾完全回收后,若Survivor及Old区仍然无法存放从Eden区复制过来的部分对象,会导致JVM无法在Eden区为新对象创建内存区域,则会导致Out of memory错误
2.几种垃圾回收机制
2.1.标记-清除收集器
这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。
2.2.标记-压缩收集器
有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。
2.3.复制收集器
这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,jvm生成的新对象则放在另一半空间中。gc运行时,它把可到达对象复制到另一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。
2.4.增量收集器
增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。
2.5.分代收集器
这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。jvm生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。
2.6.并发收集器
并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。
2.7.并行收集器
并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多cpu机器上使用多线程技术可以显著的提高java应用程序的可扩展性。
网友评论