文章目录

  • 注解
    • java文档注释
    • 自定义注解
    • 解析注解
  • 反射
    • 反射机制介绍
    • 反射入门案例
    • 反射机制原理示意图
    • 反射机制
    • 反射调用优化
    • Class类
      • 哪些类型有Class对象
      • 如何获取Class类对象
    • 类的加载过程
    • 类的加载
      • 类加载过程
    • 类加载器
    • 类加载内存分析
      • 一、类加载的步骤
        • (一)加载阶段
        • (二)连接阶段
        • (三)初始化阶段
      • 二、类的对象
      • 三、内存溢出
    • 获取类结构信息
    • 反射暴破
      • 反射暴破创建实例
      • 反射暴破操作属性
      • 反射暴破操作方法

注解

  • 概念:说明程序的,给计算机看的。

  • 注释:用文字描述程序的,给程序员看的

  • 定义:注解(Annotation),也叫元数据。是一种代码级别的说明。它是jdk1.5及以后被引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

  • 概念描述:
    *JDK1.5之后的新特性
    *说明程序的
    *使用注解:@注解名称

  • 作用分类:
    ①编写文档:通过代码里标识的注解生成doc文档
    ②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
    ③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】

  • JDK中预定义的一些注解 :

    • @Override:检测被该注解标注的方法是否是继承自父类(接口)的
    • @Deprecated:该注解标注的内容,表示已过时
    • @SuppressWarnings:压制警告(一般传递参数all)@SuppressWarnings("all")

java文档注释

可以利用Javadoc工具将Java源代码中的文档注释自动转化为API文档。
javadoc命令的基本用法:
javadoc 选项 java源文件|包

javadoc 支持通配符,比如*.java,表示当前路径下所有的java文件。

javadoc常用选项有如下几个:

  • -d(directory):该选项指定一个路径,用于将生成的api文档放到指定目录下。
  • -windowtitle(text):该选项指定一个字符串,用于设置API文档的浏览器窗口标题
  • doctitle(html-code):该选项指定一个HTML格式的文本,用于指定概述页面的标题
    注意:只有对处在多个包下的源文件来生成API文档时,才有概述页面。
  • -header(html-code):该选项指定一个HTML格式的文本,包含每页面的页眉
    除此之外,javadoc命令还包含了大量其他选项,我们可以通过在命令行窗口执行javadoc -help命令来查看javadoc命令的所有选项。

例子:

  1. 编写一个Test01类,包含了文档注释。
  2. 在该文件所在文件夹目录下,Shift+鼠标右键打开命令行窗口,输入javadoc Test01.java


4. 进入此路径,可以看到一个该目录下有刚刚生成的API文档,打开index.html,将看到如下图所示的页面。


单击该页面左侧类列表区中的某个类,可以看到该类的详细信息。


除此之外,如果希望javadoc工具生成更详细的文档信息,例如为方法参数、方法返回值等生成详细的说明信息,则可以利用javadoc标记。 **常用的javadoc标记如下:**

  • @author:指定Java程序的作者
  • @version:指定源文件的版本
  • @depreated:不推荐使用的方法
  • @param:方法的参数说明信息
  • @return:方法的返回值说明信息
  • @see:“参见”,用于指定交叉参考的内容
  • @exception:抛出异常的类型
  • @throws:抛出的异常,和@exception同义

说明一点,API文档类似与产品的使用说明书,通常只需要介绍那些暴露的、供用户使用的部分,在java类中就是以public或protected修饰的内容,因此javadoc默认只处理public或protected修饰的内容。如果开发者希望可以提取private修饰的内容,则可以在命令行使用javadoc工具增加-private选项。还有,javadoc工具只处理文档源文件在类、接口、方法、成员变量、构造器和内部类之前的文档注释,忽略其他地方的文档注释。

自定义注解

package com.kuang.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** Create By  on 2021/10/1.*/
//自定义注解
public class Test01 extends Object{//注解可以显示赋值,如果没有默认值,我们就必须给注解赋值@MyAnnotation2(age = 18)public void test() {}@MyAnnotation3("kuangshen")public void test2(){}
}@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{//注解的参数:参数类型 + 参数名();String name() default "";int age();int id() default -1;  //如果默认值为-1,代表不存在String[] schools() default {"西部开源","清华大学"};
}@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3{String value();
}

格式:
元注解
public @interface 注解名称{
属性列表;
}
本质:
注解本质上是一个接口,该接口默认继承Annotation接口
public interface myAnno extends java.lang.annotation.Annotation{
}
属性: 接口中的抽象方法
要求:
1. 属性的返回值类型有下列取值:

                 *String*枚举*以上类型的数组

2.定义了属性,在使用时需要给属性赋值
(1)如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
(2)如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
(3)数组赋值时,值使用 { } 包裹。如果数组中只有一个值,则{ } 可以省略。
元注解 : 用于描述注解的注解。

@Target:描述注解能够作用的位置ElementType取值:TYPE:可以作用于类上METHOD:可以作用于方法上FIELD : 可以作用于成员变量上@Retention:描述注解被保留的阶段@Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到@Documented:描述注解是否被抽取到api文档中@Inherited:描述注解是否被子类继承

解析注解

  1. 自定义注解,新建Pro.java 注解文件
package com.kuang.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 描述需要执行的类名和方法名*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {String className();String methodName();
}
  1. Demo01.java
