目录
记录自己在学习jvm中总结的有用内容:
JVM在执行程序时将内存分为若干数据区域
运行时数据区域
-
方法区
线程共享区域
存放JVM加载的类常量等
当无法满足内存分配的时候也会抛出OutOfMemoryError
-
运行时常量池(java 并不是要求常量一定要编译才能产生,运行时的常量放入常量池中)
-
直接内存
-
只是在hotspot虚拟机中可以称为永久代,在新版本的jdk中已经放弃永久代用Native Memory来规划,将字符串常量池移出了永久代
-
虚拟机栈
作为每隔线程所私有的,一般存放的是局部变量例如基本数据类型,主要为执行的java方法服务,在这里会有两种异常stackOverflowError和OutOfMemoryError,第一个是出现在线程请求的深度大于JVM允许的深度,第二是在动态扩展时请求内存未分配到
-
本地方法栈
主要是为我们的Native原生方法服务的地方,同时也会同虚拟机栈一样抛出相同异常
-
堆
线程共享区域,简单来说就是放实例对象的地方
简单来说就是新建对象new出来存放地方,也是主要GC回收区域
如果新对象没有在堆中完成实例分配,堆无法扩展就会出现OutOfMemoryError
可以分为新生代和老年代
-
程序计数器
是用来作为记录每个线程字节码行号的指示器,jvm通过这个计数器来确定需要执行的下一个步骤
也是jvm中唯一一个未规定OutOfMemoryError的地方
-
运行时常量池
是方法区的一部分存放字面量和符号引用,JVM中常量不一定要编译才产生,在运行时也可以放入常量池,受到内存限制,无法申请到内存时OutOfMemoryError
-
直接内存
NIO类可以直接使用Native函数库分配堆外的内存
hotspot虚拟机
-
创建对象
首先会去检查参数是否在常量池中能找到,再检查对象代表引用的类是否已加载,解析初始化过,若没有则先执行类加载
对象再分配内存时:①指针碰撞(内存规整,用过的内存放在一边,另外一边是空闲的内存,内存分配仅分配指针)②空闲列表(内存不规整的时候,需要在内存中单独维护一个表)
使用哪种分配内存取决于GC垃圾回收是否带有压缩整理功能,解决并发安全问题(①虚拟机采用CAS上配置失败重试保证原子性②内存分配按照线程分配不同空间)
-
对象访问定位
需要通过栈上的reference来操作堆上的具体对象,通过方式有句柄方式和直接指针
第一种就需要内存中的句柄池,优点是对象移动后无需修改reference数据,只用修改指针,第二种reference中就放置的对象地址访问速度快
-
垃圾收集器
-
对象存活判断
有两种算法:引用计数法和可达性分析算法,第一种简单理解就是对象的引用有一个计数器,每次使用和失效就做对应的加减,存在问题就是出现对象之间相互引用的时候导致计数都不是0,优点是效率高
第二种存在一个GC ROOT的根节点作为初始对象,从此往下搜索通过引用链是否可达判断存活,在算法判断存活后会进入缓存阶段,一般判断死亡至少经过两次标记。
在jvm中新生代的回收效率远远大于永久代的回收效率,永久代一般回收废弃常量(只要没有引用)和类(①该类的在堆中所有实例不存在②类加载被回收③Class对象没有被引用,无法反射获取Class)
-
垃圾回收的算法
①标记清除算法(先标记后清除效率低,回收后存在大量的内存碎片)
②复制算法(每次将存活对象复制在内存空间,然后整理原空间)一般新生代采用第二种,但是在一些场景下需要进入老年代的对象过多Survivor空间不够的时候需要老年代分配担保
③标记整理算法(根据老年代的特点若复制算法,效率低,若想减少50%的内存就需要担保)
④分代收集算法(在堆中分成老年代和新生代,新生代更新换代快,大部分都直接被回收,适合复制算法,老年代存活率高一般使用标记整理或标记清除)
⑤hotspot算法中:可达性分析从GC ROOT根对象判断可达当对象过多的时候会出现,随之而来的GC停顿,使用OopMap的数据结构来记录哪些地方存在引用用于当系统停顿时。通过OopMap就可以快速的完成GC ROOT枚举,但是hotspot中没有为每条指令都生成的了OopMap,而是在GC停顿时在安全点生成。所以在进入GC时安全点完美解决了问题,但是在不执行的程序例如sleep或者锁住的状态,这个时候不可能呢长时间等待,则需要安全区(指在一段代码片段中,引用关系不变化,这个区域中GC是安全的)线程执行到安全区先标记一次当要离开安全区域的时候,先检查是否完成了对GC ROOT的枚举,没完成线程等待
-
垃圾收集器
-
内存分配和回收策略