- JVM结构
- 类加载器:执行类加载,加载>>链接>>初始化
- 运行时数据区
- 方法区:存放类信息,静态变量,常量,运行时常量池(编期间生成的各种子字面量和符号引用),线程共享
- 堆:存放对象的实例,线程共享
- 本地方法栈:为本地方法(native)服务,线程私有
- 虚拟机栈:由一个一个栈帧组成,执行一个方法就放入一个栈帧,执行完毕就取出,线程私有,栈帧中包括:
- 局部变量表
- 操作数帧
- 动态链接
- 方法出口
- 程序计数器(PC寄存器):当前线程,程序执行的指示器,存放下一条指令的地址,由执行引擎读取。线程私有
- 执行引擎:解析字节码文件,加载机器码,线程共享
- 本地方法接口:调用本地方法库(不是用Java语言写的程序),线程共享
- JVM类加载过程
- 加载>>链接>>初始化
- 何时需要进行类加载:
- new 一个对象的时候
- 调用对象静态变量或者静态方法
- 反射调用类
- 初始话子类的时候,若父类没有被初始化,则也会执行父类的初始化
- 程序启动时,main方法所在类
- 加载过程:
- 通过类的全限定名(所在包名 + 类名)来获得此类的二进制字节码流
- 将这些静态数据转换为方法区中的运行时数据
- 在方法区中生这个类对应的java.lang.Class对象,作为调用此类各种信息的接口
- 链接:
- 验证>>准备>>解析
- 验证:验证加载的类信息是否符合JVM规范,确保没有安全问题
- 准备:为类的静态变量分配内存,并赋予默认值(0值),在方法区中执行
- 解析:将常量池的符号引用替换成直接引用
- 初始化:
- 对类的静态变量赋予初始值,执行静态代码块
- 类加载器的种类
- 引导类加载器
- 扩展类加载器
- 应用程序类加载器
- 双亲委派机制(为了安全):当我们需要加载一个类时,JVM会向上传递需要加载的信息,然后从上往下加载,他会首先使用引导类加载器进行加载,如果加载失败,就会使用扩展类加载器进行加载,如果失败,就会使用应用程序类加载器进行加载。例:当自定义java.lang.String包和类,加载的时候,会首先通引导类加载器进行加载,他会加载他所对应的原始的java.lang.String的类。
- 沙箱安全机制
- 在Java1.0时,本地代码默认受信任,能够访问本地资源;而远程代码默认不受信任,不能够访问本地资源
- 在java1.1时,安全机制太严格不适合扩展,所以对沙箱进行了改进,允许用户指定远程代码对本地资源的访问权限
- Java1.2时,再次升级,增加了代码签名,不管本地,还是远程代码,都由用户指定安全策略,由类加载器加载到虚拟机中不同的权限空间、
- java1.6以后,引入了域,代码会被加载到权限不同的域中。
- native关键字:说明修饰的方法是原生态方法,不是用Java写的,而是其他语言写的,例如C,C++。执行该方法的时候,会进入本地方法栈,调用本地方法接口(JNI)
- 垃圾回收算法
- 两种找到垃圾的算法
- 引用计数法,增加对对的一个引用,计数加1,消除引用,计数减1,计数为0就是可消除对象。这中方法计数麻烦,耗内存,解决不了循环引用问题
- 根可达算法,选择一些对象为根对象GC Roots,通过对象之间的相互引用(引用链),判断从这些根对象能引用到那些对象,引用不到的对象就是可消除对象,其中GC Roots:
- 虚拟机栈中引用的对象
- 方法区中静态变量或常量引用的对象
- 本地方法栈(JNI)引用的对象
- 虚拟机内部所引用的对象
-
同步锁( synchronized 关键字)持有的对象
-
回调,代码缓存等
- 常用的垃圾回收算法
- 标记清除算法,遍历一遍,将可清除的对象进行标记,然后删除标记对象,缺点:会产生零碎的内存空间。
- 复制算法:将内存一分为二,一个活动区间,一个空间区间,所有新建的对象都存放在活动区间,当活动区间满的时候,执行复制算法,将活动区间存在引用的对象复制到空闲区间,清除活动区间的所有对象,并给复制对象的引用赋予新的值,此时活动区间变为空闲区间,空闲区间变为活动区间。
- 标记整理算法:在标记清除的基础上,把碎片化的内容进行整理压缩。
- 分代收集算法:根据对象存活的周期不同,将内存分为新生代和老年代,新生代中对象存活周期短,采用复制算法,老年代对象存活率高,采用标记清除或标记整理算法
- 垃圾回收算法是回收堆中的垃圾,栈中不存在垃圾,堆的划分:
- 新生代:
- 伊甸园区
- 幸存0区
- 幸存1区
- 伊甸园区:幸存0区:幸存1区=8:1:1
- 幸存0区不存放对象,当伊甸园区内存满了的时候,执行垃圾回收算法(轻GC)将伊甸园区和幸存1区中存活的对象复制到幸存0区,并阿静伊甸园区和幸存1区的内容清空,此时幸存1区变为幸存0区,幸存0区变为幸存1区
- 老年代:当一个对象多此轻GC以后仍然存活,这个对象就会被放到老年区,当老年代满了执行重GC。默认15词以后,对象就会被放入老年代
- 永久代(方法区):Java1.8以后变为元空间,最大的区别(元空间并不在JVM中,而是使用本地内存)。为什么将永久代变为元空间:永久代有默认大小,容易产生内存溢出的问题,而且有极少一些方法(Strign::intern(),返回字符串对象的规范化表示),会因为虚拟机不同而有不同的表现。
- 新生代:
- 轻GC在新生区中执行,不会清除老年代的对象,重GC是对老年代的执行,全GC是对整个堆的执行。执行GC算法的时候程序会暂停,因为不能产生新的对象。
- 两种找到垃圾的算法
- 垃圾收集器:
- 串行垃圾收集器:单线程,执行时必须暂停引用线程
- 并行垃圾收集器:多条垃圾回收线程同时执行,适合多CPU,应用程序仍然处于停止状态。
- GMS收集器(之前用的最多):缩短暂停时间,基于标记清除算法,分成四个步骤:
- 初始标记,任需暂停,标记GC Roots能直接关联的对象
- 并发标记,从初始标记的对象遍历整个对象图,并发执行。
- 重新标记,任需暂停,修正并发标记期间应程序运行,而发生标记变动的标记(不不可达变成可达的对象)
- 并发清除:清理掉标记阶段已经判断为死亡的对象
- 缺点:
- 并发阶段会占用一定的线程,程序速度下降,CPU核心增多能减少这种情况
- 无法处理“浮动垃圾”(可达变成不可达,并发标记,清除阶段产生的垃圾)
- 需要一定预留一部分空间供并发标记时的程序使用,预留空间太小可能发生“并发失败”
- 基于标记清除算法,大量空间碎片
- G1收集器:把连续的Java堆划分为多个大小相等的独立区域(Region),每个Region都可以扮演新生代的Eden空间、Survivor空间,或者老年代空间。根据回收所获得的空间大小以及回收所需时间的经验值,去判断该回收那个区域。也分为4个阶段:
- 初始标记:标记一下GC Roots能直接关联到的对象
- 并发标记:从初始标记的对象遍历整个对象图,并发执行。
- 最终标记:处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
- 筛选回收:更新Region的统计数据,根据回收价值和成本,用户所期望的停顿时间,来制定回收计划。
JVM学习
最新推荐文章于 2024-03-03 21:41:04 发布
![](https://img-home.csdnimg.cn/images/20240711042549.png)