java内存结构
官网地址:The Java® Virtual Machine Specification
英语好的同学可以参考官方文档。
上图
可以看出JVM的结构分为:堆(Heap)、方法区(Method Area)、本地方法栈(Native Method Stack)、虚拟机栈(VM stack)、程序计数器(Program Counter Register)。这里整理总结出个模块的特点如下:
名称 | 描述 | 存放信息 | 是否共享 |
---|---|---|---|
堆(Heap) | 用于存放类实例和数组元素,java中所有通过new创建出来的对象实例都存放在此,是所有线程关共享的。当堆中无法存放新的实例,并且堆无法扩展时,就会发生oom(OutOfMemoryError)异常,我们常说的垃圾回收器就是对这个区域的管理 | 存放new出来的类信息 | 线程共享 |
方法区(Method Area) | 方法区主要用于存储每个类的结构,例如运行时常量池、字段和方法和构造函数的代码,包括在类和实例初始化和接口初始化中使用的特殊方法 ,方法区里面数据jvm线程栈共享。机制和堆类似 | 存放类信息,常量等 | 线程共享 |
虚拟机栈(VM stack) | 每个方法在执行的同时会创建一个栈桢(stack frame)用于存储局部变量表、操作数栈、动态链表、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着栈桢在虚拟机栈中入栈到出栈的过程。储局部变量表、操作数栈、动态链表、方法出口等信息,当栈内存沾满时 会StackOverflowError异常 | 方法局部变量,方法运算等 | 线程私有 |
本地方法栈(Native Method Stack) | 和虚拟机栈类似,只不过本地方法栈是运行java 中native方法的栈帧 | 本地方法运行的信息 | 线程私有 |
程序计数器(Program Counter Register) | 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)。了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各个线程的计数器之间互不影响,独立存储,我们称该类内存区域为线程私有 | ||
如果线程正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址。 为了保证程序切换回来后能正常继续执行。 | 记录当前线程运行代码行号 | 线程私有 |
堆
- 堆的分代模型
堆的分代模型从结构上来说可以分为:年轻代和老年代
其中年轻代里面又分为 Eden,To Survivor,From survivor三个区域。jvm默认三个区域内存占比为8:1:1。可通过参数-Xms 设置初始 Java 堆大小,-XX:SurvivorRatio=n设置eden和Survivor 的比例,具体参考JVM–各类参数详解及参数调优
Eden区存放新产生的对象,当eden存满之后会触发YGC机制,另外大对象会直接存入老年代。
伊甸园区的对象经历过15次(为什么是15?CMS是6为什么)回收之后会晋升进入老年代对象。
如何判断垃圾
-
可达性分析算法(主流)
可达性分析算法也被称为根搜索法,基本实现方式是通过一系列的根节点(GC ROOT)作为起点,向下搜索引用的变量和对象,能直接或间接被根对象引用的对象或节点被称为可达性对象,没有被引用的被称为不可达对象。也是垃圾回收的对象。 -
怎么判断GCROOT根节点?
目前Java中可以作为GC ROOT的对象有:
1、虚拟机栈中引用的对象(本地变量表)
2、方法区中静态属性引用的对象
3、已启动线程引用的对象
4、本地方法栈中引用的对象(Native对象)
目前基本所有GC算法都引用根搜索算法这种概念。
- 引用计数法
引用计数法简而言之就是每个对象被创建时会有一个对应的引用计数器,当对象被引用时计数器加1,当引用中断时计数器减1。在垃圾回收时,那些引用计数器为0的对象就被称作垃圾对象,可以被收集。
优点:实现简单,程序不会被长时间打断
缺点:需要额外空间来存储计数器,对循环引用很难检测。
怎么解决循环引用?
垃圾回收机制
回收算法及优劣点
-
标记-清除算法
字面意思就是先标记,然后逐个删除。
缺点:
1、效率低,标记和删除的效率都不高。
2、会产生大量不连续的碎片空间,导致后面需要分配较大的对象时 无法找到足够的内存而触发垃圾回收 -
标记-整理算法
字面意思是先标记垃圾,然后对所有存活对象归集到一起的算法
可解决标记清除算法产生大量碎片空间的问题,主要用于老年代回收。 -
复制算法
复制算法就是将堆内存分为两份,每次回收一份,存活的对象复制到另一份内存,如此循环。
以上三种算法都存在各种缺陷 -
分代收集算法(目前主流的算法,可以看成复制算法+标记整理算法组合)
分代收集(Generational Collector)算法的将堆内存划分为我们熟悉的新生代、老年代。新生代又被进一步划分为 Eden 和 Survivor 区,其中 Survivor 由 From Survivor和 ToSurvivor组成。所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。新生代,老年代使用不同的回收管理算法。
常见的新生代回收器有:Serial 收集器(复制算法)、ParNew 收集器(复制算法)、Parallel Scavenge 收集器(复制算法)
常见的老年代回收器有:Serial Old 收集器、Parallel Old 收集器(标记-清除算法)、CMS收集器(标记-清除算法)
G1垃圾回收器,打破原有的分代模型的回收器
可以看出G1垃圾回收器引入了Region分区的概念,弱化了分代概念。G1垃圾回收器会把内存分成指定大小的单独Region,每个Region会被标记为E、S、O、H,可以逻辑上看成以分代模型中的Eden、Survivor、Old,其中H表示巨型区域Humongous Region,用于存放大型对象。
G1的垃圾回收算法和CMS类似,都是采用三色标记的算法。
G1相比CMS垃圾回收器,可以对堆内存分块进行扫描管理,不像CMS回收每次扫描整块堆内存区域,G1每次扫描只是对内存快满的Region进行扫描,可以并行并发的运行,缩短stw停顿时间
JVM监控小窍门
1.JVM可视化监控窗口:window cmd运行命令:jvisualvm打开
3、阿里开源jvm 调试工具(阿尔萨斯):https://arthas.aliyun.com/doc/index.html