1.字节码文件
代码经过javac编译生成字节码文件,然后字节码文件经过Java虚拟机的解释进行运行。
字节码文件的开头几个字节是:0xCAFEBABE(又称魔数)
- 字节码文件的组成:
工具arthas,动态监控修改运行中的系统
字节码信息反编译源码
2.类的生命周期
应用场景:运行时常量池,类加载器的作用,多态的原理,类加密解密
- 生命周期:
1.加载阶段
简述:全限定名获取类的二进制字节流(文件、网络、数据库等);字节流代表的静态存储结构转化为方法区运行时数据结构;内存中生成一个java.lang.Class对象作为该类各数据的访问入口(元数据);
(本地文件、动态代理生成、网络传输)
2.连接阶段
分为三个小步骤:
验证:确保字节流中的信息全部符合约束,保证不会危害虚拟机的安全(文件格式验证、元数据验证、字节码验证、符号引用验证)
准备:正式为类中定义的static变量分配内存并设置类变量初始值(零值)
解析:将常量池中的符号引用替换为直接引用的过程(类、接口、字段、方法)
初始化阶段:
(本质): 就是执行类构造器< clinit>()方法的过程。< clinit>()方法 = 所有类变量static赋值动作 + 静态语句块合并产生。父类 > 子类,静态变量 > 静态代码块 > 构造函数;
(触发原因): 对于“加载”阶段什么时候开始,各JVM可以自行把握,但是《有且仅有》以下六种情况必须立即对类进行初始化操作(加载、验证在此前需完成)
- new关键字实例化、读取类的静态变量或方法;
- reflect包进行反射调用某个类的时候;
- 子类进行初始化时发现父类还没初始化,则先进行父类初始化;
- 用户指定执行包含main()方法的主类时,先初始化主类;
- 解析出四种方法句柄对应的类没初始化时,先触发初始化;
- JDK8接口中包含默认方法default,其实现类发生初始化时,先初始化该接口。
上面几种行为是对类型的主动引用,必须初始化,发生以下几种情况称为被动引用,不会触发初始化:
- 通过子类引用父类的静态字段,不会导致子类初始化;
- 使用数组来引用类,不会触发该类的初始化;
- 编译阶段加入常量池的常量,被用到时不会触发该类初始化,因为本质上没有引用到该类。
3.类加载器
JDK8及之前版本中默认的类加载器:
启动类加载器BootstrapClassLoader(可以用参数扩展加载的核心类),扩展类加载器ExtentionClassLoader(加载通用类),应用程序类加载器ApplicationClassLoader(加载自定义类)
JDK9及之后
– 双亲委派机制
由于JVM中有多个类加载器,双亲委派机制的核心是解决一个类到底由谁加载的问题(优先级:Boot,Ext,Application)
向上查找:如果找到则直接返回已加载的对象
向下加载:检查该类是否在自己配置的加载路径中
–打破双亲委派机制
打破双亲委派机制:重写loadclass方法
自定义类加载器能加载java.lang.String吗?不能,java开头的包在底层被保护起来了,不能自己加载
自定义类加载器的默认父类:
参考Tomcat的应用隔离机制:
类加载器小结:
4.运行时数据区(内存管理)
程序计数器:记录当前执行的字节码指令地址、保存线程切换上下文
java虚拟机栈:
栈默认大小取决于OS和计算机体系结构
堆:OOM
默认堆大小
建议
方法区:
JDK7及之前:方法区在堆上分配永久代,不合理
JDK8及之后:方法区放在元空间中,不受JVM限制,可以使用OS内存上限
字符串常量池:intern()手动将字符串加入常量池,8之后存放于堆上
静态变量存储位置:堆中Class对象,脱离永久代
垃圾回收
方法区回收:
堆回收:
判断堆上对象是否被引用:引用计数法、可达性分析法(java采用的)
1.引用计数法:为对象维护一个引用计数器(简单易用,但是频繁维护影响性能,且无法解决循环引用)
2.可达性分析法:将对象分为两类(根对象、普通对象)
垃圾回收算法:
几种垃圾回收算法:
标记清除算法:
复制算法:
标记整理算法(标记压缩算法):
优化标记清除算法的内存碎片问题,移动碎片到连续空间中
分代GC(主流):
堆被分为:新生代(Eden、Survivor)+老年代
youngGC用的复制算法,年龄到15晋升到老年代,老年代满了后:
垃圾回收器:
G1垃圾回收器:
G1垃圾回收方式:
1.youngGC
2.MixedGC
!!与之前的回收算法不同的是第3步,不是整个GC,而是选择部分区域GC,算是一种改进的复制算法。选择的依据是每个区的平均耗时,作为下次GC的参考,以此来满足用户配置的最大暂停时间
补充知识
========================= JVM补充知识点 ============================
Java语言反射泛型之类的,JVM调优,JVM应用例如Tomcat
1.发生OOM异常的原因和处理措施
- 内存溢出可能区域:除了PC程序计数器都有可能。
- 栈:固定大小报StackOverFlowError,允许动态扩展则报OOM;前者触发方式可以通过设置-Xss减小栈内存容量或者定义大量本地变量增大方法帧中本地变量表长度;后者触发方式可以死循环增加栈帧数量或者无限创建线程;
- 方法区:主要存放类的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等;8之前叫永久代,是在堆上划分的区域;8之后叫元空间,在直接内存上划分,但是字符串常量池依旧还在堆上;
- 直接内存:DirectByteBuffer是sun.misc.Unsafe类的一部分,它允许直接在堆外分配内存,从而绕过了JVM的堆内存限制。这通常用于高性能I/O操作,例如FileChannel的map方法或Selector的使用。如果Dump出的文件很小,可以考虑是否直接内存溢出;
- 启动时设置JVM参数:- XX:+HeapDumpOnOOM,转储出来的快照可以用分析工具分析:Eclipse Memory Analyzer,jhat等。经过分析工具分析导致OOM的对象信息,确认是内存泄漏还是内存溢出。
- 内存泄漏:通过工具查看泄漏对象到GCROOT的引用链,查看是什么原因导致无法被回收,这样一般可以准确定位到问题代码处;
- 内存溢出:不是内存泄漏的话,说明对象都是需要存活的,这时只能调整JVM参数-Xmx和-Xms等,此外还要检查代码设计不合理的地方,比如某些对象生命周期过长、存储结构不合理等;
=========================== 分割线:面试篇 ==========================