为了明白代理机制,需要先了解java的对象创建机制、反射等。
本节先看1.1.4然后在前面找相关的知识。
1、对象创建机制
参考文章:https://www.zhihu.com/question/24304289 java理解反射
https://baijiahao.baidu.com/s?id=1637836912223474691&wfr=spider&for=pc java创建对象的过程详解
https://www.cnblogs.com/pu20065226/p/12206463.html java类的初始化和实例化区别
1.1、java类的生命周期
1.1.1、过程
指一个class文件从加载到卸载的全过程,类的完整生命周期包括7个部分:加载——验证——准备——解析——初始化——使用——卸载,如下图所示
其中,验证——准备——解析 称为连接阶段,除了解析外,其他阶段是顺序发生的,而解析可以与这些阶段交叉进行,因为Java支持动态绑定(晚期绑定),需要运行时才能确定具体类型;在使用阶段实例化对象
加载:通过类名获取类的二进制字节流是通过类加载器来完成的。其加载过程使用“双亲委派模型”
验证:当一个类被加载之后,必须要验证一下这个类是否合法,比如这个类是不是符合字节码的格式、变量与方法是不是有重复、数据类型是不是有效、继承与实现是否合乎标准等等。总之,这个阶段的目的就是保证加载的类是能够被jvm所运行。
准备:为类变量(静态变量)在方法区分配内存,并设置零值。注意:这里是类变量,不是实例变量,实例变量是对象分配到堆内存时根据运行时动态生成的。
解析:把常量池中的符号引用解析为直接引用:根据符号引用所作的描述,在内存中找到符合描述的目标并把目标指针指针返回。
初始化:类的初始化过程是这样的:按照顺序自上而下运行类中的变量赋值语句和静态语句,如果有父类,则首先按照顺序运行父类中的变量赋值语句和静态语句在类的初始化阶段,只会初始化与类相关的静态赋值语句和静态语句,也就是有static关键字修饰的信息,而没有static修饰的赋值语句和执行语句在实例化对象的时候才会运行。执行<clinit>()方法(clinit是class initialize的简写)
实例化:在堆区分配内存空间,执行实例对象初始化,设置引用变量a指向刚分配的内存地址
1.1.2、运行时区内存分配
Mthod Area(方法区) | Heap Area(堆区) | Stack Area(栈区) |
1、方法区又名静态成员区,所有的类、静态变量、静态方法、常量、成员方法、构造方法。 2、方法区被所有的线程共享。所以存储的数据不是线程安全的。 | 1、所有的对象和它们相应的实例变量以及数组将被存储在这里:对象实例一般包含其数据成员以及该对象对应的class信息 2、一个JVM实例在运行的时候只有一个Heap区域,该区域被所有线程共享,所以存储的数据不是线程安全的 | 1、Stack区域属于线程私有,每个线程运行时都会创建一个运行时栈,Stack区域包含基本的数据类型(所有的局部变量在栈内存中创建)以及对象的引用,存放线程调用方法时存储局部变量表,操作,方法出口等与方法执行相关的信息。 2、分为三大部分,基本数据类型的变量区、执行环境上下文、操作指令区(存放操作指令) |
举例:
类A:
public class A {
private int m=2;
private String str1="youyou";
public final static String MESS="world";
static String ms="world";
public String getName(String input){
String temp=input;
return temp;
}
public static int getId(){return 0;}
}
test类
public class Test {
public static void main(String[] args) {
Class clazz=A.class;
A a=new A();
A a1=new A();
}
public void change(int i)
{
i=123;
}
}
Java中内存分成两种,一种叫做栈内存,一种叫做堆内存。还有一个方法区,也叫做静态区。
1.栈内存中放哪些东西?
①基本类型的变量,例如int a=3中的a;
②对象的引用变量,例如Thread t=new Thread();中的t。
当在代码块中定义一个变量时,Java就在栈中为这个变量分配内存空间;当超过变量的作用域后,Java会自动 释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。
2.堆内存中存放哪些东西?
① 存放由new创建的对象和数组。
在堆中存放的内存,由Java虚拟机垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量持有的内容等于数组或者对象在堆内存中的首地址。在栈中的这个特殊的变量,就成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。
3.静态区/方法区:
方法区(method)也叫做静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
方法区中包含的都是在整个程序中永远唯一的元素,例如class,static变量。
全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量放在相邻的另一块区域。
堆内存和栈内存,两者的区别?
①引用变量是普通变量,定义时在栈内存中分配,引用变量在程序运行到作用域外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在的代码块之外,数组和对象本身占用的堆内存也不会被释放。数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉,这个也是Java比较占内存的主要原因。实际上,栈中的引用变量指向堆内存中的变量,这就是Java中的指针。
②通俗来讲,堆是用来存放对象的,而栈是用来执行程序的。
③jvm只有一个堆区(heap),被所有线程共享;
每个线程包含一个栈区(stack),每个栈中的数据都是私有的,其他的栈不能访问,但是同一个栈中变量是共享的;分为3个部分:基本类型变量区,执行环境上下文,操作指令区。
为什么要有堆和栈?这样设计有什么好处?
①Java自动管理堆和栈,程序员不能直接地设置栈和堆。
②Java的堆是一个运行时数据区。堆是由JVM的垃圾回收器自动管理的。堆的优势是可以在程序运行时,动态地分配内存
大小,但是正是由于这个原因,它的存取速度较慢。
③栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小和生存期是必须确定的,缺乏灵活性。
栈有一个很重要的特性,就是存在栈中的数据可以共享。假设我们同时定义:
int a = 3;
int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。要注意这种数据的共享与两个对象的引用时
指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
1.1.3、初始化和实例化的区别
一个对象的创建过程包含两个过程:初始化和实例化。
- 在使用一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类的构造器完成类的初始化。在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化。初始化是完成程序执行前的准备工作。在这个阶段,静态的(变量,方法,代码块)会被执行。同时在会开辟一块存储空间用来存放静态的数据。初始化只在类加载的时候执行一次。
- 实例化,是指创建一个对象的过程。这个过程中会在堆中开辟内存,将一些非静态的方法,变量存放在里面。在程序执行的过程中,可以创建多个对象,既多次实例化。每次实例化都会开辟一块新的内存
执行次数 | 触发机制 | 是否执行构造方法 | 生命周期 | 执行内容 | |
初始化 (类级别) | 1 | Class.forName(),new,main方法的类,通过子类加载父类静态成员导致父类初始化。 | 否 | 第五步 | 1、为静态成员赋值 2、执行静态代码块 |
实例化 (实例对象) | n | new,Class.newInstance() | 是 | 第六步 | 1、在堆区分配内存空间 2、执行实例对象初始化 3、设置引用变量a指向刚分配的内存地址 |
1.1.4、对象的创建 (这一部分对应下一小节的反射)
一般理解的对象创建过程如下:
例如有个Person类
Person p = new Person();
整个创建对象的过程是下面这样的
javac Person.java
java Person
过程需要更细致点:
A a = new A();
1、将java文件编译为class文件
2、加载类(ClassLoader加载.class文件到内存。执行静态代码块和静态初始化语句)
3、执行new,申请一片内存空间
4、调用构造方法,创建一个空白对象
5、子类调用父类构造器
6、构造器执行(执行构造代码和初始化语句)
注:反射中的 Class.forName() 就是初始化对象,对应方法区。 在 .newInstance() 就是生成对象实例,对应堆区。