Java Virtual Machine
Java
Java是一种跨平台的面向对象编程语言,具有封装、继承、多态的特性。
同时具有有一次编写,多处运行的优势。由JVM支持在多种操作系统上运行。
类加载器
- Bootstrap:根加载器由c++编写,用于引导Java程序,是虚拟机的一部分
- Extendsion:扩展加载器由Java编写,加载jre/lib/ext下的Java平台的扩展包
- Application:用户加载器由Java编写,加载用户类路径下的类库,加载器位于rt.jar包中com.java.lang.ClassLoader
双亲委派机制:防止类冲突,一个类加载器收到类加载请求时,会将请求委托给父类加载器去完成,层层递进,当父类加载器无法完成加载请求时,会委托给子类加载器。如果出现相同包路径相同类名,则以更高级别类加载器的返回结果为准。
类加载过程:
- 加载:将class文件字节码内容加载到内存中,并转换为方法区的运行时数据结构,然后生成这个类的java.lang.Class对象
- 链接:将Java类的二进制代码合并到JVM的运行状态中的过程。包括验证(确认类信息符合JVM规范)、准备(为static变量分配内存并设默认初始值到方法区中)、解析(虚拟机常量池内的符号引用常量名替换为直接引用地址的过程)
- 初始化:
- 执行类构造器<clinit>()方法(类信息构造,不是类对象)方法的过程,由编译期收集类的所有变量赋值动作和静态代码块中的语句合并产生。
- 当初始化一个类时,如果父类未初始化,则先触发父类初始化。
- 虚拟机会保证一个类的方法在多线程环境中被正确加锁和同步。
Heap堆-年轻代&老年代
Java程序在运行过程中会不断产生新对象,这些对象的存活时间有的长有的短。所谓存活指对象被引用,无引用的对象将会被回收。
Heap堆被分为年轻代、老年代。其中年轻代又分为新生代、幸存者1、幸存者2。年轻代和老年代比例为1:8。
当Java程序创建一个对象时,这个对象将被存放在新生代。
这些对象遵循一个晋升规则:
Survivor目标存活率:-XX:TargetSurvivorRatio(默认为50)
Survivor区最大年龄:-XX:MaxTenuringThreshold(默认为15)
Young GC(YGC):回收年轻代
Full GC(FGC):回收年轻代、老年代
新生代:当被装满,会触发一次Young GC(YGC),将幸存对象移入幸存者1中。
幸存者:当超过目标存活率,则触发一次YGC,将幸存对象移入幸存者2中,且超过Survivor区最大年龄的对象移入老年代,并清空幸存者1。下次则从幸存者2移到幸存者1,如此反复,减少内存碎片提高GC性能。
老年代:每次YGC会检查老年代最大可使用的连续空间是否可以容纳整个年轻代的所有对象,如果不能容纳,则执行FGC。
GC-垃圾回收
C语言的内存需要程序员主动释放,而Java内存不需要主动释放,因为JVM提供了垃圾回收器,它遵循一定的规则,采用回收算法进行内存回收。
垃圾判断算法
1. 引用计数算法
给每个对象添加一个计数器,当有地方引用该对象时计数器加1,当引用失效时计数器减1。用对象计数器是否为0来判断对象是否可被回收。缺点:无法解决循环引用的问题,已经被弃用。
public class ReferenceCountingGC { public Object instance; public ReferenceCountingGC(String name) { } public static void testGC(){ ReferenceCountingGC a = new ReferenceCountingGC("objA"); ReferenceCountingGC b = new ReferenceCountingGC("objB"); a.instance = b; b.instance = a; a = null; b = null; } }
2. 可达性分析算法
垃圾回收算法
1. 标记清除算法
第一次遍历,从根节点开始标记所有被引用的对象,第二次遍历,将所有未标记的对象回收。此算法需要暂停整个应用,且会产生内存碎片。
2. 复制算法
把内存空间划分为两个相等的区域,遍历当前使用区域,把正在使用的对象复制到另一个区域。此算法不会产生内存碎片,但需要两倍内存空间。
3. 标记整理算法
标记所有被引用对象,回收未标记对象并把存活对象“压缩”到堆的其中一块,按顺序排放。此算法不产生内存碎片,且不需要两倍内存空间。
4. 分代收集算法
基于统计结果并综合上面三个算法的优点。分为年轻代、老年代、永久代,并根据情况回收不同分区。
JVM调优
内存诊断
通过命令行指令,观察内存状态
jstat -gc <pid> 1000
使用命令查看内存详细信息
jmap -heap <pid>
使用命令查看占用内存前十的对象
jmap -histo <pid>|head -n 10
使用命令分析Java程序线程状态
jstack <pid> > Main.txt
找到cpu占用高的线程PID
top -H -p <pid>
printf "%x\n" PID
进入Main.txt文件中搜索printf的结果与nid相同的信息,发现Main.java第5行正在执行。
检查代码发现Main.java的第5行是一个死循环,占用了大量CPU资源
使用命令对JVM内存保存快照
jmap -dump:live,format=b,file=/home/app.dump <pid>
使用jdk自带的分析工具jvisualvm.exe对快照进行分析
参考资料
https://blog.csdn.net/qq_38163244/article/details/109551205
https://blog.csdn.net/rongtaoup/article/details/89142396
https://baijiahao.baidu.com/s?id=1668705529778019153&wfr=spider&for=pc
https://www.jianshu.com/p/23f8249886c6
https://www.cnblogs.com/csniper/p/5592593.html
https://mikechen.cc/3321.html