【知晓的丧day拯救计划】java基础学习笔记补充 JVM内存模型、垃圾回收机制

笔记来自2019求知讲堂零基础Java入门编程视频教程 https://www.bilibili.com/video/av76235341

JVM的内存模型

JVM的内存模型

  • 栈:
    ①栈描述的是方法执行的内存模型,每个方法被调用时都会创建一个栈帧,用来存储局部变量、操作数方法出口等。
    ②JVM为每个线程创建一个栈,用于存放该线程执行方法的信息
    ③栈是私有的,线程之间不共享
    ④栈的存储特性:先进后出,后进先出
    ⑤是一个连续的内存空间,由系统自动分配,速度快。


  • ①堆用于存放创建好的对象和数组(数组也是对象)
    ②JVM只有一个堆,被所有线程共享
    ③堆是一个不连续的内存空间,分配灵活,速度慢

  • 方法区
    ①JVM只有一个方法区,被所有线程共享
    ②方法区实际上也堆
    ③用来存放程序中不变或唯一的内容

java类实例化的内存分析

简单类对象的实例化过程

这里有一个Person类

public class Person {
	public Person() {
		
	}
	
	int age = 1;
	String name = "zhangsan";
	int sex = 0;
	
	public void showInfo() {
		System.out.println("年龄:" + this.age);
		System.out.println("姓名:" + this.name);
		System.out.println("性别:" + this.sex);
	}
	
	public void setInfo(int age, String name, int sex) {
		this.age = age;
		this.name = name;
		this.sex = sex;
	}
}

下图描述了在执行Person p = new Person();时,JVM内存上是如何进行实例化的。
在这里插入图片描述
①我们要进行Person类的实例化,所以第一步是将Person.class加载到方法区
②实例化的变量名称是p,在栈中开辟空间,声明这个变量(此时变量还没有被赋值)
③执行到new Person()创建对象,我们知道所有对象时存放在堆内存中的,所以在堆内存中开辟空间,分配地址。
④在这个对象空间中先进行默认初始化,再进行显示初始化(如果有的话)。
⑤构造方法进栈(java的方法是运行在栈上的,调用时进栈,方法结束出栈),按照方法体内代码进行初始化。
⑥初始化结束后,将对象在堆内存中的地址赋给栈内存中的变量p,构造方法出栈。

子类对象的实例化过程

这里有一个Person类和Student类

public class Person {
	public Person() {
		int age = 100;
	}
	
	int age = 1;
	String name = "zhangsan";
	int sex = 0;
}

public class Student extends Person{
	public Student(){
		super();
		int age = 500;
	}
	String school;

}

下图描述了在执行Student stu = new Student();时,JVM内存上是如何进行实例化的。
在这里插入图片描述
①在方法区加载class文件,先加载父类,后加载子类
②在栈中申请空间,声明变量stu
③在堆内存中为new Student()开辟空间分配地址
④在对象空间中,对属性进行默认初始化(包括子类和父类的属性),此时age、sex=0,school、name=null
⑤子类构造方法进栈,此时发现子类构造方法第一行调用了父类的构造方法,所以需要先执行父类构造方法
⑥父类构造方法进栈之前需要先对父类的属性进行显示初始化,所以此时age = 1
⑦父类构造方法进栈,执行初始化代码,此时age = 100
⑧父类构造方法执行完毕后,继续执行第⑤步子类构造方法之前需要先对子类属性进行显示初始化(这里没有显示初始化,age = 100),然后继续执行子类构造方法,age= 500
⑨初始化完毕之后,将对象在堆内存中的地址赋给栈内存中的变量stu,构造方法出栈。

一个乱七八糟的测试例子

主要测试:
①子类继承父类时,若子类重写了父类的方法,父类的方法就完全被覆盖了;成员变量则不会出现这样的情况,即使子类定义了和父类相同的成员变量,这个变量不会覆盖父类中的。
②子类对象的实例化过程

public class Person {
	
	public Person() {
		System.out.print("这是在Person类构造函数内的输出,");
		System.out.print("此时age=" + this.age);
		this.show();
		System.out.println("对象是地址" + System.identityHashCode(this));
		age = 100;
	}
	
	int age = 1;
	String name = "zhangsan";
	int sex = 0;
	