package com.kuang.annotation;public class Demo1 {public void show(){System.out.println("demo...show...");}
}
  1. 解析注解
    ParseAnnotation.java
package com.kuang.annotation;import org.springframework.context.annotation.Profile;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** 解析注解*/@Pro(className = "com.kuang.annotation.Demo1" ,methodName = "show")
public class ParseAnnotation {public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {//1.解析注解//1.1获取该类的字节码文件对象Class<ParseAnnotation> parseAnnotationClass = ParseAnnotation.class;//1.2获取字节码对象/** getAnnotation(Class)* 其实就是在内存中生成了一个该注解接口的子类实现对象* public class ProImpl implements Pro{*     public String className(){*         return "com.kuang.annotation.Demo1";*     }*     public String methodName(){*         return "show";*     }* }*/Pro annotation = parseAnnotationClass.getAnnotation(Pro.class);//3.调用注解对象中定义的抽象方法,获取返回值//其实就是在内存中生成了一个该注解接口的子类实现对象String c = annotation.className();   //拿到注解中定义的属性值String s = annotation.methodName();System.out.println(c);System.out.println(s);//加载该类进内存Class<?> aClass = Class.forName(c);//创建对象Object o = aClass.newInstance();//获取方法对象Method method = aClass.getMethod(s);//执行方法method.invoke(o);}
}

打印结果:

com.kuang.annotation.Demo1
show
demo...show...

总结:
在程序使用(解析)注解:获取注解中定义的属性值

  1. 获取注解定义的位置的对象 (Class,Method,Field)
  2. 获取指定的注解
/** getAnnotation(Class)* 其实就是在内存中生成了一个该注解接口的子类实现对象* public class ProImpl implements Pro{*     public String className(){*         return "com.kuang.annotation.Demo1";*     }*     public String methodName(){*         return "show";*     }* }*/
  1. 调用注解中的抽象方法获取配置的属性值

注解底层实现原理


反射

反射机制介绍

反射入门案例

  1. 新建项目,在src下新建配置文件re.properties
classfullpath = com.kuang.reflection.Cat
method = cry
  1. 新建Cat类
package com.kuang.reflection;/*** Create By  on 2021/10/4.*/
public class Cat {private String name = "招财猫";public void hi() {   //常用方法System.out.println("hi " + name);}public void cry(){System.out.println(name+" 喵喵~");}
}
  1. 新建ReflectionQuestion类
package com.kuang.reflection.question;import com.kuang.reflection.Cat;import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;/*** Create By  on 2021/10/4.*/
@SuppressWarnings("all")
public class ReflectionQuestion {public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {//根据配置文件re.properties 指定信息,创建Cat对象并调用方法hi
//        Properties properties = new Properties();
//        properties//传统方式 new 对象   -> 调用方法
//        Cat cat = new Cat();
//        cat.hi();//1. 使用Properties类,可以读写配置文件Properties properties = new Properties();properties.load(new FileInputStream("src/re.properties"));String classfullpath = properties.get("classfullpath").toString();String method = (String) properties.get("method");//2. 创建对象 , 传统的方法行不通 -> 使用反射机制
//        new classfullpath()//3. 使用反射机制解决//(1)加载类,返回一个Class类型的对象Class cls = Class.forName(classfullpath);//(2) 通过aClass得到你加载的类 com.kuang.reflection.Cat 的对象的实例Object o = cls.newInstance();System.out.println("o的运行类型="+ o.getClass());  //运行类型//(3) 通过cls 得到你加载的类 com.kuang.reflection.Cat 的methodName "hi" 的方法对象、//即;在反射中,可以把方法视为对象(万物皆对象)Method method1 = cls.getMethod(method);//(4)通过method1 调用方法:即通过方法对象 调用方法System.out.println("=============================");method1.invoke(o);  // 传统方法: 对象.方法() , 反射机制: 方法对象.invoke(对象)}
}
  1. 打印结果:
o的运行类型=class com.kuang.reflection.Cat
=============================
招财猫 喵喵~

反射机制原理示意图

反射机制

Java反射机制可以完成:

  1. 在运行时判断任何一个对象所属的类
  2. 在运行时构造任何一个类的对象
  3. 在运行时得到任意一个类所具有的成员变量和方法
  4. 在运行时调用任意一个对象的成员变量和方法
  5. 生成动态代理

反射相关的主要类:

