JVM运行时内存区及垃圾回收机制
JVM运行时内存区
线程共享部分:方法区+堆内存
线程独占部分:虚拟机栈+本地方法栈+程序计数器
线程共享:所有线程都能访问这块内存数据,随着虚拟机或者gc而创建和销毁
线程独占:每个线程都会有它独立的空间,随线程生命周期而创建和销毁
方法区:–存放编译后的类信息
JVM用来加载类信息、常量、静态变量、编译后的代码等数据,虚拟机规范中这是一个逻辑区划。具体实现柑橘不同虚拟机来实现。如:oracle的hotspot在java7中方法区放在永久代,java8放在元数据空间,并且通过gc机制对这个区域进行管理
堆内存:–存放对象实例
划分:老年代、新生代,新生代又可以划分为eden from survivor to survivor
JVM启动时创建,存放对象的实例,垃圾回收器主要就是管理堆内存,如果满了,就会出现OutOfMemoryError,
虚拟机栈:–虚拟机执行类方法存储数据
每个线程都在这个空间有一个私有空间。线程栈由多个栈帧(Stack Frame)组成。一个线程会执行一个或多个方法,一个方法对应一个栈帧
栈帧内容包含:局部变量表、操作数栈、方法返回地址、附加信息等。栈内存默认最大时1m,超出则抛出StackOverflowError
本地方法栈:
和虚拟机栈功能类似,虚拟机栈是为了虚拟机执行JAVA方法而准备的,本地方法栈是为了虚拟机使用Native本地方法而准备的。虚拟机规范没有规定具体的实现,由不同的虚拟机厂商去实现。hotspot虚拟机中虚拟机栈和本地方法栈的实现是一样的,同样,超出大小后会抛出StackOverflowError
程序计数器:
记录当前线程执行字节码的位置,存储的时字节码指令地址,如果执行Native方法,则计数器值为空。每个线程都在这个空间有一个私有的空间,占用内存空间很少。cpu同一时间,只会执行一条线程中的命令。JVM多线程会轮流切换并分配CPU执行时间的方式。为了线程切换后,需要通过城西计数器,来恢复正确的执行位置
垃圾回收机制
自动垃圾收集
自动垃圾收集是查看堆内存,识别正在使用那些对象以及那些对象为被删除以及删除未使用对象的过程
使用中的对象或引用的对象意味着程序的某些部分任然维护指向该对象的指针。
程序的任何部分都不再引用未使用的对象或未引用的对象,因此可以回收未引用对象使用的内存。
*像C这样的编程语言中,分配和释放内存是一个手动过程。
*在JAVA中,解除分配内存的过程由垃圾收集器自动处理。
如何确定内存讯要被回收
该过程的第一步称为标记。这是垃圾收集器识别哪些内存正在使用而那些不在使用的地方
不同类型内存的判断方式
对象回收 - 引用计数
对象回收 - 可达性分析
可达性分析算法
将对象及其引用关系看做一个图,选定活动的对象作为GC Roots;
然后跟踪引用链条,如果一个对象的 GC Roots之间不可达,也就是不存在引用,那么即可认为是可回收对象
可作为GC Root的对象
1、虚拟机栈中正在引用的对象
2、本地方法栈中正在引用的对象
3、静态属性引用的对象
4、方法区常量引用的对象
引用类型和可达性级别
引用类型
1、强引用(StrongReference):最常见的普通对象引用,只要还有强引用指向一个对象,就不会回收
2、软引用(SoftReference):JVM认为内存不足时,才会去试图回收软引用指向的对象。缓存场景
3、弱引用(WeakReference):虽然是引用,但随时可能被回收掉
4、虚引用(PhantomReference):不能通过它访问对象,除了对象被finalize以后,执行指定逻辑的机制(Cleaner)
可达性级别
1、强可达(Strongly Reachable):一个对象可以有一个或多个线程可以通过不同引用访问到的情况
2、软可达(Softly Reachable):就是当我们只能通过软引用才能访问到对象的状态
3、弱可达(Weakly Reachable):只能通过弱引用访问时的状态。当弱引用被清除的时候,就符合销毁条件
4、幻象可达(Phantom Reachable):不存在其他引用,并且finalize过了,只有幻象引用指向这个对象
5、不可达(unreachable):意味着对象可以被清除了
找到一个能够作为入口的对象(常量、栈里面),找出所有被使用的对象,标记出来,没有被标记的就是可以被回收的
垃圾收集算法
标记-清除(Mark-Sweep)算法:首先表示出所有要回收的对象,然后进行清除
标记、清除过程效率优先,有内存碎片化问题,不适合特别大的堆;收集算法基本基于标记-清除的思路进行改进
复制(Copying)算法:划分两块同等大小的区域,收集时将活着的对象复制到另一块区域。
拷贝过程中将对象顺序放置,就可以避免内存碎片化。保留+预留内存,有一定的浪费。
标记-整理(Mark-Compact)算法:类似于标记-清除,但为避免内存碎片化,他会在清理过程中将对象移动,
以确保移动后的对象占用连续的内存空间
分代收集
根据对象的存活周期,将内存划分为几个区域,不同区域采用合适的垃圾回收算法。
新对象会分配到Eden,如果超过-XX:+PretenureSizeThreshold;设置大对象直接进入老年代的法制
新生代-复制算法实现垃圾回收
新生代 Eden from(S0) to(S1)8:1:1
老年代 Tenured
新生代:老年代-》1:2
minor GC
minor GC一定次数未回收对象移动到老年代-XX:MaxTenuringThreshold
老年代-标记-整理算法实现垃圾回收
垃圾收集器
串行收集器 - Serial GC -XX:+UserSerialGC
单个线程来执行所有垃圾收集工作,适合单处理器机器
Client模式下JVM的默认选项
串行收集器 - Serial GC -XX:+UserSerialOldGC
可以再老年代使用,它采用了标记整理(Mark-Compact)算法,区别于新生代的复制算法
用户线程|
------->|
------->|单线程
------->|------->新生代回收
------->|
------->|
|stop-the-world
用户线程|
------->|
------->|单线程
------->|------->老生代回收
------->|
------->|
|stop-the-world
当GC线程执行的时候所有用户线程都要停止,等待GC完毕
GC优化,主要就是如何减少停止
并行收集器 -Parallerl GC -XX:UserParallelGC
并行收集器 -Parallerl Old GC -XX:UserParallelOldGC
server模式JVM默认的GC选择,整体算法和Serial比较相似,区分是新生代和老年代GC都是并行进行;
可以设置GC时间或吞吐量等值,可以自动进行适应性调整Eden,Survivor大小和MaxTenuringThreshold的值。
也成为吞吐量优先的GC:吞吐量=用户代码运行时间/(用户代码那运行时间+GC时间)
-XX:ParallelGCThreads:设置用于垃圾回收的线程数。通常情况下可以和CPU数量相等。
-XX:MaxGCPauseMills:设置最大垃圾回收停顿时间。它的值是一个大于0的整数。
-XX:GCTimeRatio:设置吞吐量大小,它的值是一个0-100之间的整数
-XX:UserAdaptiveSizePolicy:打开自适应GC策略。已达到在堆大小、吞吐量和亭短时间之间的平衡点。
并发收集器-CMS(Concurrent Mark Sweep) GC -XX:+UserConcMarkSweepGC
专用于老年代,基于标记-清除(Mark-Sweep)算法,设计目标是尽量减少停顿时间。
采用的标记-清除算法,存在着内存碎片化问题,长时间运行等情况下发生full GC,导致恶劣的停顿。
CMS会占用更多CPU资源,并和用户线程争抢。
减少了停顿时间,这一点对于互联网web等对时间敏感的系统非常重要,一直到今天,仍然有很多系统使用CMSGC
初始标记 并发标记 重新标记 并发清除
用户线程|
---------->| ------------> =======>------------->
---------->| =======> =>------------->
---------->|=>------------> =======> =======>
---------->| =======> =======>------------->
---------->| ------------> =======>------------->
stop-the-world
优化尝试和用户线程一起去标记,从而减少停顿
CMSGC JDK1.8不在维护 1.9被替代
并行收集器 -PerNew GC -XX:+UserParNewGC
针对于新生代GC实现,他实际是Serial GC的多线程版本
可以控制线程数量,参数 -XX:ParallerGCThreads
最常见的应用长江是配合老年代CMS GC 工作。参数 -XX:+UserConcMarkSweepGC
用户线程|
------->|
------->|多线程
------->|------->新生代回收
------->|------->
------->|
|stop-the-world
并发收集器 -G1 -XX:+UserG1GC
针对大堆内存设计的收集器,兼顾吞吐量和挺短时间,JDK9后为默认选型,目标是替代CMS;
G1将堆分成固定大小的区域,Region之间是复制算法,但整体上时间可看做是标记-整理(Mark Compact)算法,可以有效地避免内存碎片。找不到大内存是执行FullGC
新生代收集 标记 清理 混合收集 都会导致暂停
年轻代
Serial ParNew ParallelScavenge
1 2 3 4 5 6 G1
1 3 2 4 5 6
CMS Serial Old Parallel Old
老年代
垃圾回收的时候 常用的收集器组合 3 6默认 G1 常用的三种