JVM和GC的简单解析

为了最近面试,特意整理了一下多年前的笔记,再次分享给大家(适用于hotStop的_1.7/1.8)

jvm简单图解


· JVM调优是调整:方法区和堆(主要是堆)

· 栈管运行,堆管存储

· 堆和方法区是是所有线程共享的内存区域;栈和程序计数器是每个线程私有内存区域。

注:由于栈是每个线程私有的,栈空间也是每个线程的运行内存

· 运行时数据区也就是我们的JVM内存模型区域。JVM在运行程序时会将他拥有内存 逻辑性的划分,让每部分内存做它该做事情。

类加载器(class loader):加载.class文件,生成类模板。(只负责类的加载,能不能运行要看Execution Engine执行引擎)

类加载器分为:

启动类加载器,

扩展类加载器,

应用类加载器

(双亲委派机制,往上抛,上面的如果可以加载,就用上面的);

本地方法接口(Native Interface):调用C/C++的本地库

本地方法栈(Native Method Stack):让执行引擎加载本地方法

程序计数器:用来存储指向下一条指令的地址,也即将要执行的指令代码

执行引擎(Execution Engine):负责解释命令,提交操作系统执行

栈(stack):

每个线程有一个私有的栈,随着线程创建而创建

存储8种基本数据类型+引用变量(存储堆中的地址)+实例方法

压栈,后进先出

java.lang.StackOverFlowError 栈溢出异常(创建的引用太多,或死循环)

方法区(Method Area):存储 -- 静态变量,常量,类模板(构造方法/接口定义)

--------------------------------------------------------------------------

堆(heap):分为三个区域

新生区

  伊甸园区

  幸存0区

  幸存1区

养老区

永久区

1、新生代+老年代 = 实际堆内存

2、1.7永久代 = 方法区(存放静态变量,常量,类模板);

  1.8永久代 = 方法区(存放静态变量,常量)+本地内存-元空间(存放类模板), 改善了之前依赖的jar包太多,加载的类模板太多,启动项目时会出现永久区OOM:PermGen space的情况

新生代占堆空间的1/3,其中伊甸园区:From区:To区 = 8:1:1

老年代占堆空间的2/3

--------------------------------------------------------------------------

GC(Garbage Collection):分区收集算法;新生区和养老区的收集算法不同

频繁收集新生区:minorGC 轻量级GC:使用复制算法

较少收集养老区:majorGC fullGC:使用标记清除算法,标记压缩算法

基本不动永久区

majoirGC速度比minorGC慢10倍以上

GC的四种回收算法:

1.引用计数法(native源码中有,但不使用):维护一个kv结构,key为变量,v为引用次数,每次清理v=0的变量,但变量互相引用后,如果变量赋值为null,那么v>0就不会被清除

2.复制算法(Copying):优点是没有内存碎片,缺点是复制过程中内存占用大

3.标记清除算法(Mark-Sweep):优点是占内存较小,缺点是清除后内存空间不连续,会生成内存碎片;标记+清除形成两次遍历,并暂停程序,且耗时严重

4.标记压缩(Mark-Compact):优点是没有内存碎片,占内存小,但是过程复杂执行时间长

5.标记清除压缩算法(Mark-Sweep-Compact):多次标记清除后,才触发压缩。有效减少标记压缩的执行时间(GMS实际使用的算法)

可达性分析算法:被栈、方法区正在引用着的对象,是可达对象;清理没有引用的对象(清理不可达对象)。

扩展:

复制算法的瓶颈是:因为只复制存活的对象,所以复制算法适用于存活率较低的场景;如果因为代码问题导致存活率较高时,复制的时间会越来越长。

标记清除算法要暂停程序的原因:

标记清除其实是三次标记,一次清除,第1、3次标记时会stop the world


如果remark标记阶段把A对象标记为1,而程序运行时又将A变为可达对象,但A仍然会被清除

--------------------------------------------------------------------------

解析:

new对象的时候,对象会出生在伊甸园区,当JVM没办法创建对象,比如当伊甸园区满了,会执行minorGC(轻GC),伊甸园区没有引用的对象和幸存0区没有引用的对象(from区),大约98%的对象会被minorGC回收,存活的对象copy(复制算法)到幸存1区(to区),然后将存储幸存对象的to区变成from区,将空的from区变成to区,幸存对象的年龄会加1,

当伊甸园区又满了之后,新的from区(里面是上次minorGC幸存的对象)的没有被引用的对象和伊甸园区没有被引用的对象,会被minorGC回收,当幸存对象的年龄到达了15,这个对象将会存储到养老区。

当养老区满了之后会执行majorGC(重GC),使用标记清除算法或标记压缩算法

标记清除算法:先标记幸存的对象,清除未被标记的对象。

标记清除压缩算法:先标记幸存的对象,清除未被标记的对象,然后整理成内存连续的空间

注:当minorGC后,将存活的对象copy到To区时,To区没存不够,会直接将剩下的对象放入养老区;所以某些特定场景下可以增大幸存0/1区的内存,减少对象进入养老区的数量,从而减少fullGC

--------------------------------------------------------------------------

1、JVM堆内存设置不够

2、老年代满了(存在大量对象,不能被gc清理,对象有引用

会引起java.lang.OutOfMemoryError:java heap space  堆内存溢出异常。

解决办法

-XX:+printGCDetails :输出详细GC处理日志

-XX:+HeapDumpOnOutOfMemoryError (发生OOM的时候生成内存快照)

当出现堆内存溢出(OOM):可以分析MAT(Eclipse Memory Analyzer 内存分析器)

分析dump快照文件,快速定位内存泄漏

获取堆中对象的数据

获取对象相互引用的关系

--------------------------------------------------------------------------

· jvm调优主要是调整堆和方法区

堆内存调优常用参数解析:

-Xms :分配初始内存,默认物理内存的1/64

-Xmx:分配最大内存,默认为物理内存的1/4

-XX:MaxTenuringThreshold:对象进入老年区要经过minorGC的次数阈值,默认15,经验值31

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容