2.3Java内存分析
1.Java内存
-
栈:
- 一般用来存放用关键字new出来的数据
-
堆:
-
基本数据类型
-
局部变量(在方法代码段中定义的变量),方法调用完后JVM回收
-
引用数据类型——即需要用关键字new出来的对象所对应的引用 也是存放在栈空间中。
此时JVM在栈空间中给对象引用分配了一个地址空间,存储引用变量,
指向,在堆空间给该引用指向的实际对象分配的地址空间。
-
-
方法区:
- 用于存放已被加载的类信息、常量、static静态变量、即时编译器编译后的代码等数据
2.4类的加载
1.类的加载过程
-
类加载是一个将.class字节码文件读入内存,并实例化为Class对象且进行相关初始化的过程
-
java的类使用时才被加载,且类的加载过程只发生一次
java类的加载过程可分为三个步骤:加载、链接、初始化
区分“类的加载过程”和其步骤“加载”
“类的初始化”只是“类的加载过程”的一个步骤
-
加载:
-
将.class字节码文件读入内存,将静态数据转换成方法区的运行时数据结构
-
生成这个类对应的Class对象
Class对象是加载到内存中才会产生的,由JVM创建,我们只能获取,不能生成
通过反射动态进行的
-
-
链接:将Java类的二进制代码 合并到 JVM的运行状态之中
- 验证:确保类信息符合JVM规范
- 准备:为static变量分配内存并设置初始值(在类初始化之前做),在内存的方法区进行
- 解析:JVM常量池内的符号引用(常量名)替换为直接引用(地址)
-
初始化:。
-
JVM执行类构造器
<clinit>()方法
的过程。类构造器
<clinit>()方法
是由编译期 自动收集类中所有类变量的赋值动作和static代码块中的语句合并产生的。类构造器是构造类信息的,不是构造该类对象的构造器。
-
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
-
JVM会保证一个类的
<clinit>()方法
在多线程环境中被正确加锁和同步。
-
代码测试:
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类的无参构造初始化");
}
}
过程:
-
加载阶段:加载Test05类和A类,在方法区分别生成其对应的Class对象
-
链接阶段:为static变量m,在方法区分配内存空间,并进行默认初始化
static int m=0;
-
初始化阶段:类的
<clinit>()方法
将static变量赋值和static代码合并,再执行赋值System.out.println("A类静态代码快初始化"); m=300; m=100; //定义阶段已经在链接阶段完成
2.类加载时机和初始化时机
-
**类加载时机:**从执行包含main函数的类开始加载,涉及到下面情况的类就加载
- 使用类的静态变量或静态方法
- 使用反射方式来强制创建某个类或接口 对应的java.lang.Class对象
- 创建类的实例
- 初始化某个类的子类
- (直接使用java。exe命令来运行某个主类)
-
**类的初始化理解:**类的初始化 主要就是对类的static变量进行初始化
区分实例变量和静态变量,实例变量在new对象时初始化
-
JVM执行类构造器
<clinit>()方法
的过程。类构造器
<clinit>()方法
是由编译期 自动收集类中所有类变量的赋值动作和static代码块中的语句合并产生的。类构造器是构造类信息的,不是构造该类对象的构造器。
-
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
-
JVM会保证一个类的
<clinit>()方法
在多线程环境中被正确加锁和同步。
-
-
会发生类初始化的情况(类的主动引用):
- 当JVM启动,先初始化main方法所在的类
- 使用new关键字创建一个类的对象
- 调用该类的static变量(final的常量除外)和static方法
- 使用java.lang.reflect包对该类进行反射调用
- 当初始化一个类时,如果其父类没有被初始化,则会先初始化其父类
-
不会发生类初始化的情况(类的被动引用):
-
引用static常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
-
当访问一个静态成员变量时,只有真正声明这个静态成员的类才会被初始化,当通过子类引用父类的静态成员变量时,不会导致子类的初始化
-
通过数组定义类引用,不会触发此类的初始化。
数组只是一个名字和一片空间
-
测试:
//测试类什么时候会初始化
public class Test06 {
static {
System.out.println("Main类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//1.主动引用,会发生类的初始化
Son son = new Son();
/*
Main类被加载
父类被加载
子类被加载
*/
//反射也会产生主动引用
Class.forName("com.kuang.reflection.Son");
/*
Main类被加载
父类被加载
子类被加载
*/
//2.被动引用,不会触发类的初始化
//通过子类调用父类static变量或者方法
System.out.println(Son.b);
/*
Main类被加载
父类被加载
2
*/
//通过数组定义类引用
Son[] array=new Son[5];
/*
Main类被加载
*/
//类的常量
System.out.println(Son.M);
/*
Main类被加载
1
*/
}
}
class Father{
static final int M=1;
static int b=2;
static{
System.out.println("父类被加载");
}
}
class Son extends Father{
static{
System.out.println("子类被加载");
m=300;
}
static int m=100;
}
3.类加载器
什么是类加载器?
类加载器 就是 加载字节码文件(.class)的类
Java程序是一种具有动态性的解释性语言,类(class)只有被加载到JVM中后才能运行。
当运行指定程序时,JVM会将编译生成的.class文件按照需求和一定的规则加载到内存中,组织成为一个完整的Java应用程序。
这个加载的过程是由类加载器来完成的。具体来说,就是由ClassLoader和它的子类来实现的。
类加载器本身也是一个类,其实质是把类文件从硬盘读取到内存中
类加载的方式:
- 隐式加载:使用new等方式创建对象,会隐式调用类加载器把对应的类加载到JVM中
- 显式加载:通过反射直接调用Class.forName()方法把所需的类加载到JVM中。
类加载器分类:
在Java语言中,类的加载是动态的,它并不会一次性将所有类全部加载后再运行。而是保证程序运行的基础类(例如基类)完全加载到JVM中。
其他类,在需要时才加载。
- 引导类加载器:BootStapLoader,加载最基础的文件
- 用C++编写,是JVM自带的类加载器,负责Java平台核心类库。
- 该加载器程序无法直接获取。
- 在
jre/lib/rt.jar
包下
- 扩展类加载器:ExtClassLoader,加载基础的文件
- 在
jre/lib/ext/*.jar
包下
- 在
- 应用类加载器:AppClassLoader,加载三方jar包和自己编写的java文件
- 在
CLASSPATH指定的所有jar和目录
- 在
类加载器的协调工作:双亲委派机制
3种类加载器是通过委托方式实现:
当加载一个类,JVM会自底向上去找是否已经加载;
且不能存在同名,如我们不能定义java.lang.Spring类;
双亲委派机制会检测安全性,保证创建出的类的唯一性
加载时:
-
自底向上检查类是否已经装载
-
自顶向下尝试加载类
获取测试:
public class Test07 {
public static void main(String[] args) {
//获取AppClassLoader
ClassLoader appClassLoader=ClassLoader.getSystemClassLoader();
System.out.println(appClassLoader);
//获取ExtClassLoader
ClassLoader extClassLoader=appClassLoader.getParent();
System.out.println(extClassLoader);
//获取BootStapClassLoader
ClassLoader bootStapClassLoader=extClassLoader.getParent();
System.out.println(bootStapClassLoader);
}
//测试当前变量是由那个加载器加载的
ClassLoader curClassLoader=Class.forName("com.kuang.reflection.Test07").getClassLoader();
}
可以看出:
Test07类是由AppClassLoader加载的。
因为BootStapLoader先搜索指定目录找不到,其次ExtClassLoader也找不到,最后AppClassLoader在ClassPath中找到了Test07类。
【注意】BootStapLoader是用C++语言实现的,所以在Java语言中看不到它,输出null
类加载器 加载过程 分为以下3步:
- 装载(将.class文件放入内存,生成Class对象)
- 链接(1.检查:检查待加载.class文件的正确性;2.准备:给类中静态变量分配存储空间并默认初始化;3.解释:将符号引用转换成直接引用)
- 初始化(对静态变量和静态代码块执行初始化工作)
2.5利用反射获取Class对象 获取运行时类的完整结构
-
Field、Method、Constructor、Superclass、Interface、Annotation
-
在实际操作中,取得类的信息的操作代码,并不会经常开发
-
一定要熟悉java.lang.reflect包的作用,反射机制
-
如何取得一个类的属性、方法、构造器名称,修饰符等
测试:
public class Test08 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class<?> c1 = Class.forName("com.kuang.reflection.User");
User user=new User();
c1=user.getClass();
//1.获得类的名字
System.out.println(c1.getName()); //获得包名+类名
System.out.println(c1.getSimpleName()); //获得类名
//2.获得类的属性
System.out.println("========================");
Field[] fields = c1.getFields(); //只能找到public属性
fields=c1.getDeclaredFields(); //找到当前类的所有属性
for (Field field : fields) {
System.out.println(field);
}
/*
private int com.kuang.reflection.User.id
private java.lang.String com.kuang.reflection.User.name
private int com.kuang.reflection.User.age
*/
//找到指定属性
Field name = c1.getDeclaredField("name");
System.out.println(name);
//3.获得类的方法
System.out.println("========================");
Method[] methods = c1.getMethods(); //获得本类及其父类的所有public方法
for (Method method : methods) {
System.out.println("正常的:"+method);
}
methods = c1.getDeclaredMethods(); //获得本类的所有方法
for (Method method : methods) {
System.out.println("Declare:"+method);
}
Method getName = c1.getMethod("getName", null); //获取指定方法
Method setName = c1.getMethod("setName", String.class);
System.out.println("指定的:"+getName);
System.out.println("指定的:"+setName);
//4.获得类的构造方法
System.out.println("========================");
Constructor<?>[] constructors = c1.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("正常的:"+constructor);
}
constructors = c1.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("Declare:"+constructor);
}
Constructor<?> declaredConstructor = c1.getDeclaredConstructor(int.class, String.class, int.class);
System.out.println("指定的:"+declaredConstructor);
}
}
结果:
com.kuang.reflection.User
User
========================
private int com.kuang.reflection.User.id
private java.lang.String com.kuang.reflection.User.name
private int com.kuang.reflection.User.age
private java.lang.String com.kuang.reflection.User.name
========================
正常的:public java.lang.String com.kuang.reflection.User.toString()
正常的:public java.lang.String com.kuang.reflection.User.getName()
正常的:public int com.kuang.reflection.User.getId()
正常的:public void com.kuang.reflection.User.setName(java.lang.String)
正常的:public void com.kuang.reflection.User.setId(int)
正常的:public int com.kuang.reflection.User.getAge()
正常的:public void com.kuang.reflection.User.setAge(int)
正常的:public final void java.lang.Object.wait() throws java.lang.InterruptedException
正常的:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
正常的:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
正常的:public boolean java.lang.Object.equals(java.lang.Object)
正常的:public native int java.lang.Object.hashCode()
正常的:public final native java.lang.Class java.lang.Object.getClass()
正常的:public final native void java.lang.Object.notify()
正常的:public final native void java.lang.Object.notifyAll()
Declare:public java.lang.String com.kuang.reflection.User.toString()
Declare:public java.lang.String com.kuang.reflection.User.getName()
Declare:public int com.kuang.reflection.User.getId()
Declare:public void com.kuang.reflection.User.setName(java.lang.String)
Declare:public void com.kuang.reflection.User.setId(int)
Declare:public int com.kuang.reflection.User.getAge()
Declare:public void com.kuang.reflection.User.setAge(int)
指定的:public java.lang.String com.kuang.reflection.User.getName()
指定的:public void com.kuang.reflection.User.setName(java.lang.String)
========================
正常的:public com.kuang.reflection.User()
正常的:public com.kuang.reflection.User(int,java.lang.String,int)
Declare:public com.kuang.reflection.User()
Declare:public com.kuang.reflection.User(int,java.lang.String,int)
指定的:public com.kuang.reflection.User(int,java.lang.String,int)
2.6反射有了Class对象以后 能做什么?
1.创建对象
-
直接利用newInstance()创建一个对象,适用于存在无参构造
User user = (User) c1.newInstance();
-
反射获取构造器,进而创建对象,适用于有参构造
Constructor<?> constructor = c1.getDeclaredConstructor(int.class, String.class, int.class); User user2 = (User) constructor.newInstance(1, "kuangshen", 18);
2.反射调用类中方法
User user3=(User)c1.newInstance();
//通过反射获取一个方法
Method setName = c1.getDeclaredMethod("setName", String.class);
//invoke使用方法(对象,方法的参数)
setName.invoke(user3,"kuangshen2");
System.out.println("user3:"+user3);
3.反射操作属性
//3.利用反射操作属性
User user4=(User)c1.newInstance();
Field name = c1.getDeclaredField("name");
//不能直接操作私有属性,需要开启允许访问,关闭安全检测。方法、字段、构造器都可以设置开启允许访问
name.setAccessible(true);
//操作属性(对象,属性值)
name.set(user4,"kuangshen3");
System.out.println("user4:"+user4);
测试:
public class Test09 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//获得Class对象
Class<?> c1 = Class.forName("com.kuang.reflection.User");
//1.1直接利用newInstance()创建一个对象,仅适用于存在无参构造
User user = (User) c1.newInstance();
System.out.println("user:"+user);
//1.2反射获取构造器,进而创建对象,使用于有参构造
Constructor<?> constructor = c1.getDeclaredConstructor(int.class, String.class, int.class);
User user2 = (User) constructor.newInstance(1, "kuangshen", 18);
System.out.println("user2:"+user2);
//2.通过反射调用方法
User user3=(User)c1.newInstance();
//通过反射获取一个方法
Method setName = c1.getDeclaredMethod("setName", String.class);
//invoke使用方法(对象,方法的值)
setName.invoke(user3,"kuangshen2");
System.out.println("user3:"+user3);
//3.利用反射操作属性
User user4=(User)c1.newInstance();
Field name = c1.getDeclaredField("name");
//不能直接操作私有属性,需要开启允许访问,关闭安全检测。方法、字段、构造器都可以设置开启允许访问
name.setAccessible(true);
name.set(user4,"kuangshen3");
System.out.println("user4:"+user4);
}
}
setAccessible:
- Method和Field、Constructor对象都有setAccessible方法。
- 开启允许利用反射访问私有字段、方法
- 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,请设置为true
普通方法调用 和反射调用方法 性能对比:
public class Test10 {
//普通方法调用
public static void test01(){
User user=new User();
long startTime=System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long endTime=System.currentTimeMillis();
System.out.println("普通方式执行10亿次:"+(endTime-startTime)+"ms");
}
//反射调用方法
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user=new User();
Class<? extends User> c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName", null);
long startTime=System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long endTime=System.currentTimeMillis();
System.out.println("反射方式执行10亿次:"+(endTime-startTime)+"ms");
}
//反射调用方法
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user=new User();
Class<? extends User> c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName", null);
getName.setAccessible(true);
long startTime=System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long endTime=System.currentTimeMillis();
System.out.println("关闭权限检测方式执行10亿次:"+(endTime-startTime)+"ms");
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
test01();
test02();
test03();
}
}
4.反射操作获取泛型
-
Java采用泛型擦除机制 来引入泛型
Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,Java新增了ParameterizedType、GenericArrayType、TypeVariable和WildcardType几种类型来代表不能被归一到Class类中但是又能和原始类型齐名的类型。
-
ParameterizedType 表示参数化类型,比如Collection
-
GenericArrayType 表示一种元素类型是参数化类型或者类型变量的的数组类型
-
TypeVariable 是各种类型变量的公共父接口
-
WildcardType 代表一种通配符类型表达式
测试:
public class Test11 {
public void test01(Map<String,User> map, List<User> list){
System.out.println("test01");
}
public Map<String,User> test02(){
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method = Test11.class.getMethod("test01", Map.class, List.class);
//获得参数类型及其内部泛型
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("参数类型:"+genericParameterType);
if(genericParameterType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("参数类型内部泛型:"+actualTypeArgument);
}
}
}
//获得返回类型及其内部泛型
method = Test11.class.getMethod("test02", null);
Type genericReturnType = method.getGenericReturnType();
System.out.println("返回类型:"+genericReturnType);
if(genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("返回类型内部泛型:"+actualTypeArgument);
}
}
}
}
2.7反射读取注解
**ORM:**object relationship Mapping 对象关系映射
类对应表
属性对应字段
对象对应记录
要求:利用注解和反射完成类和表结构的映射关系
-
框架会在类里面定义大量的注解,通过反射去读取,生成相应的信息
-
假设数据库有一张表,可以通过注解去定义和表对应的类型,通过注解生成一些数据库的语言,然后进行增删改查,自动创建表
反射读取注解实例:
package com.kuang.reflection;
import java.lang.annotation.*;
import java.lang.reflect.Field;
//测试ORM:对象关系映射
//使用反射读取注解信息三步:
//1.定义注解
//2.在实体类中实用注解
//3.使用反射获取注解,一般都是现成框架实现,我们手动实现
public class Test12 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
//反射,Class对象可以获取类的全部信息
Class<?> c1 = Class.forName("com.kuang.reflection.Student");
//1.获得这个类的注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//2.获得类的指定注解,指出属性名称,只有一个属性value
TableKuang tableKuang = c1.getAnnotation(TableKuang.class);
System.out.println(tableKuang.value());
//2.获取类指定注解的值,指出属性名称,三个属性
Field name = c1.getDeclaredField("name");
FieldKuang fieldKuang = name.getAnnotation(FieldKuang.class);
System.out.println(fieldKuang.columnName()+"-->"+fieldKuang.type()+"-->"+fieldKuang.length());
//我们可以根据得到的类的信息,通过JDBC生成相关的SQL语句,执行就可以动态生成数据库表
}
}
@TableKuang("db_student")
class Student{
@FieldKuang(columnName = "db_id",type = "int",length = 10)
private int id;
@FieldKuang(columnName = "db_name",type = "varchar",length = 10)
private String name;
@FieldKuang(columnName = "db_age",type = "int",length = 3)
private int age;
public Student() {
}
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
//表名注解,只有一个参数,建议使用value命名
@Target(value={ElementType.TYPE})
@Retention(value= RetentionPolicy.RUNTIME)
@interface TableKuang{
String value();
}
//属性注解
@Target(value={ElementType.FIELD})
@Retention(value=RetentionPolicy.RUNTIME)
@interface FieldKuang{
String columnName();//列名
String type();
int length();
}