	public void show() {
		System.out.print("这是在Person类构造函数中调用的方法,");
		System.out.println("此时age=" + age);
	}
}

public class Student extends Person{

	
	public Student(){
		super();
		System.out.print("这是在Student类构造函数内super()之后的输出,");
		System.out.println("此时age=" + age);
		this.show();
		super.show();
		age = 500;
	}
	int age = 1000;
	String school;
	
	public void show() {
		System.out.print("这是在Student类构造函数中调用的方法,");
		System.out.print("此时age=" + this.age);
		System.out.print("此时name=" + name);
		System.out.println("父类的age=" + super.age);
	}
}

public class Test {
	public static void main(String[] args) {
		Student stu = new Student();
		System.out.println(stu.age);
		System.out.println(System.identityHashCode(stu));
	}
}
/* 运行结果
这是在Person类构造函数内的输出,此时age=1这是在Student类构造函数中调用的方法,此时age=0此时name=zhangsan父类的age=1
对象是地址1361960727
这是在Student类构造函数内super()之后的输出,此时age=1000
这是在Student类构造函数中调用的方法,此时age=1000此时name=zhangsan父类的age=100
这是在Person类构造函数中调用的方法,此时age=100
500
1361960727 	
 */

根据上面子类实例化过程和运行结果,整理这段代码的运行情况:

①首先对父类和子类的属性进行默认初始化,此时父类的age、sex = 0,name = null,子类的age = 0,school = null;

②子类构造方法进栈,发现第一行调用了父类的构造方法;

③执行父类构造方法之前,先对父类的属性进行显示初始化,这里有定义,所以age = 1,name = “zhangsan” ,sex = 0 ;

④执行父类构造方法

//父类构造方法
public Person() {
		System.out.print("这是在Person类构造函数内的输出,");
		//这里的输出age=1,是父类构造方法执行前的显示初始化的结果,说明属性没有被子类同名属性的值影响
		System.out.print("此时age=" + this.age);
		//此处虽为this.show(),从运行结果看,实际调用的是子类的show()
		this.show();
		//此处输出this所指代的对象地址,发现就是子类对象stu的地址,所以上面调用的方法也是子类的方法,体现了子类对父类方法的完全覆盖。
		System.out.println("对象是地址" + System.identityHashCode(this));
		age = 100;
	}

⑤父类构造方法中调用的show()方法(从运行结果看,调用的是子类的show()方法)

public void show() {
		System.out.print("这是在Student类构造函数中调用的方法,");
		//子类和父类定义了相同的属性不会相互影响,这时的子类属性只经过了默认初始化,所以age = 0;
		System.out.print("此时age=" + this.age);
		//根据继承关系,从父类继承了name属性,所以name = "zhangsan"
		System.out.print("此时name=" + name);
		//super.age 指父类中的age属性,所以值为1
		System.out.println("父类的age=" + super.age);
	}

⑥父类构造方法执行结束,子类构造方法继续执行之前对子类属性显式初始化,此时age= 1000;

⑦执行子类构造方法

public Student(){
		super();
		System.out.print("这是在Student类构造函数内super()之后的输出,");
		//子类属性显示初始化之后,所以age = 1000
		System.out.println("此时age=" + age);
		//调用子类show()方法⑧
		this.show();
		//调用父类show()方法⑨
		super.show();
		age = 500;
	}

⑧子类构造方法中的this.show();

public void show() {
		System.out.print("这是在Student类构造函数中调用的方法,");
		//age = 1000
		System.out.print("此时age=" + this.age);
		//name = "zhangsan"
		System.out.print("此时name=" + name);
		//父类构造方法执行完毕,所以age = 100
		System.out.println("父类的age=" + super.age);
	}

⑨子类构造方法中的super.show();

public void show() {
		System.out.print("这是在Person类构造函数中调用的方法,");
		//age = 100
		System.out.println("此时age=" + age);
	}

⑩对象创建完毕,执行Test类中的输出语句,最终age = 500。

垃圾回收机制(Garbage Collection)