  1. java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
  2. java.lang.reflect.Method:代表类的方法,Method对象就表示某个类的方法
  3. java.lang.reflect.Filed:代表类的成员变量,Filed对象就代表某个类的成员变量。
  4. java.lang.reflect.Constructor:代表类的构造方法,Constructor对象就代表某个类的构造器。
  5. 这些类在java.lang.reflection中。
package com.kuang.reflection;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;/*** Create By  on 2021/10/4.*/
public class Reflection01 {public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {//1. 使用Properties类,可以读写配置文件Properties properties = new Properties();properties.load(new FileInputStream("src/re.properties"));String classfullpath = properties.get("classfullpath").toString();String method = (String) properties.get("method");//3. 使用反射机制解决//(1)加载类,返回一个Class类型的对象Class cls = Class.forName(classfullpath);//(2) 通过aClass得到你加载的类 com.kuang.reflection.Cat 的对象的实例Object o = cls.newInstance();System.out.println("o的运行类型="+ o.getClass());  //运行类型//(3) 通过cls 得到你加载的类 com.kuang.reflection.Cat 的methodName "hi" 的方法对象、//即;在反射中,可以把方法视为对象(万物皆对象)Method method1 = cls.getMethod(method);//(4)通过method1 调用方法:即通过方法对象 调用方法System.out.println("=============================");method1.invoke(o);  // 传统方法: 对象.方法() , 反射机制: 方法对象.invoke(对象)//java.lang.reflect.Filed:代表类的成员变量,Field对象表示某个类的成员变量//getField 不能得到私有的属性Field ageField = cls.getField("age");System.out.println(ageField.get(o));  //传统写法: 对象.成员变量, 反射:成员变量对象.get(对象)//java.lang.reflect.Constructor: 代表类的构造方法,Constructor 对象表示构造器Constructor constructor = cls.getConstructor();  //()中可以指定构造器参数类型,返回无参构造器System.out.println(constructor);  //输出无参构造器Constructor constructor1 = cls.getConstructor(String.class);   //传入的String.class是String类的Class对象System.out.println(constructor1);  //输出有参构造器}
}

打印结果:

18
public com.kuang.reflection.Cat()
public com.kuang.reflection.Cat(java.lang.String)

反射调用优化

反射的优点:
可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
反射的缺点:
使用反射基本是解释执行,对执行速度有影响。

应用实例:

package com.kuang.reflection;import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** Create By  on 2021/10/4.*/
//测试反射调用的性能和优化
public class Reflection02 {//    Field
//    Constructorpublic static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {m1();m2();m3();}//传统方法调用hipublic static void m1(){long start = System.currentTimeMillis();Cat cat = new Cat();for (int i = 0; i<90000000;i++){cat.hi();}long end = System.currentTimeMillis();System.out.println("传统方法来调用hi 耗时="+(end - start));}//反射机制来调用hipublic static void m2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {//        Cat cat = new Cat();long start = System.currentTimeMillis();Class cls = Class.forName("com.kuang.reflection.Cat");Object o = cls.newInstance();Method hi = cls.getMethod("hi");for (int i = 0;i<90000000;i++){hi.invoke(o);  //反射调用方法}long end = System.currentTimeMillis();System.out.println("反射机制来调用hi 耗时="+(end-start));}//反射调用优化 + 关闭访问检查public static void m3() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {long start = System.currentTimeMillis();Class cls = Class.forName("com.kuang.reflection.Cat");Object o = cls.newInstance();Method hi = cls.getMethod("hi");hi.setAccessible(true);  //在反射调用方法时,取消访问检查for (int i = 0;i<90000000;i++){hi.invoke(o);  //反射调用方法}long end = System.currentTimeMillis();System.out.println("反射机制来调用hi 耗时="+(end-start));}
}

打印结果:

传统方法来调用hi 耗时=4
反射机制来调用hi 耗时=186
反射机制来调用hi 耗时=158

反射调用优化 - 关闭访问检查

  1. Method和Field、Constructor对象都有setAccessible()方法
  2. setAccessible作用是启动和禁用访问安全检查的开关。
  3. 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查

Class类

在Object类中定义了以下方法,此方法将被所有子类继承。
public final Class getClass()
以上的方法返回值的类型是一个Class类,此类是java反射的源头,实际上,反射从程序的运行结果来看很好理解,即:可以通过对象反射求出类的名称。

package com.kuang.annotation;import java.lang.reflect.Field;/*** Create By  on 2021/10/1.*/
//什么叫反射
public class Test02 {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {//1.通过反射获取类的class对象Class c1 = Class.forName("com.kuang.annotation.User");System.out.println(c1);  //显示cls对象,是哪个类的class对象  com.kuang.annotation.User//2.输出c1运行类型  java.lang.ClassSystem.out.println(c1.getClass());//3.得到包名System.out.println(c1.getPackage().getName()); //包名//4.得到全类名System.out.println(c1.getName());//5.通过c1创建对象实例User user = (User)c1.newInstance();System.out.println(user);//6.通过反射获取属性idField id = c1.getField("id");System.out.println(id.get(user)); //2//7.通过反射给属性赋值id.set(user,8);System.out.println("id为:"+id.get(user));//8.获取所有的(字段)属性System.out.println("========所有的字段属性==========");Field[] fields = c1.getFields();for (Field f:fields){System.out.println(f.getName());  //名字}Class c2 = Class.forName("com.kuang.annotation.User");Class c3 = Class.forName("com.kuang.annotation.User");Class c4 = Class.forName("com.kuang.annotation.User");//一个类在内存中只有一个class对象//一个类被加载后,类的整个结构都会被封装在class对象中。System.out.println(c2.hashCode());System.out.println(c3.hashCode());System.out.println(c4.hashCode());}
}//实体类 :pojo ,entity
class User {public String name;public int id = 2;public int age;public User() {}public User(String name, int id, int age) {this.name = name;this.id = id;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", id=" + id +", age=" + age +'}';}
}

打印结果:

class com.kuang.annotation.User
class java.lang.Class
com.kuang.annotation
com.kuang.annotation.User
User{name='null', id=2, age=0}
2
id为:8
========所有的字段属性==========
name
id
age
1555009629
1555009629
1555009629

对象照镜子(getClass())后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。
对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[ ])的有关信息。

