JAVA虚拟机(JVM)
2022-03-11 00:29:56 1 举报
AI智能生成
java虚拟机专题
作者其他创作
大纲/内容
JAVA内存区域
组成
类加载子系统
类加载器
运行时数据区
方法区
堆
虚拟机栈
本地方法栈
程序计数器
执行引擎
即时编译器
垃圾回收器
本地库接口
本地方法库
组成概要
两个子系统:类加载子系统、执行引擎
两个组件:运行时数据区、本地方法库
作用
类加载器:根据给定的类全限定名加载class文件到运行时数据区中的方法区
执行引擎:执行class文件中的代码指令
本地接口:调用native的接口
运行时数据区:JVM内存
java程序运行机制
直观过程:java代码文件通过源码编译器生成字节码class文件,类加载器将字节码加载到内存中,存放在运行时数据区的方法区内,同时执行引擎将字节码翻译成系统底层指令,交给CPU执行。这个过程还需要本地库接口实现
java文件——源码编译器——.class文件——类加载器——运行时数据区——执行引擎——本地库接口
分支主题
运行时数据区
线程共享数据区
方法区:存储被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据
java堆:虚拟机中内存最大的一块,被所有线程共享,几乎所有的对象实例都在此分配内存
线程隔离数据区
虚拟机栈:存储局部变量表、操作数栈、动态链接、方法出口灯信息
本地方法栈:与虚拟机栈作用一样,区别在于本地方法栈调用native方法服务,虚拟机栈调用java服务的
程序计数器:当前线程所执行字节码的信号指示器,通过改变这个数值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能,都需要依赖这个计数器完成
深复制与浅复制
深复制:通过新开辟内存空间来存储复制出来的对象,指针也是新的
浅复制:仅仅复制了对象的内存地址,实际还是指向同一个对象。
java大部分都是通过浅复制复制对象的,如果需要深复制,那么需要覆写clone方法,或采用序列化的机制实现
堆与栈的
1、堆是物理地址不连续的内存区域,性能较慢,在GC的时候要考虑回收算法、分代等;而栈的数据结构决定了物理地址连续、遵循先进先出原则
2、堆大小不确定,程序运行时才能决定;栈大小确定,编译期就要确认
3、栈是内部线程可见的,属于线程私有,生命周期与线程相同;堆对整个应用程序都是可见的、共享的
4、栈存放局部变量、操作数栈、返回结果,更加关注方法的执行;堆保存对象的实例和数组,更加关注数据的存储
内存泄漏
指不再使用的对象或变量一直占据在内存中,常见的是生命周期长的对象对生命周期短的对象进行引用
HotSpot虚拟机
对象创建的方式:1、new关键字;2、class类的newInstance方法;3、constructor类 newInstance方法;4、反序列化;5、clone方法
对象创建的流程
分支主题
虚拟机遇到new指令时,首先检查常量池是否已经加载类,没有的话先执行类加载。加载完毕后,考虑分配内存。若java内存是规整的,通过指针碰撞的形式分配;否则通过空闲列表分配。同时还需考虑线程并发,可采用CAS或本地线程缓冲两种方式,然后进行存储空间初始化
为对象分配内存
类加载完成后,java虚拟机会在堆中划分一块内存分配给对象。可采用指针碰撞或空闲列表来进行分配。具体采用何种方式,是看内存是否规整,而内存是否规整又跟垃圾回收器是否带压缩整理算法功能来决定
指针碰撞算法:如果内存是规整的,所有使用过的内存和空闲内存区域有区分,分配时只要将上次的内存指针地址移动对象大小的一段距离即可
空闲列表方法:需要虚拟机维护内存空闲的表,记录着空闲的地址和大小等信息。对象分配是先从空闲列表查询到足够大的内存给对象进行分配
线程安全问题
对象的创建在虚拟机中是非常频繁的行为,哪怕简单修改一个指针的位置,在并发情况下也会存在线程安全问题。即分配的动作和指针的修改不是原子性的
CAS+自旋方案:同步处理
本地线程分配缓冲:分配动作以线程维度进行,预先将线程在堆中分配一块内存,每个线程在各自的缓存带上分配。分配完才使用同步锁。这种方法称为TLAB。可通过-XX:+/-UseTLAB参数来设定是否使用TLAB
对象访问定位
程序访问JVM栈上引用的jav堆的对象。基于JVM实现。主要有句柄和直接指针两种方式
句柄:指向指针的指针,不直接指向对象,而是指向对象的指针,查找对象是先找到对象指针,再通过对象指针找到对象的真实地址,需要两次定位
直接指针:引用的就是对象的直接地址,HotSpot采用了这种方式
垃圾收集器(GC)
概念相关
java提供的自动内存管理策略,可监控对象是否超过作用域(没有任何对象引用)达到自动回收内存,垃圾回收器通常作为一个单独的低优先级的线程运行,在虚拟机空闲或空间不足时对内存堆中的对象进行清理和回收
当代码创建一个对象时,GC便开始监控这个对象的地址、大小以及使用情况。一般采用有向图的方式记录和管理堆中的所有对象,确定对象的可达性,不可达时回收这些内存空间
垃圾回收期一般只能由JVM自动执行,代码可调用system.gc()方法通知GC运行,但不保证一定会执行
java的引用类型:1、强引用(GC时不会被回收);2、软引用(有用但不是必须的对象,内存溢出之前被回收);3、弱引用(有用但不是必须的对象,下一次GC时被回收);4、虚引用(无法通过虚引用获得的对象,一般用于GC时返回一个通知)
永久代也会发生垃圾回收,一般发生在永久代满了,发生完全垃圾回收(FULL-GC),一般要通过永久代空间大小参数的合理设置,避免FULL-GC
何时回收对象
引用计数法:为每个对象创建一个引用计数,有其它对象引用时+1,引用被释放时-1,计数为0时标记回收。缺点是不能解决循环引用的问题
可达性分析法:从GC-ROOT开始向下搜索,搜索走过的链条叫做引用链。当对象不在引用链时,回收对象
回收算法
标记-清除法:标记无使用的对象,直接进行回收。分为标记、清除两个阶段,是最基础的算法,其它算法再此基础上改进。实现简单、无需对象移动,过程效率低,会产生大量内存碎片
复制算法:将内存划分为两个大小相等的区域,当一块用完后复制存货对象到另一块区域,再把原来的区域清除掉。缺点为内存效率不高,复制频率高,优点是不会产生内存碎片
标记-整理算法:标记不存活对象并使存活的对象向一端移动,直接清除端外的内存。优点:解决了内存碎片问题;缺点:需要复制对象并移动,一定程度降低了效率
分代算法:根据对象的存活周期划分不同的内存区域,一般是新生代和老年代,新生代使用复制算法,老年代使用标记整理算法。当前商业虚拟机都采用了这种算法。
垃圾回收器
垃圾回收算法是内存回收的方法论,垃圾回收器就是内存回收的具体实现。
分类
serial收集器:采用复制算法,新生代的单线程收集器,标记和清理都是单线程,简单高效
parNew收集器:采用复制算法,新生代的多线程收集器,在多核CPU的环境下比serial更加高效
parallelScavenge收集器:复制算法,并行收集器,高吞吐量
serial Old收集器:标记整理算法,老年代单线程收集器,是serial收集器的老年代版本
parallel收集器:标记整理算法,老年代并行收集器,吞吐量优先,是parallelScavenge的老年代版本
CMS(Concurrent Mark Sweep)收集器:标记清除算法,老年代并行收集器,高效低停顿,牺牲了一定的吞吐量,对服务器响应速度高的服务器适合使用这种算法。可使用参数-XX:+UseConcurrentMarkSweepGC指定。将产生较多内存碎片,当剩余内存不足时,会使用serial-old进行清除
G1(Garbage First)收集器:标记整理算法,java堆并行收集器,不会产生内存碎片,并可作用于整个新生代、老年代。
分支主题
工作过程
管理的内存区域分为新生代、老年代,默认占比为1/3,2/3
新生代使用复制算法,有三个分区:Eden、To-Survivor、From-Survivor,默认占比为8:1:1
新生代执行流程:1、把Eden、From-Survivor存活对象放入To-Survivor区并清空原区域;2、From-Survivor、To-Suivivor分区交换,每次移动都存活的对象年龄+1,达到默认15时进入老年代
大对象直接进入老年代
老年代占用空间达到某个值时,将会触发FULL-GC,采用标记整理算法
以上过程的反复,构成了整个分代垃圾的回收执行流程
内存分配策略
一般来说对象通常在java堆上分配,主要在新生代的Eden区。如果启用了本地线程缓冲TLAB,则优先在线程本地缓存上分配。但总的来说规则也不是百分之百确定的,受参数配置、垃圾回收器的组合决定的
一般的规则
对象优在Eden区分配,此区没有足够内存时,虚拟机发起一次minor-GC,若还是不满足,直接在老年代上分配。minor-GC非常频繁,回收速度也很快
大对象直接进入老年代,大对象需要大量的连续内存空间,避免大对象在新生代之间分区频繁复制
长期存活的对象进入老年代
类加载机制
指虚拟机把描述类的数据从class文件加载到内存,并进行数据校验、解析、初始化,最终能被虚拟机直接使用的java类型的过程。类加载器本身也是一个类,一般采用动态加载的方式,不会一次性将所有的类都装载,只有用到的时候才会加载
加载方式
显式加载:程序运行过程中遇见new关键字,隐式调用加载器加载对应的类到JVM中
隐式加载:通过class.forname等反射方法,显式加载需要的类
类加载器
实现通过类的限定名加载该类的二进制字节流的代码类叫做类加载器
分类
启动类加载器(BootStrap ClassLoader):加载核心类库,无法被java程序直接引用
扩展类加载器(Extensions ClassLoader):加载java的扩展库,java虚拟机会维护一个扩展目录,此加载器在此目录下寻找并加载java类
系统类加载器(system ClassLoader):根据java路径来加载类,一般来说java应用类都由此加载完成的
用户自定义加载器:继承java.lang.ClassLoader类的方式实现
类装载的执行过程
加载:根据路径找到class文件并导入
验证:检查class文件的正确性
准备:类中的静态变量分配存储空间
解析:常量池中符号引用替换成直接引用的过程
初始化:对静态变量和静态代码块进行初始化的过程
双亲委派模型
指类加载器收到加载请求后,不进行加载,而是委托给父类去加载。每一层的类加载器都是如此,所有的类都会请求到启动类加载器中,只有父类无法完成加载请求,才让子加载器类进行加载
委派顺序:用户自定义加载器——>系统类加载器——>扩展类加载器——>启动类加载器
为什么要采用双亲委派模型:类加载器会判定,相同的类加载器且相同的类限定名认定为同一个类,如果没有双亲委派模型,相同的类可能会多次加载,造成相互赋值、混乱等一系列问题
JVM调优
调优工具
jconsole:监控JVM中内存、线程、类
jvisualvm:监控内存快照、线程快照、死锁、内存和GC变化
常用调优参数
-Xms2g:设置初始堆大小为2g
-Xmx2g:设置堆最大为2g
-XX:NewRatio=4:设置新生代比老年代的比例为1:4
-XX:SurvivorRatio=8:设置Eden区与Survicor区的比例为8:2
-XX:+UseParNewGC:使用parNew+Serial-New垃圾回收器组合
-XX:+UseParallelOldGC:使用parNew+parOld垃圾回收期组合
-XX:+UserConcurrentMarkSweepGC:使用CMS+serial-old垃圾回收器组合
-XX:+PrintGC:打印GC日志
-XX:+PrintGCDetail:打印详细的GC日志
0 条评论
下一页