C++等语言的垃圾回收是由程序员手动进行的,Java的垃圾回收是由系统自动进行的。

  • 内存管理:java的内存管理很大程度指的是堆中的对象的管理,其中包括对象空间的分配和释放。
    对象空间的分配:使用new关键字创建对象
    对象空间的释放:将对象赋值为null即可。垃圾回收器将负责回收所有“不可达”对象的额内存空间。

  • 垃圾回收过程
    1.发现无用对象(没有被引用的对象)
    2.回收无用对象占用的内存空间。

  • 垃圾回收相关算法
    1.引用计数法:每个对象对应一个引用计数器,当有引用指向这个对象,计数器加1,当指向该对象的引用失效,计数器减1,当计数器的值为0时,Java垃圾回收器认为该对象无用,并对其进行回收。该方法优点是算法简单,缺点是无法识别“循环引用的无用对象”。

2.引用可达法(根搜索法):程序把所有引用关系看作一张图,从一个节点GC ROOT开始,寻找对应引用节点,找到这个节点后,继续寻找这个节点的引用节点,当所有节点寻找完毕之后,剩下的节点则被认为是没有被引用到的节点,即无用节点。

分代垃圾回收机制

不同的对象的生命周期是不同的,因此不同生命周期的对象可以采用不同的回收算法,以提高回收效率。将对象分为三种状态:年轻代、年老代、永久代。同时将处于不同状态的对象放到堆中的不同区域。JVM将堆内存分为Eden、Survivor和Tenured/Old空间。

  • 年轻代(Eden、Survivor):年轻代的目的是尽可能快速的收集掉生命周期短的对象,对应的是Minor GC,Minor GC采用效率较高的算法,频繁的进行操作。所有新生成的对象首先都放在Eden区,Eden区满了之后进行回收,仍然存活的对象就存放到Survivor区域,然后在Survivor1、Survivor2中循环的进行回收,超过15次回收仍然存活的对象将被放到年老代。
  • 年老代:在年轻代中经历N(默认15)次垃圾回收后仍然存在的对象,就会被放入年老代区域中。因此年老代中存放的都是一些生命周期较长的对象。年老代对象越来越多,就需要启动Major GC和Full GC,进行一次大扫除,全面清理年轻代和年老代区域。
  • 永久代:用于存放静态文件,如Java类,方法等。持久代对垃圾回收没有显著影响。JDK7以前,就是方法区的一种实现。JDK8以后没有永久代了,使用metaspace元数据数据空间和堆代替。

注:
程序员无权调用垃圾回收器,调用System.gc()只是申请启动Full GC垃圾回收。
Finalize方法时Java提供给程序员用来释放对象或资源的方法,但尽量少用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于C++&OPENCV 的全景图像拼接 C++是一种广泛使用的编程语言,它是由Bjarne Stroustrup于1979年在新泽西州美利山贝尔实验室开始设计开发的。C++是C语言的扩展,旨在提供更强大的编程能力,包括面向对象编程和泛型编程的支持。C++支持数据封装、继承和多态等面向对象编程的特性和泛型编程的模板,以及丰富的标准库,提供了大量的数据结构和算法,极大地提高了开发效率。12 C++是一种静态类型的、编译式的、通用的、大小写敏感的编程语言,它综合了高级语言和低级语言的特点。C++的语法与C语言非常相似,但增加了许多面向对象编程的特性,如类、对象、封装、继承和多态等。这使得C++既保持了C语言的低级特性,如直接访问硬件的能力,又提供了高级语言的特性,如数据封装和代码重用。13 C++的应用领域非常广泛,包括但不限于教育、系统开发、游戏开发、嵌入式系统、工业和商业应用、科研和高性能计算等领域。在教育领域,C++因其结构化和面向对象的特性,常被选为计算机科学和工程专业的入门编程语言。在系统开发领域,C++因其高效性和灵活性,经常被作为开发语言。游戏开发领域中,C++由于其高效性和广泛应用,在开发高性能游戏和游戏引擎中扮演着重要角色。在嵌入式系统领域,C++的高效和灵活性使其成为理想选择。此外,C++还广泛应用于桌面应用、Web浏览器、操作系统、编译器、媒体应用程序、数据库引擎、医疗工程和机器人等领域。16 学习C++的关键是理解其核心概念和编程风格,而不是过于深入技术细节。C++支持多种编程风格,每种风格都能有效地保证运行时间效率和空间效率。因此,无论是初学者还是经验丰富的程序员,都可以通过C++来设计和实现新系统或维护旧系统。3

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值