  • Class本身也是一个类,因也继承Object类
  • Class对象不是new出来的,而是系统创建的
  • 一个加载的类在JVM中只会有一个Class实例,对于某个类的Class类对象,在内存中只有一份,因为类只加载一次。
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己是由哪个Class实例所生成。
  • 通过Class类对象可以完整地得到一个类中的所有被加载的结构,通过一系列API
  • Class对象是存放在堆中的
  • Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
  • 类的字节码的二进制数据,是放在方法区中的,有的地方称为类的元数据(包括 方法代码,变量名,方法名,访问权限等)

哪些类型有Class对象

  1. 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
  2. interface:接口
  3. 数组
  4. enum:枚举
  5. annotation:注解
  6. 基本数据类型
  7. void

如何获取Class类对象

package com.kuang.reflection;/*** Create By  on 2021/10/1.*///测试class类的创建方式有哪些
public class Test03 {public static void main(String[] args) throws ClassNotFoundException {//向上转型Person person = new Student();System.out.println("这个人是:"+ person.name);//方式一:父类引用指向子类对象Class c1 = person.getClass();System.out.println(c1.hashCode());//方式二: forName 获得Class c2 = Class.forName("com.kuang.reflection.Student");System.out.println(c2.hashCode());//方式三: 通过类名.class获得Class c3 = Student.class;System.out.println(c3.hashCode());//方式四:基本内置类型的包装类都有一个Type属性Class c4 = Integer.TYPE;System.out.println(c4);//获得父类类型Class c5 = c1.getSuperclass();System.out.println(c5);}
}class Person{public String name;public Person(String name) {this.name = name;}public Person() {}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +'}';}
}class Student extends Person{public Student() {this.name = "学生";}
}class Teacher extends Person{public Teacher(){this.name = "老师";}
}

基本数据类型(int,char,boolean,float,double,byte,long,short)获取Class类对象:
Class cls = 基本数据类型.class;
基本数据类型对应的包装类,可以通过.TYPE 得到Class类对象:
Class cls = 包装类.TYPE;

package com.kuang.reflection;import java.lang.annotation.ElementType;/*** Create By  on 2021/10/1.*/
//所有类型的Class对象
public class Test04 {public static void main(String[] args) {Class c1 = Object.class;  //类Class c2 = Comparable.class;  //接口Class c3 = String[].class;  //一维数组Class c4 = int[][].class;  //二维数组Class c5 = Override.class;  //注解类型Class c6 = ElementType.class;  //枚举类型Class c7 = int.class;  //基本数据类型Class c8 = void.class;   //voidClass c9 = Class.class;  //ClassClass c10 = Integer.TYPE; //包装类  intClass c11 = Character.TYPE;   //intSystem.out.println(c1);System.out.println(c2);System.out.println(c3);System.out.println(c4);System.out.println(c5);System.out.println(c6);System.out.println(c7);System.out.println(c8);System.out.println(c9);System.out.println(c10);System.out.println(c11);//只要元素类型与维度一样,就是同一个classint[] a = new int[10];int[] b = new int[100];System.out.println(a.getClass().hashCode());System.out.println(b.getClass().hashCode());}
}

打印结果:

class java.lang.Object
interface java.lang.Comparable
class [Ljava.lang.String;
class [[I
interface java.lang.Override
class java.lang.annotation.ElementType
int
void
class java.lang.Class
int
char
1555009629
1555009629

类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。

类的加载

反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载。

  • 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强。
  • 动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,则不报错,降低了依赖性。

类加载时机

  1. 当创建对象时(new) //静态加载
  2. 当子类被加载时,父类也加载 //静态加载
  3. 调用类中的静态成员时 //静态加载
  4. 通过反射 //动态加载

类加载过程

  1. 加载阶段:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。
  2. 连接阶段:将java类的二进制代码合并到JVM的运行状态之中的过程。
    ①验证:确保加载的类信息符合JVM规范,没有安全方面的问题。
    ②准备:正式为类变量(static)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。
    ③解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
  3. 初始化阶段:
    ①执行类构造器< clinit>()方法的过程。类构造器< clinit>() 方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
    ② 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
    ③虚拟机会保证一个类的< clinit>() 方法在多线程环境中被正确加锁和同步。

类加载器

ClassLoader能根据需要将class文件加载到JVM中,它使用双亲委托机制,在加载类的时候会判断类未被自己加载过,就优先让父加载器加载。另外在使用instanceof关键字、equals()方法、isAssignableFrom()方法、isInstance()方法时,就要判断是不是由同一个类加载器加载。

  1. 类加载器的种类
    1.1 启动类加载器(Bootstrap ClassLoader)
    负责加载JDK中的核心类库,即%JRE_HOME%/lib 目录下,这个类完全由JVM自己控制,外界无法访问这个类。不过在启动JVM时可以通过参数-Xbootclasspath来改变加载目录,有以下三种使用方式:
    -Xbootclasspath 完全取代系统默认目录;
    -Xbootclasspath/a 在系统加载默认目录前后,加载此目录;
    -Xbootclasspath/p 在系统加载默认目录里,加载此目录。
    1.2 扩展类加载器(ExtClassLoader)

继承自URLClassLoader类,默认加载%JRE_HOME%/lib/ext 目录下的jar包。可以用-D java.ext.dirs 来指定加载位置。-D是设置系统属性,即System.getProperty()的属性。
1.3 应用类加载器(AppClassLoader)

继承自URLClassLoader类,加载当前项目bin目录下的所有类。可以通过System.getProperty("java.class.path")获取到目录地址。
1.4自定义类加载器
如果我们想要自己实现类加载器,一般会继承URLClassLoader这个子类,因为这个类已经实现了大部分工作,只需要在适当的地方做些修改就好,就像我们要实现Servlet时通常会直接继承HttpServlet
不管是直接实现抽象类ClassLoader,还是继承URLClassLoader类,或其他子类,它的父类加载器都是AppClassLoader,因为不管调用哪个父类构造器,创建对象都必须最终调用getSystemClassLoader()作为父类加载器,然后获取到AppClassLoader
2. 类加载器的加载顺序
在JVM启动时,首先“启动类加载器”会去加载核心类,然后再由“扩展类加载器”去加载,最后让“应用类加载器”加载项目下的类。
另外,类加载器使用双亲委托模型,可以保证类只会被加载一次(当父类架加载了该类的时候,子类就不必再加载),避免重复加载。在加载类的时候,会判断如果类未被自己加载过,就让父加载器进行加载。这个父加载器并不是父类加载器,而是在构造方法中传入(如果不在构造方法中传入,默认的加载器是加载这个类的加载器),并且委派加载流程是在loadClass方法中实现的。当我们自定义类加载器的时候,一般不推荐覆盖loadClass方法。
ClassLoader抽象类中的loadClass方法如下:

loadClass方法中,当该类没有被自己加载过时,就调用父加载器的loadClass方法(没有父加载器则使用“启动类加载器”)。如果父类加载器没有加载到该类,就使用自己的findClass方法查找该类进行加载。 如果没有找到这个类则会抛出ClassNotFoundException异常。得到这个类的Class对象后,调用resolveClass方法来链接这个类,最后返回这个类的Class对象。
loadClass 中使用的几个方法如下:
①findClass通常是和defineClass方法一起使用。首先要去查找所加载的类字节码文件(不同的类加载器可以通过重写这个方法来实现不同的加载规则,如ExtClassLoaderAppClassLoaderer加载不同的类),然后调用defineClass方法生成类的Class对象并返回。
defineClass方法是将byte字节流解析成JVM能识别的Class对象,有了这个方法使我们不仅可以通过class文件实例化对象,还可以通过其他方式实例化对象,比如我们通过网络接收到一个类的字节码,可以利用这个字节码流直接创建类的Class对象形式实例化对象。
resolveClass是对这个类进行连接。如果想在类被加载到JVM中时就被连接,那么可以调用resolveClass方法,也可以选择让JVM在什么时候才连接这个类。
3. 自定义类加载器的作用
Tomcat自己实现自定义类加载器,因为要解决如下功能:
- 隔离两个Web应用所使用的类库,因为两个应用程序可能会用到同一个类的不同版本。
- 共享两个Web应用程序所使用的类库,如果两个应用程序所使用的类完全相同。
- 支持热替换
因此,tomcat服务器自己实现了类加载器。

双亲委派模型就是在其中实现的,所以如果不想打破双亲委派模型,只需 重写findClass方法,如果想要打破双亲委派模型,那可以重写loadClass方法。

  1. 类加载器的使用
    使用当前类的类加载器。
    MyTest.class.getClassLoader().loadClass("");
    ClassLoader.getSystemClassLoader().loadClass("");

类加载内存分析

package com.kuang.reflection;/*** Create By  on 2021/10/2.*/
//类的加载
public class Test05 {public static void main(String[] args) {A a = new A ();System.out.println(A.m);}
}
class A{static {System.out.println("A类静态代码块初始化");m = 300;}static int m = 100;public A(){System.out.println("A类的无参构造初始化");}
}

程序运行结果:

A类静态代码块初始化
A类的无参构造初始化
100

一、类加载的步骤

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括七个阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、 初始化(Initialization)、使用(Using)、卸载(Unloading),其中验证、准备、解析三个阶段统称为连接。

注:加载、验证、准备、初始化、卸载这五个顺序是一定的,而解析阶段在某些情况下可以在初始化之后再开始。

(一)加载阶段
首先会获取这个类的二进制字节流,将这个字节流中的数据存储到方法区中,然后生成一个代表该类的java.lang.Class对象(存放在堆中)(HotSpot是把Class对象放到方法区中),用来访问方法区中这些数据。
  1. 类会编译成class,加载到内存的方法区(特殊的堆)中,类加载时,它会把这些信息加进去:Test05类和A类的数据(1.静态变量 2.静态方法 3.常量方法 4.代码 …)
  2. 类加载时,会形成一个class对象,指向堆中的java.lang.Class对象(代表Test05类);产生另一个class对象,指向堆中的Java.lang.Class对象(代表A类)。这两个class对象包含了方法区中类的所有东西。

关于这个类的二进制字节流,我们可以利用自定义类加载器从以下渠道获取:

  • 从压缩包中读取:如JAR、WAR格式;
  • 从网络中获取:如Applet的应用;
  • 从数据库中读取:如有些中间件服务器
  • 运行时生成:如在java.lang.reflect.Proxy中为特定接口生成代理类;
  • 从其他文件中生成:如JSP生成对应的class类
  • … …

对于数组而言,加载情况有所不同,数组类本身不通过类加载器创建,是由JVM直接创建的。但是数组中的元素还是要靠类加载器去创建,如果数组去掉一个维度后是引用类型,就采用类加载器去加载,否则就交给启动类加载器去加载。
另外,加载阶段与连接阶段的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始。

将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成。

(二)连接阶段

(将类的二进制数据合并到JRE运行环境中。)

  1. 第一步,验证:是为了确保类中字节码的信息符合JVM的要求, 并且不会危害虚拟机自身的安全,有文件格式验证、元数据验证、字节码验证、符号引用验证。只有通过了文件格式验证,字节流中的数据才会被储存到方法区中,而后面的三种验证则是在方法区中进行的。符号引用验证发生在符号引用转化为直接引用的时候。
    对文件安全校验。
  2. 第二步,准备为类变量(static)分配内存,并设置类变量默认值(如static int a = 123 ,此时a的值为0,在初始化阶段才会变成123),这些内存都将在方法区中进行分配。这一阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
  3. 第三步,解析:将class常量池内的符号引用,加载到运行时常量池内成为直接引用的过程。符号引用是以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,但引用目标并不一定已经加载到内存中;直接引用在不同的虚拟机中有不同的实现方式,它可以是直接指向目标的指针相对偏移量、或是一个能间接定位到目标的句柄,引用的目标必定已经在内存中(类变量、类方法的直接引用可能是直接指针或句柄,实例变量、实例方法的直接引用都是偏移量。实例变量的直接引用可能是从对象的映像开始算起到这个实例变量位置的偏移量,实例方法的直接引用可能是方法表的偏移量)。
    将符号引用转为直接引用。

java字面量和符号引用:
字面量就是比如说int a = 1; 这个1就是字面量。又比如String a = “abc”; 这个abc就是字面量。
在java中,一个java类将会编译成一个class文件。在编译中,java类并不知道引用类的实际内存地址,因此只能使用符号引用来代替。比如org.simple.People类要引用org.simple.Tool类。在编译时People类并不知道Tool类的实际内存地址,因此只能使用符号org.simple.Tool(假设)来表示Tool类的地址。而在类装载器装载People类时,此时可以通过虚拟机获取Tool类的实际内存地址,因此便可以既将符号org.simple.Tool替换为Tool类的实际内存地址,即直接引用地址。

(三)初始化阶段

JVM负责对类进行初始化,这里主要是指静态成员。

首先什么情况下类会初始化?什么情况下类不会初始化?
1. 类的“主动引用”(一定发生初始化)

  • 创建类的实例(如通过new、反射、克隆、反序列化)
  • 访问类的静态变量(除了常量)和静态方法
  • 利用反射调用方法时
  • 初始化类时发现其父类未初始化,则初始化其父类
  • 虚拟机启动时,包含main()方法的类

2.类的“被动引用”(一定不发生初始化)

  • 访问一个静态变量,但这个变量属于其父类,只会初始化其父类。
  • 创建类的数组不会发生初始化(A[ ] a = new A[10];)。
  • 引用常量不会发生初始化(常量在编译阶段就存入所属的常量池中了)

接口的加载过程与类的加载过程稍有不同,接口中不能使用static{ }块。当一个接口在初始化时,并不要求其父接口全部都完成初始化,只有在真正用到父接口时(如引用接口中定义的变量)才会初始化。

二、类的对象

  1. 对象的创建
    A a = new A();会在堆中产生一个新的对象(A类的对象)。该对象指向方法区中的A类,从而可以拿到A类的所有数据(静态变量、静态方法、常量池、代码 …),通过这些数据,给 A类显示赋值,进而初始化。
    详细分析:
    当JVM遇到new指令时,首先去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载过,如果没有就先执行类加载。如果类已经被加载过,则会为新生对象分配内存(所需内存大小在类加载后就可以确定),分配对象内存采取的方式是“指针碰撞”或“空闲列表”,前者是在内存比较规整的情况下,后者是在空闲内存和已使用内存相互交错的情况下,而内存是否规整又取决于垃圾回收器。
    对象的创建是很频繁的,即使是简单的指针位置的修改,在并发情况下可能会出现线程安全问题。解决这个问题的方式有两种,一种是进行同步处理——JVM采用了CAS方式失败重试来保证的原子性操作;另一种是把内存分配划分在不同空间中——即每个线程预先分配一小块内存,成为本地线程分配缓冲(TLAB),可以通过-XX:+/-UseTLAB参数来设定是否使用。
    内存分配完成后,设置对象的对象头中的信息,如这个对象是哪个类的实例,如何找到。类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,此时,对象已经产生,但是还没有初始化,所有字段都为0。
  2. 对象的内存布局
    对象在内存中存储的布局可以分为3块区域:对象头、实例数据和对齐填充。
  • 对象头:包括两部分信息。第一部分储存对象自身运行时的数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳。另一部分是类型指针,指向它的类元数据的指针(不是所有的虚拟机都有)。如果是数组,那在对象头中还必须有一块记录数组长度的数据。
  • 实例数据:这部分是对象真正存储的有效信息,即在对象中定义的各种字段内容(无论是从父类中继承下来的,还是本身所定义的)。存储顺序受虚拟机的分配策略和定义顺序的影响,HotSpot默认的分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oops。
  • 对齐填充:不是必然存在的,也没有特别含义,仅仅起着占位符作用。因为HotSpot要求对象起始地址必须是8字节的整数倍。
  1. 对象的访问定位
    可以通过Java栈中对象的引用去访问这个对象,访问对象的主流方式有2种:使用句柄和直接指针。
  • 使用句柄访问:在Java堆中划分一块内存作为句柄池,引用中储存的内容就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
  • 直接指针访问:在对象的内存布局中就要放置访问类型数据的指针。
    这两种方式各有优势,使用句柄的好处是引用中存储的是稳定的句柄,对象被移动时(垃圾回收时对象被移动)只需改变句柄中的实例数据的指针,不需要改动引用本身。而使用直接指针的好处是速度更快,它节省了一次指针定位的开销。HotSpot使用的是第二种方式进行对象的访问。

三、内存溢出

除了程序计数器外,JVM中其他几个内存区域都有可能发生OutOfMemoryError异常。

  1. Java堆溢出
    如果不断创建对象,并且对象始终被强引用,则垃圾回收器无法回收这些对象。最终会产生内存溢出。通过-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机出现内存溢出时Dump出当前对储存为快照,以后事后分析。
    解决堆溢出,一般先通过内存映像分析工具对这个快照进行分析,弄清楚成出现了内存泄漏还是内存溢出。如果是内存泄漏,可以通过工具查看泄漏对象到GC Root的引用链,以此来判断泄漏原因;如果不存在泄漏,即内存中的对象都必须活着,可以调整堆的大小参数和对代码进行优化。
  2. Java栈溢出和本地方法栈溢出
    (在HotSpot中不区分Java栈和本地方法栈,虽然可以通过-Xoss参数设置本地方法栈的大小,但是并没有效果,栈容量只有由-Xss参数设定。)
    栈中发生的异常有两种:
  • 如果需要的深度超过最大深度时抛出StackOverflowError异常。
  • 如果栈无法申请到足够内存时抛出OutOfMemoryError异常。
  1. 方法区和运行时常量池溢出
    String 字符串的intern() 方法作用是,如果字符串常量池存在这个字符,则返回其对象的引用,否则将字符出拷贝到方法区中的字符串常量池。在Java7之后方法区被移入堆中,intern()方法也有所变化,不会将首次遇到的字符串对象本身放入常量池,只会在常量池中记录这个字符串对象的引用。
    在使用CGLib 动态的将类加载进内存时,很容易造成溢出。
  2. 本机内存溢出
    NIO

获取类结构信息

通过反射获取类的结构信息:



反射暴破

反射暴破创建实例

通过反射创建对象的方式:

package com.kuang.reflection;import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;/*** Create By  on 2021/10/4.*/
public class ReflectCreateInstance {public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {//1. 先获取到User类的Class对象Class<?> userClass = Class.forName("com.kuang.reflection.User");//2. 通过public的无参构造器创建实例Object o = userClass.newInstance();System.out.println(o);//3. 通过public 的有参构造器创建实例//3.1 先得到对应构造器Constructor<?> constructor = userClass.getConstructor(String.class);//3.2 创建实例 ,并传入实参Object hsp = constructor.newInstance("hsp");System.out.println("hsp="+hsp);//4. 通过非public的有参构造器创建实例//4.1 得到私有的构造器对象Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(int.class,String.class);//4.2 创建实例//暴破(暴力破解) ,使用反射可以访问私有构造器/方法/属性declaredConstructor.setAccessible(true);Object user2 = declaredConstructor.newInstance(100, "小张");System.out.println("user2"+user2);}
}class User{private int age = 10;private String name = "fangfang";public User(){}public User(String name){this.name = name;}private User (int age,String name){this.age = age;this.name = name;}public String toString(){return "User [age=" + age +",name=" +name+"]";}}

打印结果:

User [age=10,name=fangfang]
hsp=User [age=10,name=hsp]
user2User [age=100,name=小张]

反射暴破操作属性

package com.kuang.reflection;import java.lang.reflect.Field;/*** * 反射暴破操作属性*/
public class ReflectAccessProperty {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {//1.得到Student01类对应的Class对象Class<?> aClass = Class.forName("com.kuang.reflection.Student01");//2.创建对象  //o的运行类型就是StudentObject o = aClass.newInstance();System.out.println(o.getClass());//3.使用反射得到age属性对象Field age = aClass.getField("age");age.set(o,88); //通过反射操作属性System.out.println(o);System.out.println(age.get(o)); //返回age属性的值//4.使用反射操作 私有、静态属性Field name = aClass.getDeclaredField("name");//对私有、静态属性使用暴破name.setAccessible(true);
//        name.set(o,"杉菜");name.set(null,"杉菜~");  //因为name是static属性,是属于所有对象的,在类加载的时候就已经有了,o也可以写成nullSystem.out.println(o);System.out.println(name.get(o));  //获取属性值System.out.println(name.get(null));  //获取属性值,要求name是静态的}}
class Student01{public int age;private static String name;public Student01(){}public String toString(){return "Student [age=" + age + ",name=" + name +"]";}
}

打印结果:

class com.kuang.reflection.Student01
Student [age=88,name=null]
88
Student [age=88,name=杉菜~]
杉菜~
杉菜~

反射暴破操作方法

package com.kuang.reflection;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** 通过反射调用方法*/
public class ReflectAccessMethod {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {//1.得到Boss类对应的class对象Class<?> bossCls = Class.forName("com.kuang.reflection.Boss");//2.创建对象Object o = bossCls.newInstance();//3.得到public方法对象
//        Method hi = bossCls.getMethod("hi",String.class);//3.1 得到所有方法对象Method hi = bossCls.getDeclaredMethod("hi", String.class);//3.2 调用hi.invoke(o,"杉菜酱~");//4.调用私有静态方法//4.1 得到say方法对象Method say = bossCls.getDeclaredMethod("say", int.class,String.class, char.class);//4.2 因为say方法是private,所以需要暴破say.setAccessible(true);System.out.println(say.invoke(o, 2, "888", '9'));//4.3 因为say方法是静态的,还可以这样调用,可以给对象传入空System.out.println(say.invoke(null,200,"66",'5'));//5.在反射中,如果方法有返回值,统一返回Object,但是他运行类型和方法定义的返回类型一致Object reVal = say.invoke(null, 300, "wangwu", 'm');System.out.println("reVal的运行类型="+reVal.getClass());   //返回say方法的运行类型 String}
}class Boss {public int age;private static String name;public Boss() {}private static String say(int n, String s, char c) {return n + " " + s + " " + c;}public void hi(String s) {System.out.println("hi " + s);}
}

打印结果:

hi 杉菜酱~
2 888 9
200 66 5
reVal的运行类型=class java.lang.String

Java 注解和反射相关推荐

  1. java注解的反射_Java注解与反射

    概要 本文主要是总结Java注解与反射的相关知识,加深自己对Java类动态语言的理解,同时为日后学习Spring打下基础. 注解: 什么是注解 Annotation的作用 不是程序本身,但是可以对程序 ...

  2. java 注解与反射

    java 注解与反射 学习笔记: 1.注解:注释给计算机理解 英文:Annotation 2.注解不是必须的,但是会因为程序的需要 3. 三种java内置类型:Override.Deprecated. ...

  3. Java注解与反射系列——Class类day1-3

    Java注解与反射系列--Class类 Class类 常用方法 哪些类型有Class对象? 类的加载过程 类的加载 链接 初始化 什么时候会发送类的初始化 类的主动引用 类的被动引用 获取Class实 ...

  4. java注解与反射的使用及原理

    写在前面   你们好,我是小庄.很高兴能和你们一起学习Java注解与反射的使用及原理.如果您对Java感兴趣的话可关注我的动态.   写博文是一种习惯,在这过程中能够梳理知识和巩固知识点. 文章目录 ...

  5. 关于Java你不知道的那些事之Java注解和反射

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:硬刚一周,3W字总结,一年的经验告诉你如何准备校招! 个人原创100W+访问量博客:点击前往,查看更多 作者:轻 ...

  6. JAVA注解和反射(笔记)

    注解简介 Annotation是从JDK5.0开始引入的新技术). Annotation的作用 : 不是程序本身(可以对程序作出解释.(这一点和注释(comment)没什么区别) 可以被其他程序(比如 ...

  7. java 需要class interface 或enum_阿里P8教你Java注解与反射

    Ⅰ 什么是注解 Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制.Java 语言中的类.方法.变量.参数和包等都可以被标注.和 Javadoc 不同,J ...

  8. Java基础学习(11)---Java注解和反射

    文章目录 一.注解概述 二.内置注解 三.元注解 四.自定义注解 五.反射概述 ★ 静态语言 VS 动态语言 反射机制 Java反射机制研究及应用 Java反射优点和缺点 六.Class类 获取Cla ...

  9. java注解和反射——狂神说java学习笔记三

    原文链接:公众号狂神说 视频教程[狂神说B站]: https://www.bilibili.com/video/BV12J41137hu 如有侵权,联系立删 什么是注解 Annotation是从JDK ...

最新文章

  1. Tensorflow 10分钟快速上手
  2. 进程和线程的基本概念
  3. 【Linux】一步一步学Linux——newgrp命令(91)
  4. 机器学习中的数据预处理(sklearn preprocessing)
  5. C语言学习及应用笔记之一:C运算符优先级及使用问题
  6. 软件需求工程与UML建模第十二周作业
  7. 习惯养成android软件,7款好用的习惯养成与打卡APP推荐
  8. TrinityCore3.3.5环境搭建
  9. matlab视频文件处理,Matlab处理视频文件1
  10. #2297. 战争调度(war)
  11. linux-一条命令,找出包含指定字符串的进程并杀死
  12. Nodejs+express+vue+Elementui酒店客房管理系统
  13. ASP.NET WebForm 回传机制
  14. ipad html文件,ipad HTML文件怎么打开
  15. php网站恶意注册表,利用注册表对付恶意网站篡改IE主页
  16. 人性的弱点摘录和感悟
  17. 爆火论文打造《西部世界》雏形:25个AI智能体,在虚拟小镇自由成长
  18. 运用计算机来动画制作的视频,如何制作一个时钟转动动画视频?电脑制作动画的软件制作时钟转动的小视频的方法...
  19. android第三方app改为系统app,加入system组,获取system权限
  20. MapServer的安装和使用

热门文章

  1. 两性关系心理学:探讨魅力点和感觉的关系
  2. 连接到mysql数据库失败的原因_连接MySQL数据库失败的原因
  3. Linux- 系统随你玩之--玩出花活的命令浏览器上
  4. Unity + Vuforia 使用ModelTarget 实现3D物体的识别
  5. excel自动填充长数字
  6. 卖家必看:双十二如何玩转淘金币
  7. OPPO Android8刷机包.0,ColorOS8.0
  8. 天热难耐 考拉抱着水瓶喝水萌化了
  9. FATAL : No Bootable medium found! System halted
  10. 华为手机承载系统在哪里_说好的取代安卓呢?华为鸿蒙为啥不是手机操作系统了...