强引用:如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Jvm宁愿抛出OutOfMemoryError错误,使程序异常终止
软引用:内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存 ===> 软引用可以和一个引用队列(ReferenceQueue)联合使用
弱引用:弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
虚引用: 虚引用并不会决定对象的生命周期, 任何时候都可以被回收
主要是针对常量池的回收和类型的卸载。
常量池中的字符串没用被引用, 即可被回收
类型卸载需要满足:
该类的所有实例都已经被回收,java堆中不存在该类的任何实例
加载该类的ClassLoader已经被回收
该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
分配担保策略
大对象之间进老年代
长期存活的对象进入老年代
动态对象年龄判断, 并不是要求必须达到年龄才进入老年代,当相同的对象大于survivor一半时,进入老年代
空间分配担保:先检查老年代最大可用的联系空间是否大于新生代所有对象总空间。成立确保安全。不成立,看是否允许担保失败。允许继续检查老年代最大连续空间是否大于历代晋升到老年代对象的平均大小。
jps: 虚拟机进程状况工具
jstat: 虚拟机统计信息监视工具
jinfo: 查看Java配置信息
jmap: Java内存映射工具,堆转储快照文件, finalize队列, Java堆和方法去,收集器等信息
jhat: 堆转储快照文件分析工具, 内置了web服务器, 可用再浏览器查看
jstack: 生成虚拟机当前时刻的线程快照
只要装载了虚拟机, 而class文件能被它识别即可运行,实现了一次编译,到处运行。
而class文件都对应一个唯一的接口或类, 但反过来可能不对, class文件是一组以8个字节为基础单位的二进制流。中间没用任何分隔符。
Class文件格式采用一种类似c语言的伪结构体来存储数据。 分为一下俩种:
无符号表:基本数据类型,以u1 u2 u4 u8来代表1,2,4,8字节的无符号数, 可用用来描述数字,索引引用,数量值,或按照UTF-8组成的字符串值
表: 由多个字符号数或其他表作为数据项的复合数据类型。 为了区分习惯性命名为【_info】
而整个class文件也可以视为一张表!
魔数:Magic 每个class文件头4个字节, 唯一的作用确定能否被JVM加载
版本号:Minor version 次版本 Major 主版本号, 各俩个字节, 保证执行的Class文件版本再JDK版本以下。
常量池: Class文件的资源仓库。再入口会放一个u2 类型的数据, 代表常量池容量计数值。 在容量池中,为16进制数0x0016,即十进制为22, 而容量计数从1开始, 索引值范围1~21。 设计者将0空出来, 是为了常量池的索引值的数据再特定情况下需要表达不在引用任何一个常量池项目, 即索引设置为0 ;Class文件结构中除了常量池的容量计数外, 其他集合, 字段集合,索引集合都是从0开始
常量池中主要放俩大常量:
字面量 : 文本字符串, 被声明为的final的常量值
符号引用: 被模块导出或者开发的包; 类和接口的全限定名; 字段,方法的名称和描述符;
方法句柄和方法类型; 动态调用点和动态常量
符号引用不能直接被虚拟机使用,在类加载时,将会从常量池中获取符号引用,再在类创建时或解析时,加载为直接引用。
常量池中每一项都是一个表,共20中种:UTF-8,整数型,浮点型,长整型,双精度型,类和接口,字符串,字段,类中方法,接口中方法,字段或方法部分,方法句柄,方法类型,动态计算常量, 动态方法调用点, 模块, 模块开发的包和导出的包
一个类型从被加载到JVM内存开始,到卸载,整个生命周期是将会经历加载, 验证, 准备, 解析, 初始化, 使用, 卸载, 7个阶段。其中验证,准备,解析为连接
加载, 验证, 准备,初始化,卸载五个顺序是确定的, 类型也必须按照这种顺序按部就班开始。 而解析不一定在初始化前面。 这也将支持java可用运行时绑定特性。为啥是开始而不是进行或者完成, 因为这些阶段通常是互相交互地混合进行, 会在一个阶段中调用,激活另外地阶段。
在于在什么情况下开始加载第一个阶段JVM并没用强制约束。但严格规定了六种情况必须立即对类进行初始化
1)遇到static, getstatic, putstatic, invokestatic这四条指令时, 如果类型没用进行过初始化,先触发初始化阶段。 java场景:
使用new关键字, 读取或设置一个类型地静态字段(被final修饰除外)
调用静态方法
2)使用reflect包对类型进行反射调用时
3)当初始化类时, 发现父类没有初始化
4)虚拟机启动时,需要指定一个要执行地主类(main)
5)JDK7加入地动态语言支持,即MethodHandle生成地四个句柄【REF_getStatic,putStatic,invokeStatic,newInvokeSpecial】
6)当定义一个接口,它地实现类发生初始化,其该接口必须在之前被初始化
注意: 以上行为称对一个类型地主动行为, 其他行为均不会发生初始化,即被动行为!
子类调用父类地静态字段时,只有父类会初始化
通过数组定义来引用类, 也不会发生初始化
调用父类地常量即final修饰地字段, 不会被初始化,编译阶段就已经被存储
1)通过一个类地全限定名来获取此类地二进制流
2)将字节流所代表地静态存储结构转换为方法区运行时数据结构
3)在内存中生成Class对象, 作为方法区访问入口
加载阶段是开发可控性最强地阶段, 可用使用JVM内置地引导类加载器, 也可以使用用户自定义加载器。开发人员可用通过自己地类加载器去控制字节流获取方式(重写一个类加载器地findClass(),loadClass()方法)实现根据自己地想法赋予应用程序动态性
数组本身不是通过类加载器创建,由JVM直接在内存中动态构造,但是数组地组件类型需要依赖与类加载器
加载阶段完成后,会存储在方法区,在堆中实例化Class对象,这个对象将是程序访问方法区中类型数据地外部接口
确保Class文件的信息符合规范:文件格式验证,元数据验证,字节码验证符号引用验证
准备阶段正式为类中定义的变量即【static】, 分配内存和赋予初始值JDK8以后,类变量会随着Class对象存储在Java堆中。【这里说的是类变量, 不是实例变量】
将常量池中的符号引用转变为直接引用, 类和接口解析, 字段解析, 方法解析
是类加载的最后一步,在之前几个步骤中, 除了用户自定义加载器可局部参加外, 其余都是由JVM控制, 只要初始化阶段, jvm才会区调用编写的java程序代码, 将主导权交给应用程序。
由
1) 词法分析&语法分析
把源代码的字符流转换为Token流, 即不可再拆分的标记:int ==> Token.INT + ==> Token.PLUS
语法分析是根据,Token集合 解析成 抽象解析树 即【AST】
这个结构,可以层级地展示代码中所有的变量、方法甚至是注释等各种信息 (校验语法的重要手段)
2) 填充字符表
符号表就是: 由符号地址(位置)和符号信息构成的”表格“。 过程如下:
a. 将每个AST顶层节点放到待处理列表
b. 将所有的类符号(类的声明,名称)都输出到外层的作用域的符号表中
c. 如果发现有package-info.java文件(描述整个包的信息和包内的常量),将其顶层节点放到待处理的列表中
d. 明确泛型的真实类型
e. 如果没用构造器,默认添加无参构造器
f. 将类中符号输入到类自身的符号表中
3) 注解处理
注意的是, 并不是所有注解都是再编译器起作用,平时使用的反射处理注解主要是运行时注解,运行时注解再编译期, 不受影响,直到class文件加载到JVM中运行才会生效
而编译器注解@Retention(RetentionPolicy.SOURCE)
定义的,在编译期就处理了的注解,这一类注解不会保留到class文件中。列如: lombok, 就是采用编译器注解, 生成get set, 但生成的class文件中我们找不到这个注解了!
编译器注解也是对【AST】语法树的增强, 这也是一步为数不多,编译器留给程序员编写代码来影响源代码编程过程的时机
4)语法分析
再第一步语法分析, 并不会深入分析,比如类型是否匹配计算,是否已经声明等!
4.1 标注检查: 在这里就是AST和符号表共同发挥作用。int a = b + 1 . 需要检查符号表中是否有b
a. 需要泛型方法类型的推导。必须明确真实类型
b. 常量折叠: int a = 3 + 1 ==> int a = 4 是javac编译器对源码少数的优化手段之一
4.2 数据分析
主要是标注检查进一步的检验, 主要检验局部变量和声明的返回值, final变量也是在这一步检查
5)解语法糖
语法糖就是实现便捷方式封装的一些语法, foreach, 自动拆装箱等, 就是将这些语法还原为笨拙的状态
类的加载指的是将类的 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在创建 一个java.lang.Class 对象,用来封装类在方法区内的数据结构。
分类: 引导类加载器(c/c++实现) BootStrapClassLoader 和 自定义加载器(派生于classLoader)(Extension Class Loader、System Class Loader、User-Defined ClassLoader)
启动类加载器: 加载核心类库, 由jvm提供自身需要的类, 不继承classLoader
Extension Class Loader: 扩展类加载器: 从java.ext.dirs系统属性所指定的目录中加载类库, 或从JDK的安装目录的jre/lib/ext 子目录(扩展目录)下加载类库。派生于classLoader, 父类是:BootStrapClassLoader
System Class Loader: 系统加载器: 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库 父类是: 扩展类加载器
用户自定义加载器: 继承classLoader, 实现findClass, 由defineClass(name, b, off, len, null)传入字节码二进制流的字节数组即可构建class对象
就是不管是那个类, 都需先委派给父类,看有没有, 如果父类存在,则由父类完成。
避免了有人恶意写一个与核心类库一样的包名和类名加载入内存中后, 存在病毒代码, 产生危害。 而有了双亲委派, 永远都不会执行到这份恶意的代码到内存。
如何实现双亲委派:
ClassLoaderh中有loadClass(String name, boolean resolve) 函数:
1. 首先判断是否被加载:findLoadedClass(name), 被加载返回
2. 判断是否有父类加载器, 有调用父类的loadClass, 否则调用自己的findClass
如何实现自定义类加载器?
1. 创建一个类继承ClassLoader
2. 重写findClass方法, 利用File类 加载class文件,得到二进制数据后, 我们可以通过defineClass()转换为Class> 类