一、大概内容
jvm内存模型、垃圾回收算法、垃圾回收器
二、jvm内存模型
jvm内存分为堆、虚拟机栈、本地方法栈、程序计数器、方法区。这些都属于运行时数据域。
jvm的堆内存:(线程间共享的)是创建的对象占用的内存区域,几乎所有的对象都是在堆内存中创建的(还有部分对象是在栈内存中创建的)
堆内存一般分为年轻代和老年代,一般的堆内存模型为 Eden: Survivor(from:to 1:1): Old
eden区和survivor区是属于年轻代对象所在的区域的。每个对象的对象头都会存在一个关记录龄的数值,当创建的对象每抗过一次垃圾回收,就会给对象头的年龄值加一,一个默认的晋升老年代的年龄阈值是15,当对象经过15次垃圾回收还没有被回收的时候,就会被提升为老年代对象(也有直接提升老年代的,survivor放不下,直接放到老年代)。
虚拟机栈:栈内存是每个线程私有的,他的生命周期和线程是相同的。虚拟机栈描述的是java方法执行的线程内存模型,每个方法执行的时候都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法执行一次就会产生一个栈帧,执行时入栈,执行结束后会出栈。
本地方法栈:为虚拟机提供使用本地方法服务。
方法区:(线程共享的内存区域)线程共享的内存区域,存储已经被虚拟机加载的类信息,常量,静态变量,即时编译后的代码缓存等数据。
运行时常量池是方法区的一部分。class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池信表,用于存放编译期生成的各种字面量与符号引用,这部分内容在类加载后存放在方法区的运行时常量池。(string类可以用intern方法放入常量池)
程序计数器:(线程私有的)一个较小的空间,可以看做当前线程执行的字节码的行号指示器。
三、关于对象
根据对象在内存的分布可以分为三部分:对象头、实例数据和对其填充。
对象头:包含hash码,gc年龄分代,锁状态,线程持有的锁,偏向线程id、偏向时间戳
四、永久代和元空间
jdk7之前,常量池是在方法区的,jdk7及以后常量池被移动到堆内存中。
jdk7有永久代,jdk8改为元空间
元空间的参数:
-XX:MaxMetaspaceSize=512M 元空间大小
-XX:MetaspaceSize=128M 元空间的初始大小
-XX:MinMetaspaceFreeRatio 作用是控制垃圾收集后最最小的元空间剩余容量的百分比,可以减少因为元空间不足导致的垃圾回收的频率。-XX:MaxMetaspaceFreeRatio 控制最大的元空间剩余容量的百分比
五、垃圾收集器与内存分配策略
判断对象是否存活:
1、引用计数器
对象头部有计数器,每被引用一次,计数器就加一,引用失效就减一,计数器为0时,对象就是不可能再被引用的。
但是无法解决循环引用的问题
2、可达性分析
从gcroot根对象开始,根据引用关系向下搜索,所走过的路径称为引用链。如果一个对象到gcroot没有任何引用链相连,那么对象就可以被回收。
可以作为gcroot的对象:
虚拟机栈(栈帧中的本地变量表)中引用的对象,比如当前正在运行的方法所使用的参数、局部变量、临时变量。
方法区中静态属性引用的对象,比如java类的引用类型静态变量。
方法区中常量引用的对象,比如常量池中引用的对象。
本地方法栈中JIN(所说的native方法)引用的对象
java虚拟机内部对像的引用,如基本数据类型对应的class对象,常驻的异常对象,还有系统的类加载器
所有被同步锁(synchornized)持有的对象
反应虚拟机内部情况的jmxbean,jvmti中注册的回调,本地代码缓存等
3、引用类型(四种)
强引用:
像Object obj = new Object();这种引用关系 ,只要引用关系存在,对象就不会被回收。
软引用:
只被软引用关联的对象,在系统发生oom异常之前,把这些对象列入回收范围,进行第二次回收,若果还是没有足够的内存,才会抛出异常。jdk1.2版本提供了SoftReference类来实现软引用。
弱引用:
当垃圾收集器开始工作,无论内存是否足够,都会回掉只被弱引用关联的对象。
虚引用:
为了在对象被回收时能够得到通知。jdk1.2提供了PhantomReference实现虚引用。
六、分代回收理论
在java虚拟机里边,垃圾回收是分代的,分为年轻代和老年代。年轻代和老年代在内存的不同区域,因此java的垃圾回收也是针对特定内存块的垃圾回收。
垃圾回收的种类有MinorGC(回收年轻代),MajorGC(回收老年代,目前只有CMS收集器有单独收集老年代的行为),MixedGC(混合回收,年轻代和老年代一起回收),Full GC(整堆回收)
七、垃圾收集算法
标记-清除算法:
可以先表计所有需要回收的对象,然后对需要回收的对象进行回收也可以标记存活的对象,对没有被标记的对象进行回收。
缺点:清除之后会造成很多连续不断地内存碎片,可能会导致分配大对象时找不到连续的内存,而不得不进行另外的垃圾回收。
标记清除的执行效率都会随对象的增长而变的越来越低。
复制算法:
将内存分为大小相等的两块,其中一块使用完之后,会将存活的对像复制到另一块上去(存活的对象比较少,复制所使用的开销不大),然后直接清理之前使用过的内存区域。
缺点:可利用内存直接将为原来的一半儿,内存利用率比较低。
优点:不会产生内存碎片化的问题
标记整理算法:
标记活动对象,将活动对象移到内存的一边,清理另一边的内存。(对象移动需要全程暂停用户的应用程序)
缺点:对象存活率较高时,需要进行比较多的复制操作,但是效率会降低。
优点:不会产生内存碎片化的问题
八、垃圾收集器
1、Serial
最基础的,单线程的新生代垃圾收集器
2、ParNew
是serial的多线程并行版本,除了包含多线程回收垃圾,还包含serial收集器可用的所有控制参数
-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HasnlePromotionFailure等,收集算法、stop-the-word、对象分配规则、回收策略、都与Serial完全一致。
新生代采用复制算法、暂停所有的用户线程(是多线程的)
老年代采用标记-整理算法,暂停用户所有的线程
jdk5出现的CMS收集器,cms只能和ParNew搭配使用。parnew作为新生代,cms作为老年代
3、Parallel Scavenge 收集器
基于 标记-复制算法实现的收集器,是能够并行进行的多线程收集器。
Parallel Scavenge目标是达到一个可控的吞吐量(用于用户运行代码的时间与处理器总消耗时间的比值)。
两个参数:
最大垃圾收集停顿时间: -XX:MaxGCPauseMillis,大于0的毫秒数
吞吐量大小: -XX:GCTimeRatio
Parallel Scaenge收集器的自适应策略,-XX:UseAdaptiveSizePolicy,开启后只需要指定最-Xmx大小、和-XX:MaxGCPauseMillis
参数(最关心最大停顿时间)或 -XX:GCTimeRatio(最关心吞吐量)。虚拟机可以自动调整其他的参数细节(eden和survivor区的比例,晋升老年代对象大小)
4、Serial Old
是serial老年代版本的收集器,同样是一个单线程的收集器,使用标记-整理算法。
5、Parallel Old
是Parallel Scavenge的老年代版本、支持多线程的收集器,基于标记-整理算法
Parallel 和Parallel Old收集器一起搭配使用(吞吐量优先)
6、CMS收集器
是一种以短停顿时间为目标的收集器。CMS基于标记-清除算法。初始标记和重新标记仍然需要stop-the-word。
清理过程:
初始标记:标记gc root能够关联的对象,速度块;
并发标记:从gc root的直接关联对象开始,遍历整个对象图的过程,耗时较长,但是不需要停顿用户的线程。用户线程可以与垃圾回收线程一起并发运行。
重新标记:修正并发标记期间,因用户线程继续运行而导致标记产生变动的那一部分对象的标记记录,这阶段比初始标记停顿时间要长,但是比并发标记阶段的时间短。
并发清除:删除标记阶段判断死亡的对象,由于不需要移动存活对象,所以这个阶段可以与用户线程同时并发。
缺点:cms默认启动的回收线程数是(处理器核心数量+3)/4,如果核心数量在4以上,那么垃圾收集线程只占用不少于1/4的处理器运算资源,并且随核心数量增大而下降。但是当核心数量在四个以下的时候,需要分出至少一半儿的运算能力去执行收集线程,可能会导致用户的程序执行速度大幅降低。
会产生浮动垃圾:并发标记和并发清理阶段,由于是和用户线程一起运行,所以该阶段产生的对象,需要在下一个垃圾回收期间才能够被回收。因此cms收集器需要保留一部分内存资源供自己使用。jdk5默认老年代使用68%就会触发cms开始清理垃圾,如果老年代增加的不是太快,那么可以适当调高参数-XX:CMSInitiatingOccu-pancyFraction来提高CMS触发的百分比。
JDK6默认提高至92%。
cms会产生内存碎片,-XX:CMSFullGCsBeforeCompaction(jdk9开始废弃),在若干次不整理空间的full gc后下一次进行full gc前会先进行碎片整理。
默认为0,每次都整理。
7、Garbage First收集器(G1GC)
g1gc也是根据分代理论的,g1gc将内存分为大小相等的region,region大小可以是2的0次幂到5次幂。1m-32m
Humongous区域专门存储大对象,在g1gc中只要对象超过了整个region容量的一半即可判定为大对象。
-XX:G1HeapRegionSize 参数设定region的大小。对于超过整个region容量的超大对象,会被存放在N个连续的Humongous Region中。
G1的大多数行为都把Humongous Region 当做老年代来看待。
g1的老年代和年轻代区域不再是连续的,而是一系列区域的动态集合,一个region可以存储年轻代对象,也可以存储老年代对象
回收过程
初始标记:标记一下GC Root能够关联到的对象,并且修改TAMS指针的值,需要线程停顿,让下一阶段用户线程并发运行时能够正确的在可用region中分配对象。需要线程停顿,但是耗时短,而且是借助minor gc时完成。
并发标记:从gcroot 开始对堆对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,比较耗时,但是与用户线程并发执行。扫描完成后,需要重新处理ssatb记录下的在并发时有引用变动的对象。
最终标记:用户线程停顿,处理并发标记后仍然留下的少量的satb记录。
筛选回收:负责更新region的统计数据,对个region的回收价值和成本进行排序,更具用户期望的停顿时间来制定回收计划,可以选择任意多个region构成回收集,然后把决定回收的一部分region的存活对象复制到空的region中,再清理掉整个旧的region空间。(这里涉及对象的移动,必须暂停用户线程,有多条线程并行完成)
八、G1垃圾收集器和CMS垃圾收集器的区别
G1垃圾收集器和CMS垃圾收集器的主要区别体现在以下四个方面:
- 使用范围:CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用。而G1收集器则收集范围是老年代和新生代,不需要结合其他收集器使用。
- 停顿时间:CMS收集器以最小的停顿时间为目标。而G1收集器则具有可预测的垃圾回收停顿时间的能力(建立可预测的停顿时间模型),这是其相较于CMS的一个优势。
- 垃圾碎片:CMS收集器使用“标记-清除”算法进行垃圾回收,这容易导致内存碎片的产生。而G1收集器使用的是“标记-整理”算法,进行了空间整合,没有产生内存空间碎片。
- 垃圾回收过程:这个方面的描述比较抽象,简单来说,CMS和G1的垃圾回收过程是不同的。
九、类加载机制
生命周期
加载
连接:验证、准备、解析
初始化:
使用:
卸载:
类加载器:
启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器。
双亲委派模型
工作过程:如果一个类加载器收到了类加载请求,他自己不会尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,把类加载的请求委派给父类,只有当父类反馈无法加载时,子加载器才会尝试自己加载完成。
显而易见的好处是,类和他的加载器一起具备了带优先级的层次关系。
双亲委派模型的优点在于,它保证了任何类加载器收到的对java.lang.Object的加载请求,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。此外,双亲委派模型还避免了Java核心API的修改,从而保持了Java平台的长久稳定。