目录
一、JAVA的内存分析
内存概述
java的内存存储总共分为5大区域:堆、栈、方法区、本地方法区、寄存器区
其中本地方法区(存第三方语言-C/C++)和寄存器区(多线程资源)不用区分析,没法跟踪这两个区域
重点分析:堆,栈,方法区
堆:
存new对象,成员属性,常量池(放字符串常量,之前方法区,现在放堆区-性能考虑,但沿用了方法区特点)栈:
存局部变量方法区:
class资源,static资源; 特点:优先于对象的加载,且相同资源只维护一份
内存分析流程:
- 加载Test1.class与Person.class—方法区(class字节码文件)
- new对象开辟空间-------堆区
- 将地址赋值给引用变量person-------栈区
接下来,使用简单的面向对象案例,说明内存的执行过程
案例分析
//案例:小明在打游戏
//分析:类-Person,对象:new 属性-姓名,方法:打游戏
class Person{
String name;
public void play() {
System.out.println(name+"正在打游戏");
}
}
public class Test1 {
public static void main(String[] args) {
Person person = new Person();
person.name = "小明";
person.play();
}
}
二、静态-修饰属性
静态属性与成员属性
成员变量结论:
实例化多个对象,每个对象都有独立的成员属性静态变量结论:
实例化多个对象,共享同一份静态变量
成员变量 VS 静态变量
1. 存储位置:
- 静态变量-方法区
- 成员变量-堆
2. 所属资源:
- 静态变量-类
- 成员变量-对象
3. 执行顺序:
- 静态变量优先于成员变量进行加载;在类加载时,就已经加载了静态资源
4. 调用方式:
- 静态变量-推荐用类调
- 成员变量-对象调
//案例:女朋友帮忙洗衣服
class Girl{
//String name; //成员变量
static String name; //静态变量
public void wash() {
System.out.println("女朋友"+name+"正在帮忙洗衣服");
}
}
public class Test1 {
public static void main(String[] args) {
Girl lyf = new Girl();
Girl.name="刘亦菲"; //lyf.name = "刘亦菲";
Girl fj = new Girl();
Girl.name = "凤姐";
lyf.wash(); //凤姐
fj.wash(); //凤姐
}
}
场景-统计全局资源
说明: 在面向对象中,直接使用静态变量场景不多;但有些场景下,还是用得较多
例如:统计全局资源及作为状态值(静态常量)的使用
//案例:统计某个对象被new了多少次
//分析:实例化对象后,使用构造方法来记录次数
class A{
//int count; //初始为0 //成员变量:每个对象都是独立的一份
static int count; //静态变量:所有对象共享同一份
public A() {
count++; //每次new了之后,然后+1
}
}
public class Test2 {
public static void main(String[] args) {
new A();
new A();
new A();
System.out.println("次数:"+A.count);//3
}
}
场景-状态值应用
状态值 — 静态常量 static+final
-
final最终的,修饰变量,变为常量;所存的数值不可改变
-
往往这种静态常量可以用于状态值,也就是系统给定值(也可自定义),用于判断,不要改变它
-
好处:可读性很强,维护性很强
//案例:200响应码-成功 404-找不到访问页面 500-服务器报错
//模拟代码实现:
public class Test3 {
public static final int HTTP_OK = 200; //状态值
public static final int HTTP_ERR = 404;
public static final int SERVER_ERR = 500;
public static void main(String[] args) {
int repcode = getResponseCode();
//HTTP_OK = 400; final修饰的不能更改
switch (repcode) {
case HTTP_OK:
System.out.println("返回成功页面!");
break;
case HTTP_ERR:
System.out.println("访问页面未找到!");
break;
case SERVER_ERR:
System.out.println("服务器出现异常");
break;
default:
System.out.println("错误的响应码");
break;
}
}
private static int getResponseCode() {
//执行了很多后端操作
return HTTP_OK;
}
}
三、静态-修饰方法
特点
修饰方法时,在静态方法中不能使用成员变量
注意事项:
- 在静态方法中不能使用成员变量,因为加载时机问题-static优先加载,不认识成员
- 静态方法可以继承,但不能重写
- 静态方法中不能使用this或super关键字
class A{
String name; //成员变量
static int age;
public void eat() { //成员方法
System.out.println(name+"正在吃");
System.out.println(age); //在成员方法中,可以使用静态变量
}
public static void test() { //静态方法
//在静态方法中不能使用成员变量,因为加载时机问题-static优先加载,不认识成员
//System.out.println(name);
//System.out.println(this.name); //静态方法中不能使用this或super关键字
//静态方法可以继承,但不能重写
}
}
class B extends A{
/*@Override //静态方法不能重写
public static void test() {
}*/
}
public class Test1 {
public static void main(String[] args) {
B.test(); //静态方法具有继承性
}
}
场景-工具类
工具类的应用 — 通过类名直接调静态方法
好处:不需要通过new对象进行调用;如果需要多次调用,可节约内存
工具类的修饰符:
- 类:public + class 类名
- 方法:public static + 返回值类型 方法名(参数列表)
//例如系统提供的:System,Arrays都是工具类的用法
//System.arraycopy; Arrays.sort Array.copyOf Arrays.toString
class MyArrays{
public static void sort(int[] a) {
//冒泡排序:
for(int i=0;i<a.length-1;i++) {
for(int j=0;j<a.length-1-i;j++) {
if(a[j]>a[j+1]) {
int t = a[j];
a[j] = a[j+1];
a[j+1]= t;
}
}
}
}
}
public class Test2 {
public static void main(String[] args) {
int[] a = {2,6,4,8,3};
MyArrays.sort(a); //自定义工具类
System.out.println(Arrays.toString(a));
}
}
四、静态-修饰代码块
动态代码块
在类中写入**{}**,就是一个动态代码块,只要实例化一次对象,即可触发一次代码块
- 执行时机:属性赋值>动态代码块>构造方法
- 作用:可用于给属性赋值
//动态代码块应用:
class A{
String name = "成员属性";
public A() { //构造方法
System.out.println("构造方法");
}
//动态代码块
{
System.out.println(name);
System.out.println("动态代码块");
}
}
public class Test1 {
public static void main(String[] args) {
new A();
}
}
输出结果:
在Java中,属性赋值、动态代码块和构造方法的顺序是固定的。以下是它们的顺序:
1. 属性赋值: 在类中定义的属性会被赋予默认值,例如整数类型默认为0,布尔类型默认为false,引用类型默认为null。然后,按照代码中的顺序,属性会被赋予初始值。
2. 动态代码块(也称为实例初始化块): 动态代码块是在创建对象时执行的代码块。它们在构造方法之前执行,并且可以用于初始化实例变量。如果有多个动态代码块,它们会按照它们在类中出现的顺序依次执行。
3. 构造方法: 构造方法是用于创建对象并初始化其状态的特殊方法。它们在动态代码块之后执行。如果有多个构造方法,它们可以根据参数的不同进行重载。
总结起来,属性赋值发生在动态代码块和构造方法之前,而动态代码块在构造方法之前执行。这个顺序保证了对象在创建时属性得到适当的初始化。
类加载
类加载机制:JVM首次使用某个类,通过classpath找到.class文件,并将类资源加载到内存中
这些类资源包括:包名,类名,属性,方法,对象…
类加载的触发时机:run 配置–verbose
使用场景:
- 实例化对象
- 实例化子类对象
- 静态属性触发
- 静态方法触发
- 使用反射触发
案例:
class B{
static String name="静态属性";
public static void show() {
System.out.println("静态方法触发");
}
}
class C extends B{
}
public class Test2 {
public static void main(String[] args) throws ClassNotFoundException {
//new B(); //类加载只有一份;与实例化多次对象无关
//new B();
//new C(); //实例化子类对象
//System.out.println(B.name); //静态属性
//B.show(); //静态方法
Class.forName("com.qf.d_block.B"); //全限定名 反射应用
}
}
静态代码块
静态代码块: static{}
执行顺序:静态属性 > 静态代码块
作用:用于给静态属性赋值
案例:
class D{
static String name="静态属性";
static {
System.out.println(name);
System.out.println("静态代码块");
}
}
public class Test3 {
public static void main(String[] args) {
new D();
}
}
实例化过程
之前学习过实例化对象后,开辟了空间,并赋初值等过程;此处再加上静态代码块与动态代码块的执行
- 顺序:
静态属性>静态代码块>子类静态属性>子类静态代码块>成员属性
>动态代码块>构造方法>子类成员属性>子类动态代码块>子类构造方法
- 规则: 先静态后动态,先父类后子类
案例一:
//将静态代码块与动态代码块综合应用:
class Super{
String name = "成员属性";
public Super() { //构造方法
System.out.println("构造方法");
}
//动态代码块
{
System.out.println(name);
System.out.println("动态代码块");
}
static String name2="静态属性";
static {
System.out.println(name2);
System.out.println("静态代码块");
}
}
public class Test1 {
public static void main(String[] args) {
new Super(); //静态属性>静态代码块>成员属性>动态代码块>构造方法
System.out.println("---------------");
new Super(); //成员属性>动态代码块>构造方法 静态的只加载一次
}
}
输出结果:
案例二:
//实例化子类对象的执行过程:
class Son extends Super{
String name = "子类成员属性";
public Son() { //构造方法
System.out.println("子类构造方法");
}
//动态代码块
{
System.out.println(name);
System.out.println("子类动态代码块");
}
static String name2="子类静态属性";
static {
System.out.println(name2);
System.out.println("子类静态代码块");
}
}
public class Test2 {
public static void main(String[] args) {
new Son(); //实例化子类对象的过程
}
}
输出结果: