简单梳理Java知识时,整理的笔记,分享一下,如有错误,还请指正,谢谢:pray:
编译由 Java 源码编译器(javac)来完成。
过程:源代码---【词法分析器】---Token 流---【语法分析器】---语法树---【语义分析器】---注解抽象语法树---【字节码生成器】---JVM 字节码
解析与填充符号表
注解处理
分析和字节码生成
泛型只会在 Java 源码中存在,编译过后会被替换为原来的原生类型(Raw Type,也称为裸类型)了。这个过程也被称之为“泛型擦除”。
代码更加简洁【不用强制转换】
程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现 ClassCastException 异常】
可读性和稳定性【在编写集合的时候,就限定了类型】
字节码是不区分系统的,而 JDK 是区分系统的。各个平台编译的字节码都符合 JVM 的规范,从而做到一次编译到处运行。
不是所有的类都会被加载到 JVM 的,Java 类的加载是动态的,只有在需要用到的时候才会被加载,这样节省了内存开销。
启动类加载器:【Bootstrap ClassLoader】负责加载$JAVA_HOME 中 jre/lib/ rt.jar 里所有的 class,由 C++实现,不是 ClassLoader 子类。
扩展类加载器【Extension ClassLoader】负责加载 java 平台中 扩展功能 的一些 jar 包,包括$JAVA_HOME 中 jre/lib/*.jar 或-Djava.ext.dirs 指定目录下的 jar 包。
应用类加载器【Application ClassLoader】负责记载 classpath 中指定的 jar 包及目录中 class。
用户自定义类加载器【User ClassLoadder】
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。
好处: 防止内存中出现多份同样的字节码(安全性角度)
类加载器在成功加载某个类之后,会把得到的 java.lang.Class 类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而 不会尝试再次加载 。
通过自定义 ClassLoader,并重写父类的 loadClass 方法可以打破双亲委派的机制,而 Java 的 SPI(服务提供发现机制)也是打破双亲委派机制的。
加载,查找并加载类的二进制数据,在 Java 堆中也 创建一个 java.lang.Class 类的对象 。
1)验证,文件格式、元数据、字节码、符号引用验证;
2)准备,为类的静态变量分配内存,并将其初始化为默认值;
3)解析,把类中的符号引用转换为直接引用
初始化,为类的静态变量赋予正确的初始值。
【Just In Time】将热点代码的字节码重新编译优化,让 CPU 直接执行,这样的执行效率会更高。HotSpot 是基于计数器来检测热点代码的而非采样。
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。
通过“可达性分析算法”分析哪些对象是垃圾,然后通过垃圾回收算法进行回收。
Serial 收集器,串行收集器是最古老, 最稳定以及效率高的收集器 ,但可能会产生 较长的停顿 ,只使用一个线程去回收。
ParNew 收集器,ParNew 收集器其实就是 Serial 收集器的 多线程版本 。
Parallel 收集器,Parallel Scavenge 收集器类似 ParNew 收集器,Parallel 收集器 更关注系统的吞吐量 。
Parallel Old 收集器,Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多线程“标记-整理”算法
CMS 收集器,CMS(Concurrent Mark Sweep)收集器是一种以 获取最短回收停顿时间 为目标的收集器。它需要 消耗额外的 CPU 和内存资源 ,在 CPU 和内存资源紧张,CPU 较少时,会加重系统负担。CMS 无法处理浮动垃圾 。CMS 的“标记-清除”算法,会导致大量 空间碎片的产生 。
G1 收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征 。
JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。
栈管运行,堆管存储。
JVM 内存会划分为堆内存和非堆内存,堆内存中也会划分为年轻代和老年代,而非堆内存则为永久代(方法区、元空间)。
年轻代又会分为 Eden 和 Survivor 区。Survivor 也会分为 FromPlace 和 ToPlace,toPlace 的 survivor 区域是空的。Eden,FromPlace 和 ToPlace 的默认占比为 8:1:1。
当然这个东西其实也可以通过一个 -XX:+UsePSAdaptiveSurvivorSizePolicy 参数来根据生成对象的速率动态调整
内存泄漏:对象可达,但是对象不会被使用。
内存溢出:装载类的空间不够、内存空间不足、内存泄漏等等都可能导致溢出。
JVM 规范让每个 Java 线程拥有自己的独立的 JVM 栈,也就是 Java 方法的调用栈。
当方法调用的时候,会生成一个栈帧。栈帧是保存在虚拟机栈中的,栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息
线程运行过程中,只有一个栈帧是处于活跃状态,称为“当前活跃栈帧”,当前活动栈帧始终是虚拟机栈的栈顶元素。
部分对象会在 From 和 To 区域中复制来复制去, 如此交换 15 次 (由 JVM 参数 MaxTenuringThreshold 决定,这个参数默认是 15),最终如果还是存活,就存入到老年代;
如果 对象的大小大于 Eden 的二分之一会直接分配在 old ,如果 old 也分配不下,会做一次 majorGC,如果小于 eden 的一半但是没有足够的空间,就进行 minorgc 也就是新生代 GC;
minor gc 后,survivor 仍然放不下,则放到老年代;
动态年龄判断 ,大于等于某个年龄的对象超过了 survivor 空间一半 ,大于等于某个年龄的对象直接进入老年代;
是不是 频繁创建了大对象(也有可能 eden 区设置过小) (大对象直接分配在老年代中,导致老年代空间不足--->从而频繁 gc)
是不是 老年代的空间设置过小了 (Minor GC 几个对象就大于老年代的剩余空间了) 1. 如果一次 full GC 后,剩余对象不多,那么说明 Eden 的空间设置太小,导致大量短生命周期的对象被分配到了老生代。 2. 如果一次 full GC 后,老生代的变化不大,那么是老生代分配空间太小了。
当 young gen 中的 eden 区分配满的时候触发 MinorGC(新生代的空间不够放的时候).