1、
1.1何为“垃圾”?就是指所有不再存活的对象。常见的判断是否存活有两种方法:引用计数法和可达性分析!
引用计数法:为每一个创建的对象分配一个引用计数器,用来存储该对象被引用的个数。当该个数为零,意味着没有人再使用这个对象,可以认为“对象死亡”。(问题:无法检测“循环引用”),因此java里没有采用这样的方案来判定对象的“存活性”。
可达性分析:思路是把所有引用的对象想象成一棵树,从树的根节点(GC Roots)出发,持续遍历找出所有连接的树枝对象,这些对象被称为“可达”对象(存活对象),其余就被视为“死亡”的不可达对象(即垃圾)。
GC Roots:本身是可达的,一般java里哪些对象一定可达呢?
*虚拟机栈中的引用的对象
*方法区中静态属性引用的对象
*方法区中常量引用的对象
*本地方法栈中JNI引用对象
1.2 如何回收垃圾?
*标记-清理(简单方便,但是容易产生内存碎片):第一步:标记就是利用可达性遍历堆内存,把存活对象和垃圾对象进行标记;第二步:垃圾已经被标记,再遍历一遍,把所有垃圾对象所占的空间直接清空即可。
*标记-整理:上面方法会产生内存碎片,那就在清理的时候,把所有存活对象扎堆到同一个地方,这个适合存活对象多垃圾少的情况。
*复制:直接把堆内存分成两部分,一段时间只允许在其中一块内存上进行分配,当这款内存分配完后,则执行垃圾回收,把所有存活对象全部复制到另外一块上,当前内存则全部清空。(适用于存活少,垃圾多的情况)
1.3.java垃圾回收概况:
在java虚拟机中,存在自动内存管理和垃圾清扫机制。概括的说,该机制对JVM中的内存进行标记,并确定哪些需要回收,根据一定的回收策略,自动回收内存,永不停息的保证JVM的内存空间,防止出现内存泄漏和溢出问题。
GC机制主要完成三件事:确定哪些内存需要回收,确定什么时候需要执行GC,如何执行GC。
目的:排查各种内存溢出或泄露问题,解决性能瓶颈,达到更高的并发量,写出更高的程序。
从四个方面学习GC机制:1.内存是如何分配的。2、如何保证内存不被错误回收。3、在什么情况下执行GC以及执行GC的方式。4、如何监控优化GC机制。
在Java运行时的数据区里,由JVM管理的内存区域分为下图几个模块:
图片.png
其中①、程序计数器:是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,可以理解为当前线程的行号指示器。字节码解释器在工作时,会通过改变这个计数器的值来取吓一跳语句指令。每个程序计数器只用来记录一个线程的行号,所以他是线程私有的(一个线程就一个程序计数器)。
如果执行的是java方法,则计数器记录的是正在执行的虚拟机字节码指令地址,由于程序计数器只是记录当前指令地址,所以不存在内存溢出的情况,因此,程序计数器也是所有JVM内存区域中唯一一个没有定义OutOfMemoryError的区域。
②、虚拟机栈:一个线程的每个方法在执行的同时,都会创建一个栈zhen,栈帧中存储的有局部变量表、操作站、动态链接、方法出口等,当方法被调用时,栈帧在JVM中入栈,当方法执行完,栈帧出栈。
局部变量表综存储着方法的相关局部变量,包括各种基本数据类型,对象的引用,返回地址等。在局部变量中,只有long和double类型会占用两个局部变量空间(Slot,对于32位机器,一个Slot就是32个bit),其它都是1个Slot。注意:局部变量表示在编译时就已经确定好的,方法运行所需要分配的空间在栈帧中是完全确定的,在方法的生命周期内都不会改变。虚拟机栈中定义了两种异常,如果线程调用的栈深度大于虚拟机允许的最大深度,则抛出StatckOverFlowError(栈溢出);不过多数Java虚拟机都允许动态扩展虚拟机栈的大小(有少部分是固定长度的),所以线程可以一直申请栈,直到内存不足,此时,会抛出OutOfMemoryError(内存溢出)。
每个线程对应着一个虚拟机栈,因此虚拟机栈也是线程私有的。
③、本地方法栈:本地方法栈在作用,运行机制,异常类型等方面都与虚拟机栈相同,唯一的区别是:虚拟机栈实质性java方法的,而本地方法栈是用来执行native方法的,在很多虚拟机中,会将本地方法栈与虚拟机栈放在一起使用。
本地方法栈也是线程私有的。
④、堆(Heap):堆区是理解java GC机制最重要的区域,没有之一。在JVM所管理的内存中,堆区是最大的一块,堆区也是java GC机制所管理的主要区域,堆区由所有线程共享,在虚拟机启动时创建。堆区的存在是为了存储对象实例,原则上讲,所有的对象都是在堆上分配内存(不过现在技术里,也不是那么绝对,也有栈上直接分配的)。
一般的,根据java虚拟机规定范围,堆内存需要在逻辑上是连续的(在物理上不需要),在实现时,可以是固定大小的,也可以是可扩展的,目前主流的额虚拟机都是可扩展的。如果在执行垃圾回收之后,仍没有足够的内存分配,也不能再扩展,将会抛出OutOfMemoryError:Java heap space异常。
关于堆区的内容还有很多,将在下节“Java内存分配机制”中详细介绍。
⑤、方法区:在java虚拟机中,将方法区作为一个堆的逻辑部分来对待,但事实上,方法去并不是堆;另外,Java GC的分代收集机制分为3个代:青年代,老年代,永久代,这些作者将方法区定义为“永久代”,这是因为,对于之前的HotSpot Java虚拟机的实现方式中,将分代收集的思想扩展到了方法区,并将方法区设计成了永久代。不过,除HotSpot之外的多数虚拟机,并不将方法区当做永久代,HotSpot本身,也计划取消永久代。本文中,由于笔者主要使用Oracle JDK6.0,因此仍将使用永久代一词。
方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。
方法区在物理上也不需要是连续的,可以选择固定大小或可扩展大小,并且方法区比堆还多了一个限制:可以选择是否执行垃圾收集。一般的,方法区上执行的垃圾收集是很少的,这也是方法区被称为永久代的原因之一(HotSpot),但这也不代表着在方法区上完全没有垃圾收集,其上的垃圾收集主要是针对常量池的内存回收和对已加载类的卸载。
在方法区上进行垃圾收集,条件苛刻而且相当困难,效果也不令人满意,所以一般不做太多考虑,可以留作以后进一步深入研究时使用。
在方法区上定义了OutOfMemoryError:PermGen space异常,在内存不足时抛出。
运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存储编译期就生成的字面常量、符号引用、翻译出来的直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址,将在类链接阶段完成翻译);运行时常量池除了存储编译期常量外,也可以存储在运行时间产生的常量(比如String类的intern()方法,作用是String维护了一个常量池,如果调用的字符“abc”已经在常量池中,则返回池中的字符串地址,否则,新建一个常量加入池中,并返回地址)。
⑥直接内存:直接内存并不是JVM管理的内存,可以这样理解,直接内存,就是JVM以外的机器内存,比如,你有4G的内存,JVM占用了1G,则其余的3G就是直接内存,JDK中有一种基于通道(Channel)和缓冲区(Buffer)的内存分配方式,将由C语言实现的native函数库分配在直接内存中,用存储在JVM堆中的DirectByteBuffer来引用。由于直接内存收到本机器内存的限制,所以也可能出现OutOfMemoryError的异常。
——————————————————————————————
Java对象的访问方式:一般来说一个java的引用访问涉及到3个内存区域:JVM栈,堆,方法区。
最简单的本地变量引用:Object obj = new Object()
obj表示一个本地引用,存储在JVM栈的本地变量中,表示一个reference类型数据;
new Object()作为实例对象数据存储在堆中;
堆中还记录了Object类的类型信息(接口,方法,field,对象类型等)地址,这些地址所执行的数据存储在方法区中;在java虚拟机规范中,对于通过reference类型引用访问具体对象的方式没有规定,目前直流的实现方式有两种:1.通过句柄访问(这种实现方法由于用句柄表示地址,因此十分稳定。);2.通过直接指针访问(这种方法最大的优势是速度快,在HotSpot虚拟机中用的就是这种方式。)。
2. 图片.png
————————————————————————————————————
垃圾收集器(GC机制中,起重要作用的是垃圾收集器,垃圾收集器是GC的具体实现)
1.Serial收集器
2.ParNew收集器
3.Parallel Scavenge 收集器
4.Serial Old收集器
5.Parallel Old收集器
6.CMS(Concurrent Mark Sweep)收集器
网友评论