决战圣地玛丽乔亚Day38---JVM相关
Posted Dva清流
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了决战圣地玛丽乔亚Day38---JVM相关相关的知识,希望对你有一定的参考价值。
JVM的内存结构:
1.程序计数器:线程私有,保存执行指令地址。
2.java虚拟机栈(线程创建,并存方法调用的相关参数):
每个线程在创建时候都会被分配一个虚拟机栈。当线程调用方法时,会创建一个栈帧,入栈,方法执行完毕栈帧出栈。
栈帧会在调用方法的时候把存局部变量表,操作数栈,动态连接,方法出口等信息存进去,然后压入java虚拟机栈。
3.本地方法栈
其他变成语言的接口。
4.java堆
所有线程共享的一块区域,优化GC主要优化的部分。
用来存一些实例对象(数组/普通对象)。
我们new Entity()的时候会分配一块空间存这个Entity。
5.方法区
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
1)类信息:java程序运行,java虚拟机会把类的信息存在方法区。包括类的名称、父类的名称、类的修饰符、类的字段、方法等信息
2) 运行时常量池:方法区的一部分,存一些字面量和符号引用。其中包括字符串常量、类和接口的全限定名、字段和方法的名称和描述符等信息。
3)静态变量:由于静态变量是类级别的变量,因为这个是类的属性不属于对象的属性,所以存方法区。
4)即时编译后的代码
7.直接内存
与Java堆不同,直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。直接内存的分配不受Java堆大小的限制,但是会受到本机总内存大小以及处理器寻址空间的限制
常量池的作用?为什么是-128~127?
1.常量池把所有的常量存储在一起,避免了重复分配的问题,如果用到就要重新分配,太浪费内存空间。
2.使用频繁,用常量池避免查找和创建的消耗。
3.动态解析:符号引用是通过类名、方法名、字段名等符号来引用类、方法、字段等元素的。在程序运行时,Java虚拟机需要将这些符号引用解析成实际的内存地址,才能进行方法调用、字段访问等操作
这个过程称为符号引用的动态解析。常量池存符号引用进行动态解析,符合java语言的动态语言特性。
-128~127:
这个范围的数字使用频率高,java虚拟机把这个范围作为常量池缓存而不是每次都重新创建。这个范围是整数常数,像Boolean、String、Double等没有常量池缓存限制。
永久代1,.8为什么会被移出到堆外?
之前永久代在java堆内的特殊区域,存元数据,静态变量,常量池等等。但是由于永久代大小有限,经常出现内存溢出。
所以1.8之后把元数据、静态变量、常量池等存储到本地内存(Native Memory),称之为元空间。
元空间相比之前的永久代的好处是:
元空间的大小是动态的,根据策略进行动态调整防止OOM。
避免了java堆的内存碎片化问题,提高内存使用率。
减少了FULL GC,提高程序响应速度和性能。
栈和堆:
栈主要存方法调用的东西。
堆主要存对象、数组等动态分配的东西,和类息息相关。对象创建时,java虚拟机在堆分配一个连续的空间并返回引用来存对象,对象不再被引用的时候,会通过垃圾回收算法对其进行回收操作。
栈溢出的情况:
1.递归调用过深。递归自身或其他方法,栈帧被大量创建打满java栈的大小。
2.方法调用层数过多
3.局部变量过多,方法的局部变量过多打满java虚拟机栈大小
如何进行垃圾回收:
1).标记阶段:Java虚拟机从根对象(如线程、静态变量等)出发,递归遍历所有可达对象,将它们标记为“存活”。
2).清除阶段:Java虚拟机清除所有未被标记的对象,释放它们所占用的内存空间。
3).整理阶段:Java虚拟机将所有存活对象向一端移动,从而使剩余的内存空间变成一块连续的空间,便于后续的对象分配。
首先,标记阶段有一个问题,如何判断对象是否可达?
引用计数器法:引用计数法是一种简单的判断对象是否可达的方法。它的原理是在每个对象中记录一个引用计数器,每当有一个对象引用它时,引用计数器加1,每当一个对象取消对它的引用时,
引用计数器减1。当某个对象的引用计数器为0时,就可以判断它已经不再被引用,可以被回收了。但是,引用计数法无法解决循环引用的问题,即两个或多个对象相互引用,导致它们的引用计数器永远不为0,无法被回收。
可达性分析:可达性分析法是一种更加可靠的判断对象是否可达的方法。它的原理是从一组根对象出发,遍历所有对象,将所有可达的对象标记为“存活”,所有不可达的对象标记为“死亡”,
从而回收它们所占用的内存空间。在可达性分析法中,通过一系列算法和数据结构,可以高效地判断对象是否可达,避免循环引用的问题。
1,从一组根对象(如线程、静态变量等)出发,将它们作为起始点,遍历所有与之相连的对象,将它们标记为“存活”。(根搜索)
2.将所有可达的对象标记为“存活”,不可达的对象标记为“死亡”。(对象标记)
3.清除所有被标记为“死亡”的对象,回收它们所占用的内存空间。(清除死亡对象)
4.将所有存活的对象向一端移动,从而使剩余的内存空间变成一块连续的空间,便于后续的对象分配(压缩存活对象)
具体的实现方法:
标记-清除算法:
最简单的方法。
1.标记所有可达对象存活,不可达对象死亡。
2.清除死亡对象回收空间
优缺点:内存碎片,回收效率低
标记-复制算法:
1.标记所有可达对象存活,不可达对象死亡。
2.在复制阶段,将所有存活的对象复制到另一个内存区域中,保证存活对象之间的空间是连续的。在清除阶段,将原来的内存空间清空,回收它们所占用的内存空间
加大内存负担,用更多空间保证内存碎片问题。
三色标记法:
白色:没有被标记过,一开始都是白色。(没有引用)
灰色:已经被标记过,但是该对象下的属性没有全被标记完(由引用,但是没有处理完引用关系)
黑色:已经标记过,并且该对象下的属性全部标记完毕。(有引用,不用清除)
1.从一组根对象出发,将它们标记为灰色,加入待处理队列
2.从待处理队列中取出一个灰色对象,将它标记为黑色,遍历所有与之相连的对象,将它们标记为灰色,加入待处理队列。
重复步骤2,直到待处理队列为空
3.清除所有被标记为白色的对象,回收它们所占用的内存空间。
结束后,白色里仍然没有标记的,是不可达,进行回收。
并发标记带来的问题:
1.并发修改导致标记结果不准确。 加锁/CAS等性能损失换原子性
2.漏标情况,应用程序动态产生新对象,漏标,内存泄漏。可以使用增量标记优化。
3.并发标记过程中,应用程序可能会持续地分配内存空间,导致垃圾回收器无法回收内存空间,从而导致内存空间的不足。为了解决这个问题,需要使用内存分配器的优化策略,如分代回收、TLAB等,以提高内存分配的效率和减少内存空间的浪费
如果并发标记,标记期间对象间的引用可能发生变化,就会出现多标漏标情况。
多标:
黑色,灰色变成白色,但是之前标记成了黑色,变成了不会被发现的浮动垃圾,交给下次gc处理即可,影响不大。
漏标:
并发标记的过程中,白色断开引用成为垃圾,黑色引用白色对象。但是黑色已经被完全扫描过不会重新扫描,导致对象需要被引用但是又面临被回收。CMS和G1都对此进行了优化
CMS:
对于CMS垃圾回收器而言,它采用增量标记的方式来解决漏标问题。增量标记是指在垃圾回收过程中,将标记过程分解成多个阶段,每个阶段只标记一部分对象,然后让应用程序继续执行一段时间,再继续标记下一部分对象。这样可以避免垃圾回收线程一次性占用太多CPU资源,同时也减少了漏标的可能性。
G1:
即使用 remembered set 来解决漏标问题。remembered set 是一种数据结构,用于记录从年轻代到老年代的引用关系,当年轻代中的对象被回收时,G1垃圾回收器会扫描 remembered set 中的引用关系,从而标记所有与之相关的对象。这样可以避免漏标问题,并且也减少了垃圾回收的开销。
以上是关于决战圣地玛丽乔亚Day38---JVM相关的主要内容,如果未能解决你的问题,请参考以下文章
决战圣地玛丽乔亚Day53-----分布式原理与RPC之服务治理与基本原理