Java虚拟机
导图
1、概述
1.1、简介
·一处编辑四处运行、⾃从内存管理机制之下,不再需要为没⼀个new操作去写配对的内存分配和回收等代码, 不容易出现内存泄漏和内存溢出等问题。
1.2、运行时数据区
·线程共享数据区:方法区、堆
·线程隔离数据区:程序计数器,java虚拟机栈、本地方法栈
2、运行时数据区
2.1、程序计数器
·简介:程序计数器是内存一块较小的空间,它可以当做是当前线程执行字节码的行号指示器、字节码解析器工作时就是通过行号来选取下一条执行的字节指令。分支、循环、跳转、异常处理,线程恢复都是靠程序计数器完成。
·作用:为了线程切换后能恢复到正确的执⾏位置,每条线程都需要有⼀个独⽴的程序计数器,各条线程之间计数器互不影响,独⽴存储,我们称这类内存区域为“线程私有”的内存
·特点:内存区域唯一么有oom(OutOfMemoryError)区域
2.2、java虚拟机栈
·简介:用于方法函数执行的一块内存区域
·作用:每个⽅法在执⾏的同时都会创建⼀个栈帧(Stack Framel)⽤于存储局部变量表、操作数栈、动态链接、⽅法出⼝等信息。每⼀个⽅法从调⽤直⾄执⾏完成的过程,就对应着⼀个栈帧在虚拟机栈中⼊栈到出栈的过程
·特点:
1)局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)以及对象引⽤(reference 类型)
2)如果线程请求的栈深度⼤于虚拟机所允许的深度,将抛出 StackOverflowError 异常
2.3、本地方法栈
·简介;是作用于本地方法执行区域的一块内存区域,本地方式即native 方法
·作用:与Java虚拟机栈相同,每个⽅法在执⾏的同时都会创建⼀个栈帧(Stack Framel)⽤于存储局部变量表、操作数栈、动态链接、⽅法出⼝等信息。每⼀个⽅法从调⽤直⾄执⾏完成的过程,就对应着⼀个栈帧在虚拟机栈中⼊栈到出栈的过程
·特点:与java虚拟机栈相同, 执行的方法不同,hotShot将java虚拟机栈合二为一
2.4、方法区
·简介:是各个线程共享的内存区域,它⽤于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
·作用:内存中存放类信息、静态变量等数据,属于线程共享的⼀块区域
·特点:并⾮数据进⼊了⽅法区就如永久代的名字⼀样“永久”存在了。这区域的内存回收⽬标主要是针对常量池的回收和对类型的卸载;⽅法区也会抛出OutofMemoryError,当它⽆法满⾜内存分配需求时
2.5 、堆
·简介:是Java内存区域中⼀块⽤来存放对象实例的区域,【⼏乎所有的对象实例都在这⾥分配内存】
·作用:此内存区域的唯⼀⽬的就是存放对象实例、是 Java 虚拟机所管理的内存中最⼤的⼀块 Java 堆是被所有线程共享的⼀块内存区域
·特点:Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”;Java堆可以分成新⽣代和⽼年代
3、对象创建
3.1、对象创建的流程步骤包括哪些
1)虚拟机遇到一条new指令时,首先检查这个对象的类能否在常量池中定位到一个类的引用
2)判断类是否被加载,解析和初始化
3)为这个新生代java对象在堆中分配内存空间(指针碰撞,空闲列表)
4)将分配到的内存空间都初始化为0
5)设置对象头数据(GC 分代年龄,对象哈希吗hashcode,元数据信息)
6)执行对象方法
3.2、对象结构
1)对象头信息
用于存储对象元数据信息
2)Mark word
部分数据的长度在32位和64位虚拟机中分别32bit和64bit(未开启压缩指针),对象存储自身运行时数据,如哈希码值等。一般被涉及为非固定的数据结构,以便存储更多是数据信息和复用自己的存储空间。
3)实例数据
·存储真实有效的数据,例如各种字段内容,字段的分配策略为longs,ints,shorts等,相同宽度的字段总是被分配到一起,便于之后取数据,父类定义的变量会出现在子类定义的变量前面
4)对其填充
起到占位符的作用
3.3、对象访问
·当我们在堆上创建一个对象实例后,就要通过【reference(引用)类型数据】来操作堆上的对象。
主流的访问方式有2种:
1)句柄访问
·即reference中存储的对象是句柄的地址,而句柄内包含了“对象的实例数据”与“对象数据类型”数据的具体信息地址,相当于二级指针
2)指针访问
·即reference中存储的就是“实例对象”和“对象类型的地址”,相当于一级指针(hotspot使用的是指针访问)
3)两种访问对比
·访问效率:指针方问效率高于句柄访问,指针访问只进行了一次指针定位,节省了时间
·垃圾回收: 句柄访问当垃圾回收移动对象的时候,reference内存储的是稳定的地址,仅需要更新对象的句柄地址;指针访问时需要修改reference中的存储地址
4、对象存活算法
4.1、引用计数法
·顾名思义引用计数器记录次数,根据次数进行回收,例如当进行对象a引用的时候,引用计数器会针对a对象引用的次数+1,当引用失效时引用次数-1; 如果引用的次数为0时进行垃圾回收,如果不为0位继续使用内存。
优点:维护每个对象的引用计数,简单,高效
缺点:无法检测出循环引用,例如父对象有一个子对象的引用,子对象反过来引用福对象,这样的情况下引用计数永远不可能为0
4.2、可达性分析(根搜索法)
·程序将所有的引用关系看做一张图(对象引用链),从一个根节点(GC ROOT)开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,存在未被引用到的节点就是无用节点(被回收)
优点:可以解决循环引用问题
5、垃圾回收算法
5.1、垃圾回收的意义
开发者开发时不需要内存管理的问题,由于有垃圾回收机制,Java内的对象不在有作用域的概念,只有对象的引用才做有作用域的概念。垃圾回收可以有效防止内存泄露,有效的用空闲的内存。
5.2、标记复制算法(java9以后默认回收算法)
将内存按量划分为大小相同的两块,每次只使用一块,当这一块的内存使用完后,就将内存中存活的对象复制到另外一个内存上面,然后再把上一个内存空间一次清理掉。
优点:内存分配时也就不用考虑内存碎片化问题,只需要一定堆指针,按照顺序分配内存即可,实现简单,高效。
缺点:内存利用率低
5.3、标记清除法
最基础的收集算法,分为标记和清除两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
优点:解决了 引用计数算法 不能清除循环引用的问题
缺点:效率低,标记清除后会产生大量不连续的内存碎片,空间碎片太多会导致后续在程序运行钟需要分配较大对象时,无法找到足够的连续内存而不得不提前触发垃圾收集动作。
5.4、标记整理法与分代收集
·标记整理
标记过程与标记清楚算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界外的内存
·分代收集
把堆分为新生代,老年代,这样就可以根据不同的年代采用最适当的收集算法。新生代每次垃圾收集时都会发现有大批量对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集,而老年代对象存活率较高,没有额外空间对它进行分配担保,就必须使用“标记-清理”,“标记-整理”算法来进行回收
优点:分别有整理,和收集两个过程,方法灵活,可以针对不同年代进行设定,可以应对内存中所有对象都100%存活的端情况。
缺点:算法复杂,在回收对象时,会伴随大量的对象移动,从而会导致对象回收阶段会占用相对多的时间。
6、JVM垃圾收集器
6.1、Serial 收集器
·单线程的垃圾收集器
·特点:
·当进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束,在用户不可见的情况下把用户正常工作的线程停掉
·多用于桌面应用,client端垃圾回收器,桌面应用内存小,垃圾回收的时间短
6.2 Serial old收集器
它是⼀个单线程收集器,Serial垃圾收集器的老年版本、使⽤"标记–整理"算法
6.3、ParNew 收集器
·是Serial收集器的多线程版本
·特点:
1)可以与Cms收集器配合工作
2)使用-XX:ParallelGCThreads 参数来限定垃圾收集的线程数
3)多线程操作存在上下文切换问题,所以建议将-XX:ParkallelGCThreads 设置成和cpu核心数相同,如果设置太多会产生上下文切换消耗
6.4、Parallel Scavenge 收集器
·是一个新生代收集器,它也是使用复制算法的收集器,由于与吞吐量关系密切,也经常被叫为“吞吐量”优先收集器,可以牺牲等待时间换取系统的吞吐量
·特点:
1)目标则是达到可控制的吞吐,吞吐量就是 CPU ⽤于运⾏⽤户代码的时间与 CPU 总
消耗时间的⽐值,即吞吐量=运⾏⽤户代码时间/(运⾏⽤户代码时间+垃圾收集时间),虚拟
机总共运⾏了 100 分钟,其中垃圾收集花掉 1 分钟,那吞吐量就是 99% 停顿时间越短就越
适合需要与⽤户交互的程序,良好的响应速度能提升⽤户体验,⽽⾼吞吐量则可以⾼效率地
利⽤ CPU 时间,尽快完成程序的运算任务,主要适合在后台运算⽽不需要太多交互的任务。
2)虚拟机会根据当前系统的运⾏情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最⼤的吞吐量,这种调节⽅式称为 GC⾃适应调节策略
3)-XX:MaxGCPauseMillis参数GC停顿时间(参数配置太小会发生频繁gc)
4)-XX:GCTimeRatio参数,
6.5、Parallel old 收集器
Parallel old收集器Parallel Scavenge收集器的⽼年代版本,使⽤多线程+标记整理算法
6.6、CMS 收集器
·cms收集器是一种以获取最短停顿时间为目标的收集器,适用于B/S架构,基于“标记-清除”算法实现
缺点:
1)对cpu资源非常敏感
2)无法处理浮动垃圾,程序在进行并发清除阶段用户线程所产生的新垃圾
3)标记-清除暂时空间碎片
流程:
1)初始标记(CMS initial mark) -----标记⼀下 GC Roots 能直接关联到的对象,速度很快
2)并发标记(CMS concurrent mark --------并发标记阶段就是进⾏ GC RootsTracing 的过程
3)重新标记(CMS remark) -----------为了修正并发标记期间因⽤户程序导致标记产⽣变动的标
记记录
4)并发清除(CMS concurrent sweep)
6.7、G1 收集器
·是面向服务端的垃圾收集器
·特点:
G1 中每个 Region 都有⼀个与之对应的 Remembered Set,当进⾏内存回收时,在 GC 根节点的枚举范围中加⼊ Remembered Set 即可保证不对全堆扫描也不会有遗漏 检查Reference引⽤的对象是否处于不同的Region
·G1 收集器的运作⼤致可划分为以下⼏个步骤:
1)初始标记(Initial Marking) --标记⼀下 GC Roots 能直接关联到的对象
2)并发标记(Concurrent Marking)—从GC Root 开始对堆中对象进⾏可达性分析,找出存活的对象,这阶段耗时较⻓,但可与⽤户程序并发执⾏
3)最终标记(Final Marking) —为了修正在并发标记期间因⽤户程序继续运作⽽导致标记产⽣变动的那⼀部分标记记录。虚拟机将这段时间对象变化记录在线程 Remembered Set Logs⾥⾯
4)最终标记阶段需要把 Remembered Set Logs的数据合并到 Remembered Set 中
5)筛选回收(Live Data Counting and Evacuation)
·优势:
1)空间整合:基于“标记⼀整理”算法实现为主和Region之间采⽤复制算法实现的垃圾收集
2)可预测的停顿:这是 G1 相对于 CMS 的另⼀⼤优势,降低停顿时间是 G1 和 CMS 共同的关
注点,但 G1 除了追求低停顿外,还能建⽴可预测的停顿时间模型
3)在 G1 之前的其他收集器进⾏收集的范围都是整个新⽣代或者⽼年代,⽽ G1 不再是这样。使
⽤ G1 收集器时,Java 堆的内存布局就与其他收集器有很⼤差别,它将整个 Java 雄划分为多
个⼤⼩相等的独⽴区域(Region),虽然还保留有新⽣代和⽼年代的概念,但新⽣代和⽼年
代不再是物理隔髙的了,它们都是⼀部分 Region(不需要连续)的集合。
4)G1 收集器之所以能建⽴可预测的停顿时间模型,是因为它可以有计划地避免在整个 Java 堆中进⾏全区域的垃圾收集。G1 跟踪各个 Regions ⾥⾯的垃圾堆积的价值⼤⼩(回收所获得的空间⼤⼩以及回收所需时间的经验值),在后台维护⼀个优先列表,每次根据允许的收集时间,优先回收价值最⼤的 Region(这也就是 Garbage- Firsti 名称的来由)。这种使⽤Region 划分内存空间以及有优先级的区域回收⽅式,保证了 G1 收集器在有限的时间内可以获取尽可能⾼
6.8、新生代垃圾回收器和老生代垃圾回收器都有哪些
·新生代回收器:Serial、ParNew、Parallel Scavenge
·老年代回收器:Serial Old、Parallel Old、CMS
·分代的垃圾收集器:G1
7、堆内存是怎么分配
7.1、基本分配规则
·对象主要分配在新生代Eden区上
·如果启动本地线程分配缓冲,将按照线程优先在TLAB上分配
·少数情况下也可能会直接分配在老年代中
7.2、大对象分配
·大对象:是指需要大量连续内存空间的java对象,例如很长的字符串以及数组,虚拟机提供了一个-XX:PretenureSizeThreshold 参数,大于设定参数值的对象直接在老年代分配。这样做的目的是避免在Eden区以及两个Survivor区之间发生大量内存复制。
8、逃逸分析和栈上分配
8.1、逃逸分析
逃逸分析基本行为就是分析对象动态作用域,当一个对象方法被定义后,它可能被外部方法所引用,称为方法逃逸。
8.2、栈上分配
栈上分配就是把方法中的变量和对象分配到栈上,方法执行完成后会自动销毁,而不需要垃圾回收的介入,从而提高系统性能
…未完待续、后续持续更新并且附图片,感谢查阅!!!!