JVM加载class
- 类的加载过程和生命周期
加载:通过全类名获取定义此类的二进制字节流(
Classloader.loadClass
)。连接
- 验证:校验载入的class是否符合jvm规范。
- 准备:为类的静态变量分配内存,并将其初始化为系统默认值(基本数据类型)(但是static final修饰的初始化时为赋予的实际值)。
- 解析:解析阶段是虚拟机将常量池内的
符号引用替换为直接引用的过程
。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。
初始化:对静态变量和静态代码块执行初始化工作 执行顺序。
- 当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
- 当jvm执行new指令时会初始化类。即当程序创建一个类的实例对象。
- 当jvm执行getstatic指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。
- 当jvm执行putstatic指令时会初始化类。即程序给类的静态变量赋值。
- 当jvm执行invokestatic指令时会初始化类。即程序调用类的静态方法。
- 使用 java.lang.reflect 包的方法对类进行反射调用时如
Class.forName,
newInstance()等等。 ,如果类没初始化,需要触发其初始化。 - 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
- 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
- MethodHandle和VarHandle可以看作是轻量级的反射调用机制,而要想使用这2个调用, 就必须先使用findStaticVarHandle来初始化要调用的类。
初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
MethodHandle和VarHandle可以看作是轻量级的反射调用机制,而要想使用这2个调用, 就必须先使用findStaticVarHandle来初始化要调用的类。
使用
卸载:卸载类即该类的Class对象被GC(jdk自带的BootstrapClassLoader,PlatformClassLoader,AppClassLoader负责加载jdk提供的类,所以它们(类加载器的实例)肯定不会被回收。而我们自定义的类加载器的实例是可以被回收的,所以使用我们自定义加载器加载的类是可以被卸载掉的。)
类加载器
反射:在运行过程中能知道这个类的方法和属性,任意一个对象就能进行调用这种动态获取信息及动态调用方法就叫做反射。
ClassLoader:加载所有class文件,将二进制文件装载,然后交给jvm进行连接初始化等操作。
类的加载方式:
- 隐式加载new关键字
- 显式加载ClassLoader.loadClass:得到的class还没有链接(继承ClassLoader,重写findClass方式)
- 显式加载Class.forName:得到class已经初始化完成了(native方法)
//反射相关代码
Class rc = Class.forName("com.interview.javabasic.reflect.Robot");
Robot r = (Robot) rc.newInstance();
System.out.println("Class name is " + rc.getName());
Method getHello = rc.getDeclaredMethod("throwHello", String.class);
getHello.setAccessible(true);
Object str = getHello.invoke(r, "Bob");
System.out.println("getHello result is " + str);
Method sayHi = rc.getMethod("sayHi", String.class);
sayHi.invoke(r, "Welcome");
Field name = rc.getDeclaredField("name");
name.setAccessible(true);
name.set(r, "Alice");
sayHi.invoke(r, "Welcome");
JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader
:
- BootstrapClassLoader(启动类加载器) :最顶层的加载类,由C++实现,负责加载
%JAVA_HOME%/lib
目录下的jar包和类或者或被-Xbootclasspath
参数指定的路径中的所有类。 - ExtClassLoader(扩展类加载器) :主要负责加载目录
%JRE_HOME%/lib/ext
目录下的jar包和类,或被java.ext.dirs
系统变量所指定的路径下的jar包。 - AppClassLoader(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。
- 自定义ClassLoader,加载外部class文件(继承ClassLoader,重写findClass方式)
双亲委派
每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用 双亲委派模型 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 loadClass()
处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader
中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 BootstrapClassLoader
作为父类加载器。
运行时数据区域_class加载的使用阶段
线程私有的:
程序计数器
字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
虚拟机栈
描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的
Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:
局部变量表、操作数栈、动态链接、方法出口信息
。
本地方法栈
- 与虚拟机栈作用类似,区别是虚拟机栈为执行字节码服务,而本地方法栈为虚拟机使用到的native方法服务(底层c语言调用),也会有栈帧。
线程共享的:
- 堆:几乎所有的对象内存空间都在堆上分配,
存放new对象实例,定义数组。堆是垃圾收集器管理的主要区域,因此也被称作GC 堆
。- 新生代(Young Generation)
- 老年代(Old Generation)
- 永久代(Permanent Generation)
- 1.8过后永久代被元空间(使用jvm外部内存)替代了。
- 方法区是Java虚拟机规范中的定义是一种规范,而永久代是一种实现。(永久代来实现方法区)且jdk8元空间替代永久代,方法区仍然存在。
- 方法区:存储已被虚拟机加载的
类信息、常量、静态变量
即时编译器编译后的代码等数据。
垃圾回收机制
不可达对象:对象没有被引用或者存活,线程不定时去回收。
- Object o=new Object();//可达,虽然没使用掉,
- object=null //不可达
- System.gc();//进行标记,提示可以进行垃圾回收,不是立即进行回收
- 重写finalize方法:Object方法,
新生代:刚出生不久对象,存放在新生代里面,存放不是经常使用的对象。
- EDEN区:新new的User对象,先放eden区
- S0(from)、S1(to)区:
老年代:存放比较活跃的对象,经常被引用。
怎么判断为垃圾:
- 没有被其他对象引用
垃圾回收算法:
引用计数算法
特点:
通过判断引用数量来决定对象是否可以被回收
每个对象实例都有一个引用计数器,被引用+1,完成引用-1
任何引用计数为0的对象实例可以被当做垃圾收集
优点:执行效率高,程序执行受影响小
缺点:无法检测出循环引用情况,导致内存泄露
- new两个相同对象实例,互相赋值给对方
可达性算法
- 特点:
- 通过判断对象引用链是否可达来决定对象是否可以被回收,通过gc root向下搜索,搜索走过的路径称为引用链。没有任何引用链可达,就叫 不可达
- gc root对象
- 虚拟机栈中引用的对象(栈幁中本地变量表)
- 方法区中常量引用的对象
- 方法区中类静态属性引用的对象
- 本地方法栈中JNI(Native方法)的引用对象
- 活跃线程的引用对象(万物皆对象)
- gc root对象
- 通过判断对象引用链是否可达来决定对象是否可以被回收,通过gc root向下搜索,搜索走过的路径称为引用链。没有任何引用链可达,就叫 不可达
- 特点:
垃圾回收算法
标记-清除算法
- 特点
- 标记:从根集合进行扫描,对存活对象进行标记
- 清除:对堆内存从头到尾进行线性遍历,回收不可达对象内存
- 缺点:内存碎片化
- 特点
复制算法
- 特点:
- 分为对象面和空闲面
- 对象在对象面上创建
- 存活的对象被从对象面复制到空闲面
- 将对象面所有对象内存清除
- 优缺点:
- 解决碎片化问题
- 顺序分配内存,简单高效
- 适用对象存活率低的场景eg:年轻代
- 特点:
标记-整理算法
- 特点:
- 标记:从根集合进行扫描,对存活对象进行标记
- 清除:移动所有存活对象,且按照内存地址次序依次排列,然后将末端内存地址以后内存全部回收。
- 优缺点:
- 避免内存的不连续
- 不用设置两块内存互换
- 适用于存活率高的场景eg:老年代
- 特点:
分代收集
- 特点:
- 垃圾回收算法组合
- 按照对象生命周期的不同划分区域以采用不同垃圾回收算法
- 优缺点:
- 提升jvm垃圾回收效率
- GC分类:
- Minor GC(年轻代):占比1/3
- 特点:年轻代垃圾收集动作,采用复制算法,存活率不高,朝生夕灭。为了尽快收集那些生命周期短的对象。
- 分区:
- Eden区:
- 对象刚创建出来,首先分配中eden区,如果不够用了,可能就会放在Survivor甚至是老年代中。空间大小占比80%
- 两个Survivor:
- 会分为from区和to区,这两个不固定,会随着垃圾回收的进行相互转换。空间大小各占比10%
- Eden区:
- Full GC(老年代和新生代):占比2/3
- 特点:
- 对老年代的回收伴随着年轻代的回收。
- -XX:MaxTenuringThreshold参数设置年龄调整年轻代进入老年代,默认15岁。
- 如何晋升到老年代:
- 进过一定Minor次数后依然存活。在新生代复制每次+1,超过15岁仍然存活
- Survivor区中存放不下的对象
- 新生成的大对象(-XX:+PretenuerSizeThreshold)
- 触发full gc条件
- 老年代空间不足
- 永久代空间不足(jdk1.8后不存在了)
- CMS GC时出现promotion failed(Survivor放不下了,只能放入老年代,老年代也放不下),concurrent mode failure(放入老年代,老年代也放不下)
- Minor GC晋升到老年代的平均大小大于老年代剩余空间
- 调用System.gc(),但是回不回收没有绝对控制权
- 使用RMI来进行RPC或者管理JDK应用,每小时执行1次full gc
- 特点:
- 常用调优参数
- -XX:SurvivorRation:Eden和Survivor的比值,默认8:1
- -XX:NewRatio:老年代和年轻代内存比例大小
- -XX:MaxTenuringThreshold:对象从年轻代晋升到老年代经过GC次数的最大阈值
- Minor GC(年轻代):占比1/3
- 分代收集算法
- stop-the-world [GC用]
- JVM由于要执行GC而停止了应用程序的执行
- 任何一种gc算法中都会发生
- 多数GC优化通过减少stop-the-world发生的时间来提高程序性能,达到高吞吐,低停顿
- Safepoint [可达性分析用]
- 作用:在可达性算法的可达性分析中,分析对象的引用必须在一个快照点进行,在这个点所有的线程都被冻结。不能出现在分析过程中对象的引用关系还产生变化的情况。需要确保程序具有安全性,因此设置了安全点。
- 发生条件
- 分析过程中对象引用关系不会发生变化的点
- 产生Safepoint的地方:方法调用,循环跳转,异常跳转等
- 安全点数量得适中
- stop-the-world [GC用]
- 特点: