齐博网站模板,怎么让网站收录在google,微信商城小程序开发一般需要多少钱,深圳 做网站 互联文章目录 一、集合1.1 List1.1.1 ArrayList1.1.2 Vector1.1.3 LinkedList 1.2 Deque1.3 Set1.4 Map1.4.1 HashMap1.4.2 LinkedHashMap 1.5 注意事项 二、函数式接口和 Lambda 表达式三、方法引用3.1 静态方法引用3.2 实例方法引用3.2 特定类型的方法引用3.4 构造器引用 四、Str… 文章目录 一、集合1.1 List1.1.1 ArrayList1.1.2 Vector1.1.3 LinkedList 1.2 Deque1.3 Set1.4 Map1.4.1 HashMap1.4.2 LinkedHashMap 1.5 注意事项 二、函数式接口和 Lambda 表达式三、方法引用3.1 静态方法引用3.2 实例方法引用3.2 特定类型的方法引用3.4 构造器引用 四、Stream 流4.1 获取 Stream 流4.2 Stream 流的中间方法4.3 Stream 流的终结方法 五、异常和错误六、泛型6.1 泛型类6.2 泛型接口6.3 泛型方法 七、反射八、注解九、Java 运行时数据区域9.1 程序计数器9.2 虚拟机栈9.3 本地方法栈9.4 堆9.5 元空间 十、类的生命周期十一、类加载11.1 Java 中默认的类加载器11.2 双亲委派机制11.3 打破双亲委派机制的三种方式 十二、垃圾回收12.1 可达性分析算法12.2 垃圾回收算法12.2.1 标记-清除算法12.2.2 复制算法12.2.3 标记-整理算法12.2.3 分代垃圾回收算法 一、集合
1.1 List
1.1.1 ArrayList
ArrayList 内部基于动态数组 Object[] 实现会根据实际存储的元素数量动态地扩容或缩容。不过 ArrayList 只能存储对象对于基本数据类型需要使用其对应的包装类同时线程不安全。
ArrayList 有三个构造函数其中以无参数构造方法创建 ArrayList 时它会初始化一个空数组但不分配实际容量只有在添加第一个元素时数组的容量才会扩展为默认大小通常为 10。
ArrayList()
ArrayList(int initialCapacity)
ArrayList(Collection? extends E c)ArrayList 在空间不足时会进行动态扩容扩容时首先会将容量变为原来的 1.5 倍左右奇数会丢掉小数然后检查新容量是否大于最小需要容量若还是小于最小需要容量那么就把最小需要容量当作数组的新容量。
1.1.2 Vector
Vector 与 ArrayList 类似也采用 Object[] 实现是 List 的古老实现类同时线程安全但是在增长时会以固定的幅度增加容量而不是按倍数增加这可能导致一些内存浪费因此通常更倾向于使用 ArrayList并使用显式的同步措施来确保线程安全性。
1.1.3 LinkedList
LinkedList 底层使用的是双向链表不过需要用到 LinkedList 的场景几乎都可以使用 ArrayList 来代替并且性能通常会更好。同时 ArrayList 也不是线程安全的。 1.2 Deque
ArrayDeque 和 LinkedList 都实现了 Deque 接口ArrayDeque 基于动态数组实现并且是循环数组在队满时会扩容为原来的两倍。LinkedList 基于链表实现速度较慢且存储密度低。因此一般优先考虑 ArrayDeque不过 ArrayDeque 不支持存储 null 且线程不安全。
ArrayDeque 主要通过 addFirst()、addLast()、removeFirst() 和 removeLast() 实现双端队列的相关操作这四个方法失败时都会抛出异常。 1.3 Set
Set 主要包括 HashSet、LinkedHashSet 和 TreeSet它们都能保证元素唯一并且都不是线程安全的。
三者主要区别在于底层数据结构不同
HashSet 的底层数据结构是哈希表基于 HashMap 实现。LinkedHashSet 的底层数据结构是链表和哈希表元素的插入和取出顺序满足 FIFO。TreeSet 底层数据结构是红黑树元素是有序的排序的方式有自然排序和定制排序。 1.4 Map
1.4.1 HashMap
HashMap 是一个无序的键值对集合可以存储 null 的 key 和 value但 null 作为键只能有一个null 作为值可以有多个。
创建时如果不指定容量初始值HashMap 默认的初始化大小为 16之后每次扩充容量翻倍。创建时如果给定了初始容量值HashMap 会将其扩充至 2 n 2^n 2n 大小主要是为了在减少哈希碰撞同时提高哈希运算的效率如果数组的长度为 2 n 2^n 2n那么在映射时只需要直接取键的低 n 位即可通过位运算即可实现不需要取余。
HashMap 基于数组 链表 / 红黑树实现默认采用数组 链表当链表长度大于 8 时会通过 treeifyBin() 处理哈希冲突如果此时数组长度小于 64 会优先对数组进行扩容否则会将链表转化为红黑树以减少搜索时间。
HashMap 是非线程安全的其并发版本为 ConcurrentHashMap。ConcurrentHashMap 通过 synchronized 锁定链表或红黑二叉树的首节点从而保证并发安全。不过 ConcurrentHashMap 的 key 和 value 均不能为 null因为多线程情况下无法准确判断 null 表示的是不存在还是存在一个空的键或值。同时ConcurrentMap 保证的只是单次操作的原子性而不是多次操作因此类似于先通过 containsKey() 判断键是否存在然后再通过 put() 插入元素的操作都是线程不安全的。如果想要执行复合操作可以通过 putIfAbsent() 等复合函数代替。
1.4.2 LinkedHashMap
LinkedHashMap 和 HashMap 都是 Java 集合框架中的 Map 接口的实现类。它们的最大区别在于迭代元素的顺序。HashMap 迭代元素的顺序是不确定的而 LinkedHashMap 提供了按照插入顺序或访问顺序迭代元素的功能。LinkedHashMap 内部维护了一个双向链表用于记录元素的插入顺序或访问顺序而 HashMap 则没有这个链表。因此LinkedHashMap 的插入性能可能会比 HashMap 略低但它提供了更多的功能并且迭代效率相较于 HashMap 更加高效。
LinkedHashMap 提供了两种顺序迭代元素的方式
按照插入顺序迭代元素按照插入顺序迭代元素是 LinkedHashMap 的默认行为。LinkedHashMap 内部维护了一个双向链表用于记录元素的插入顺序。因此当使用迭代器迭代元素时元素的顺序与它们最初插入的顺序相同。按照访问顺序迭代元素可以通过构造函数中的 accessOrder 参数指定按照访问顺序迭代元素。当 accessOrder 为 true 时每次访问一个元素时该元素会被移动到链表的末尾因此下次访问该元素时它就会成为链表中的最后一个元素从而实现按照访问顺序迭代元素。 1.5 注意事项
判断所有集合内部的元素是否为空使用 isEmpty() 方法而不是 size() 0因为 isEmpty() 方法的可读性更好并且效率更高size() 0 在某些情况下还要进行类型转换。集合转数组时使用集合的 collection.toArray(T[] array) 方法有以下两种使用方式。import java.util.*;class Solution {public static void main(String[] args) {ListInteger list new ArrayList(Arrays.asList(1, 2, 3));Integer[] array new Integer[3];// 1. 返回参数list.toArray(array);for (int i 0; i array.length; i) {System.out.println(i); // 0 1 2}// 2. 通过 new Integer[0] 说明返回类型0 是为了节省空间array list.toArray(new Integer[0]);for (int i 0; i array.length; i) {System.out.println(i); // 0 1 2}}
}数组转集合时使用 Arrays.asList(array) 方法。不过该数组必须是对象数组而不是基本类型数组。同时 Arrays.asList() 方法返回的并不是 java.util.ArrayList而是 java.util.Arrays 的一个内部类因此需要一次附加的转换。import java.util.*;class Solution {public static void main(String[] args) {Integer[] array new Integer[] {1, 2, 3};ListInteger list Arrays.asList(array);System.out.println(list); // [1, 2, 3]System.out.println(list.getClass()); // class java.util.Arrays$ArrayListListInteger trueList new ArrayList(list);trueList.remove(2);System.out.println(trueList); // [1, 2]System.out.println(trueList.getClass()); // class java.util.ArrayList}
}二、函数式接口和 Lambda 表达式
函数式接口和 Lambda 表达式均为 Java8 新特性。函数式接口就是有且仅有一个抽象方法但是可以有多个非抽象方法的接口可以被隐式转换为 Lambda 表达式。Lambda 表达式实际是匿名函数其原型为(参数列表) - {函数体};。
import java.util.*;class Solution {public static void main(String[] args) {Integer[] array new Integer[]{1, 2, 3};Arrays.sort(array, new ComparatorInteger() {Overridepublic int compare(Integer o1, Integer o2) {return o2 - o1;}});ListInteger list Arrays.asList(array);list.forEach((i)-{System.out.println(i); // 3 2 1});}
}三、方法引用
方法引用通过方法的名字来指向一个方法可以使语言的构造更紧凑简洁减少冗余代码。方法引用使用一对冒号 :: 表示主要分为静态方法引用、实例方法引用、特定类型的方法引用和构造器引用。
3.1 静态方法引用
public class MyCompare {public static int compareFunc(int o1, int o2) {return o2 - o1;}
}import java.util.*;class Solution {public static void main(String[] args) {Integer[] array new Integer[]{1, 2, 3};// 静态方法引用Arrays.sort(array, MyCompare::compareFunc);ListInteger list Arrays.asList(array);list.forEach(System.out::println); // 3 2 1}
}3.2 实例方法引用
public class MyCompare {public int compareFunc(int o1, int o2) {return o2 - o1;}
}import java.util.*;class Solution {public static void main(String[] args) {Integer[] array new Integer[]{1, 2, 3};MyCompare compare new MyCompare();// 实例方法引用Arrays.sort(array, compare::compareFunc);ListInteger list Arrays.asList(array);list.forEach(System.out::println); // 3 2 1}
}3.2 特定类型的方法引用
import java.util.*;class Solution {public static void main(String[] args) {String[] array new String[]{b, A};// 特定类型的方法引用Arrays.sort(array, String::compareToIgnoreCase);ListString list Arrays.asList(array);list.forEach(System.out::println); // A b}
}3.4 构造器引用
public class MyCompare {}import java.util.function.Supplier;class Solution {public static void main(String[] args) {SupplierMyCompare sup MyCompare::new;}
}四、Stream 流
Stream 在 Java8 中被引入它提供了一种类似于 SQL 语句的方式来对 Java 集合进行操作和处理。
4.1 获取 Stream 流
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;class Solution {public static void main(String[] args) {ListString list new ArrayList();String[] array new String[10];// 获取集合的 Stream 流StreamString listStream list.stream();// 获取数组的 Stream 流StreamString arrayStream Arrays.stream(array);}
}4.2 Stream 流的中间方法
常用中间方法说明StreamT filter(Predicate? super T predicate)用于对流中的数据进行过滤StreamT sorted(Comparator? super T comparator)按照指定规则排序StreamT limit(long maxSize)获取前几个元素StreamT skip(long n)跳过前几个元素StreamT distinct()去除流中重复的元素自定义对象需要重写 equals() 和 hashCode() 方法R StreamR map(Function? super T, ? extends R mapper)加工元素并返回新流static T StreamT concat(stream a, Stream b)合并 a 和 b 为一个流
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;class Solution {public static void main(String[] args) {ListInteger list new ArrayList(Arrays.asList(1, 2, 3));list.stream().filter(i - i 1).forEach(System.out::print); // 2 3list.stream().sorted((o1, o2) - o2 - o1).forEach(System.out::print); // 3 2 1list.stream().map(i - i 1).forEach(System.out::print); // 2 3 4}
}4.3 Stream 流的终结方法
常用终结方法说明void forEach(Consumer action)遍历long count()统计元素个数OptionalT max(Compatator? super T comparator)获取最大值元素OptionalT min(Compatator? super T comparator)获取最小值元素R collect(Collector collector)把流中的元素收集到集合Object[] toArray()把流中的元素收集到数组 五、异常和错误
java.lang.Throwable 是所有异常和错误的父类它有 Exception 和 Error 两个子类其中异常又可以分为受检查异常和不受检查异常。
不受检查异常在编译阶段不会出现错误提醒主要包括 RuntimeException 及其子类例如 NullPointerException、IllegalArgumentException、ArrayIndexOutOfBoundsException 和 ClassCastException 等异常。受检查异常在编译阶段就会出现错误提醒除了 RuntimeException 及其子其他的 Exception 类及其子类都属于受检查异常例如 IOException、SQLException 和 ClassNotFoundException 等。处理可检查异常的方式可以是使用 try-catch-finally 语句块进行捕获和处理或者在方法签名中声明抛出该异常。Error 类用来表示系统级别的严重错误例如 OutOfMemoryError、StackOverflowError 等。对于系统错误一般不建议进行捕获和处理而是直接让 JVM 终止。
Throwable 类常用方法:
String getMessage()返回异常发生时的简要描述。String toString()返回异常发生时的详细信息。void printStackTrace()在控制台上打印 Throwable 对象封装的异常信息。
Exception 支持自定义新的异常类型但是在项目中保持一个合理的异常继承体系是非常重要的因此可以定义一个 BaseException 根异常继承自 RuntimeException其他业务类型的异常再从根异常中派生。
需要注意的是在通过 try-catch-finally 捕获和处理异常时不应该在 finally 语句块中使用 return因为当 try 语句和 finally 语句中都有 return 语句时try 语句中的返回值会先被暂存在一个本地变量中当执行到 finally 语句中的 return 之后这个本地变量的值就变为了 finally 语句中的 return 返回值从而导致 try 语句块中的 return 语句会被忽略。同理catch 中的 return 也会被 finally 中的 return 覆盖。
class Solution {public static void main(String[] args) {System.out.println(func()); // 2}private static int func() {try {return 1;} finally {return 2;}}
}此外在 JDK7 之后还提供了 try-with-resources 用于管理文件、网络连接、数据库连接等所有实现了 java.lang.AutoCloseable 接口的资源类它简化了资源管理的代码并确保资源在使用后被正确关闭以避免资源泄漏。 六、泛型
泛型是 JDK5 中引入的一个新特性类似于 C 中的模板泛型编程以一种独立于任何特定类型的方式编写代码。Java 中的泛型主要有泛型类、泛型接口、泛型方法三种使用方式。
6.1 泛型类
public class MyClassT {private T data;public T getData() {return data;}public void setData(T data) {this.data data;}
}class Solution {public static void main(String[] args) {MyClassInteger classInt new MyClass();classInt.setData(1);System.out.println(classInt.getData()); // 1MyClassDouble classDouble new MyClass();classDouble.setData(1.0);System.out.println(classDouble.getData()); // 1.0}
}6.2 泛型接口
public interface MyInterfaceT {T func();
}public class MyClass implements MyInterfaceString {Overridepublic String func() {return str;}
}class Solution {public static void main(String[] args) {MyInterfaceString myClass new MyClass();System.out.println(myClass.func()); // str}
}6.3 泛型方法
class Solution {public static void main(String[] args) {Integer i 1;Long l 1L;func(i); // class java.lang.Integerfunc(l); // class java.lang.Long}private static T void func(T data) {System.out.println(data.getClass());}
}七、反射
反射机制允许我们在运行时获取类的信息、调用类的方法、操作类的属性而无需在编译时知道类的具体名称但存在一定的安全问题同时会影响性能。
获取 Class 对象的四种方式
package atreus.ink;public class MyClass {}import atreus.ink.MyClass;class Solution {public static void main(String[] args) throws ClassNotFoundException {{// 1. 使用 .class 获取不会触发类的初始化ClassMyClass clazz MyClass.class;System.out.println(clazz); // class atreus.ink.MyClass}{// 2. 通过 Class.forName() 传入类的全路径获取Class? clazz Class.forName(atreus.ink.MyClass);System.out.println(clazz); // class atreus.ink.MyClass}{// 3. 通过对象实例的 getClass() 方法获取MyClass myClass new MyClass();Class? extends MyClass clazz myClass.getClass();System.out.println(clazz); // class atreus.ink.MyClass}{// 4. 通过类加载器的 loadClass() 方法传入类的全路径获取不会触发类的初始化ClassLoader classLoader MyClass.class.getClassLoader();Class? clazz classLoader.loadClass(atreus.ink.MyClass);System.out.println(clazz); // class atreus.ink.MyClass}}
}反射的一些基本操作
package atreus.ink;public class MyClass {private String data;public MyClass() {data atreus;}private void privateMethod() {System.out.println(private void privateMethod());}public String publicMethod(String s) {return s;}
}import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;class Solution {public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {// 通过 Class.forName() 传入类的全路径获取 Class 对象Class? clazz Class.forName(atreus.ink.MyClass);// 创建类的实例对象Object instance clazz.getDeclaredConstructor().newInstance();// 获取类中定义的所有方法Method[] methods clazz.getDeclaredMethods();for (Method method : methods) {System.out.println(method);}// 获取指定方法并调用{Method method clazz.getDeclaredMethod(publicMethod, String.class);Object result method.invoke(instance, public String publicMethod(String s));System.out.println(result);}{Method method clazz.getDeclaredMethod(privateMethod);method.setAccessible(true);method.invoke(instance);}// 获取指定参数并在修改后输出Field field clazz.getDeclaredField(data);field.setAccessible(true);field.set(instance, new data);System.out.println(field.get(instance));}
}public java.lang.String atreus.ink.MyClass.publicMethod(java.lang.String)
private void atreus.ink.MyClass.privateMethod()
public String publicMethod(String s)
private void privateMethod()
new data八、注解
注解可以看作是一种特殊的注释主要用于修饰类、方法或者变量提供了某些信息供程序在编译或者运行时使用。
注解本质上是是一个接口继承自 Annotation 类注解属性本质则是抽象方法使用注解实际上使用的是该接口的实现类。可以通过 interface 自定义注解且注解中如果只有一个 value 属性使用注解时 value 名称可以不写
public interface MyAnnotation {public 属性类型 属性名() default 默认值;
}public interface MyAnnotation extends Annotation {public abstract 属性类型 属性名();
}public interface MyAnnotation {String value();
}class Solution {MyAnnotation(str) // 可以省略 valuepublic static void main(String[] args) {}
}元注解是修饰注解的注解主要分为 Target 和 Retention
Target声明被修饰的注解能在哪些位置使用如类、接口、成员变量、成员方法等。Retention声明注解的保留周期。SOURCE 表明只作用在源码阶段字节码文件中不存在。CLASS 为默认值表明保留到字节码文件中但运行阶段不存在。RUNTIME 表明一直保留到运行阶段。
AnnotatedElement 接口定义了与注解解析相关的方法。注解一般需要与反射结合使用所有的类成分 Class、Method、Field 和 Constructor 都实现了 AnnotatedElement 接口它们都拥有解析注解的能力。
主要解析方法有
Annotation[] getDeclaredAnnotations()获得当前对象上使用的所有注解返回注解数组。T getDeclaredAnnotation(ClassT annotationClass)根据注解类型获得对应注解对象。boolean isAnnotationPresent(ClassAnnotation annotationClass)判断当前对象是否使用了指定的注解。 九、Java 运行时数据区域
Java 运行时数据区域主要分为程序计数器、虚拟机栈、本地方法栈、堆和元空间其中只有堆和元空间为线程共享。
9.1 程序计数器
每个线程会通过自己的程序计数器记录当前要执行的字节码指令的地址。程序计数器一方面能够控制指令的执行顺序实现分支、跳转和异常等逻辑另一方面也能够在多线程执行情况下为当前线程记录 CPU 切换前指令的执行位置。
9.2 虚拟机栈
虚拟机栈的生命周期和线程相同随着线程的创建而创建随着线程的死亡而死亡。它由一个个栈帧组成调用一个新的函数会在栈上创建一个新的栈帧当函数返回时这个栈帧会被自动销毁。如果栈的大小不支持动态扩展发生栈溢出时就会抛出 StackOverFlowError。如果栈的大小支持动态扩展在扩展过程中无法申请到足够的内存空间时就会抛出 OutOfMemeoryError可以通过虚拟机参数 -Xss 修改栈的大小。
每个栈帧由局部变量表、操作数栈和帧数据三部分组成
局部变量表本质是一个数组数组中从前向后依次保存了实例方法的 this 对象非静态方法、方法的参数有参方法以及方法体中声明的局部变量数组的每个位置称之为槽long 和 double 类型占用两个槽其他类型包括 this 等引用类型占用一个槽。局部变量表的具体内容在编译成字节码文件时就已经确定不过最大槽位数是固定的因此为了节省空间一旦槽中的某个局部变量不再被使用当前槽就可以被其他局部变量复用。操作数栈主要用于存放执行执行过程中的中间数据在编译期就能确定操作数栈的最大深度从而在执行时正确分配内存大小。帧数据主要包含动态链接、方法出口和异常表的引用 动态链接如果当前类的字节码指令引用了其他类的属性或者方法时需要将符号引用转换成对应的运行时常量池位于元空间中的内存地址动态链接就保存了符号引用到运行时常量池中的内存地址的映射关系。方法出口方法在正确或者异常结束时当前栈帧会被弹出同时程序计数器应该指向上一个栈帧中的下一条指令的地址方法出口存储的就是这条指令的地址。异常表的引用异常表存放的是代码中异常的处理信息包含了 try 代码块的覆盖范围以及出现或未出现异常时需要跳转到的字节码指令的地址。
9.3 本地方法栈
本地方法栈存储的是 native 本地方法的栈帧不过在 HotSpot 虚拟机中Java 虚拟机栈和本地方法栈使用的是同一个栈空间。
9.4 堆
堆内存是空间最大的一块内存区域创建出来的所有对象都保存在堆空间上。可以通过虚拟机参数 -Xms 修改堆的初始大小total通过 -Xmx 修改堆的最大大小max。
堆除了存储普通的对象还存储了字符串常量池。字符串常量池主要存储了字符串字面值从而实现字符串的重用。可以通过 intern() 方法手动将堆中字符串的引用放入字符串常量池。
9.5 元空间
元空间是在 JDK8 之后对方法区的具体实现方法区只是逻辑上的概念类似于 C 中的自由存储区元空间使用的是直接内存。
元空间主要保存了类的基本信息元信息与运行时常量池:
类的元信息主要包括类的名称、方法和字段的描述、访问修饰符以及继承关系等。运行时常量池运行时常量池是类文件中的常量池的运行时表示包括字面量、符号引用、方法和字段引用等。 十、类的生命周期
加载阶段类加载器会通过全类名获取定义此类的二进制字节流字节码然后 JVM 会将字节流中的信息以 InstanceKlass 对象的形式保存到方法区最后在堆中生成一份与 InstanceKlass 中的数据类似的 java.lang.Class 对象。验证阶段验证 Class 文件的字节流中包含的信息是否符合《Java 虚拟机规范》主要分为文件格式验证例如文件是否以 0xCAFEBABE 开头、主次版本号是否符合虚拟机要求、元数据验证例如类必须有父类、字节码验证例如方法内的执行指令跳转位置是否合理、符号引用验证例如类是否访问了其他类中的 private 方法。准备阶段为静态变量分配内存并设置初始值。一般情况下准备阶段的初始值为 0代码中的指定值会在初始化阶段再赋给静态变量但如果这个静态变量还被 final 修饰由于其常量性会直接初始化为代码中的指定值。解析阶段将常量池内的符号引用替换为直接引用直接引用不再使用编号而是直接使用内存中的地址访问具体的数据。初始化阶段执行静态代码块以及静态字段初始化语句具体执行顺序取决于 Java 中编写的顺序只有在主动使用类时才会初始化类。使用阶段使用包括主动引用和被动引用只有主动引用会引起类的初始化而被动引用不会引起类的初始化。卸载阶段卸载类即将方法区中的类回收只有同时满足以下三个条件时类才能被卸载 此类的所有实例对象都已经被回收在堆中不存在任何该类的实例对象以及子类。加载该类的类加载器已经被回收。该类对应的 java.lang.Class 对象没有在任何地方被引用。
主动引用包括
通过 new 创建一个类的对象。访问未被 final 修饰的静态变量或者调用静态方法。调用 Class.forName(String className)。虚拟机启动时会初始化包含 main 方法的主类。初始化一个类如果其父类还未初始化则先触发父类的初始化。
被动引用包括
引用父类的静态字段只会引起父类的初始化而不会引起子类的初始化。定义类数组不会引起类的初始化。引用类的 static final 常量不会引起类的初始化。 十一、类加载
11.1 Java 中默认的类加载器
启动类加载器BootstrapClassLoader最顶层的加载类由 Hotspot 虚拟机提供通过 C 实现JDK 9 之后由 Java 实现且由按路径查找变为按模块查找。主要用来加载 JDK 内部的核心类库如 java.lang 和 java.util 等以及被 -Xbootclasspath 参数指定的路径下的所有类。扩展类加载器ExtensionClassLoader由 JDK 提供主要负责加载 JDK 的扩展类库即 $JAVA_HOME/jre/lib/ext 目录下的类它们通用但不重要以及被 -Djava.ext.dirs 参数指定的路径下的所有类。应用程序类加载器AppClassLoader由 JDK 提供是面向用户的加载器主要负责加载项目中生成的字节码文件以及第三方依赖中的字节码文件。
11.2 双亲委派机制
双亲委派机制保证了类加载的安全性所有核心类都有顶层类加载器加载避免了恶意程序篡改核心类库同时避免了类的重复加载。
双亲委派机制的执行流程
在类加载的时候系统会首先判断当前类是否被加载过已经被加载的类会直接返回。类加载器在进行类加载的时候它首先不会自己去尝试加载这个类而是调用父加载器 loadClass() 方法把这个请求委派给父类加载器去完成。这样所有的请求最终都会传送到顶层的启动类加载器 BootstrapClassLoader 中。只有当父加载器反馈自己无法完成这个加载请求它的搜索范围中没有找到所需的类时子加载器才会调用自己的 findClass() 方法来尝试自己去加载。如果子类加载器也无法加载这个类那么它会抛出一个 ClassNotFoundException 异常。
11.3 打破双亲委派机制的三种方式
自定义类加载器自定义类加载器继承自 ClassLoader重写 loadClass() 方法即可打破双亲委派机制。Tomcat 就通过自定义类加载器为每个 Web 应用指定独立的类加载器从而实现了应用之间类的隔离。线程上下文类加载器以 JDBC 为例启动类加载器在加载 DriverManager 类的过程中需要同时获取并加载第三方驱动其中驱动的获取可以通过 SPI 机制实现但第三方驱动的加载只能由应用程序类加载器实现因此需要通过线程上下文类加载器应用程序类加载器打破双亲委派机制加载第三方驱动类并创建对象。Osig 框架的类加载器允许同级之间的类加载器委托加载同时还使用类加载器实现了热部署的功能。不过 Osig 已经废弃热部署可以通过阿里的 Arthas 实现。 十二、垃圾回收
12.1 可达性分析算法
可达性分析算法的基本思想就是以一系列的 GC 根节点GC Roots对象作为起点从这些节点开始向下搜索节点所走过的路径称为引用链当一个对象到 GC 根节点没有任何引用链相连的话则证明此对象是不可用的需要被回收。
可作为 GC 根节点的对象主要有以下几种
线程 Thread 对象。系统类加载器加载的 java.lang.Class 对象。监视器对象用来保存同步锁 synchronized 关键字持有的对象。本地方法调用时使用的全局对象。
12.2 垃圾回收算法
12.2.1 标记-清除算法
标记阶段将所有存活的对象进行标记。Java 中使用可达性分析算法从 GC Root 开始通过引用链遍历出所有存活对象。清除阶段从内存中删除没有被标记也就是非存活对象。
优点实现简单只需要在第一阶段给每个对象维护标志位第二阶段删除对象即可。
缺点由于内存是连续的所以在对象被删除之后内存中会出现很多内存碎片。同时由于内存碎片的存在需要维护一个空闲链表对内存空间进行管理。
12.2.2 复制算法
准备 From 空间和 To 空间两块空间只能使用 From 空间进行内存分配。在垃圾回收 GC 阶段将 From 中存活对象复制到 To 空间。将两块空间的 From 和 To 名字互换。
优点不会产生内存碎片复制算法在复制时会将对象按顺序放入 To 空间因此不存在内存碎片。
缺点可用内存空间会缩小为总内存空间的一半内存利用率低。同时如果待复制的对象过大复制开销也会增加。
12.2.3 标记-整理算法
标记阶段将所有存活的对象进行标记。Java 中使用可达性分析算法从 GC Root 开始通过引用链遍历出所有存活对象。整理阶段将存活对象移动到堆的一端依次排列清理掉其余空间。
优点不会产生内存碎片同时内存利用率较高相对于复制算法。
缺点整理阶段会有较大的性能开销。
12.2.3 分代垃圾回收算法
分代垃圾回收将整个内存区域划分为年轻代存放存活时间比较短的对象和老年代存放存活时间比较长的对象其中年轻代还可以再分为 Eden、Survivor 0 和 Survivor 1 三个区。
创建出来的对象首先会被放入 Eden 伊甸园区。随着 Eden 区的对象越来越多如果 Eden 区满继续向年轻代放入对象就会触发年轻代的 GC称为 Minor GC 或者 Young GC。Minor GC 会把 Eden 区和 From 区中需要回收的对象回收把没有回收的对象放入 To 区其中 From 区和 To 区由 Survivor 0 和 Survivor 1 轮流担任即采用复制算法。每轮 Minor GC 都会为本轮存活对象记录一个年龄当年龄达到 JVM 阈值时相应对象会被晋升至老年代。当老年代中空间不足时如果继续向老年代放入对象首先会尝试 Minor GC通过回收年轻代尝试避免继续将对象放入老年代如果还是不足就会触发 Full GCFull GC 会对整个堆进行垃圾回收。如果 Full GC 依然无法回收掉老年代的对象那么当对象继续放入老年代时就会抛出 Out Of Memory 异常。 参考
https://javaguide.cn/home.html