JVM的内存模型
JVM内存是人为根据不同内存空间的存储特点以及存储的数据进行划分的。
程序计数器:当前线程所执行的字节码行号指示器
本地方法栈:为虚拟机使用的native方法服务
Java虚拟机栈:描述Java方法执行的内存模型,每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法会创建一个栈帧,栈帧存放了当前方法的数据信息(局部变量),当方法执行完毕栈帧就被销毁。
Java堆:被所有线程共享的一块内存区域,在虚拟机启动时创建,所有的对象实例及数组都要在堆上分配。(使用new关键字表示在堆中开辟一块新的存储空间)
方法区:线程共享的内存区域,存储已被虚拟机加载的类信息,常量,静态变量以及代码编译后的代码数据(字节码)。
记住一句话:有方法就有栈,有对象就有堆,有类就有方法区。
以下代码在JVM中的执行顺序是怎样的?
public void main(){
a();
b();
}
public void a(){}
public void b(){
c();
}
public void c(){}
一看到new关键字,就知道要在堆中创建对象,所以在堆中开辟内存。
定义int类型数组nums操作发生于栈中,位于虚拟机栈的栈帧中的局部变量表,以slot形式存储。
实际在栈中利用引用数据0x1234,操作堆中的对象,如果堆中对象所占的内存空间没有被引用,就会被垃圾回收处理。
public class test {
public static void main(String[] args)
{
int x =10;
System.out.println("main方法前x=" + x);//10
change(x);
System.out.println("main方法后x=" + x);//10
}
public static void change(int x)
{
System.out.println("change方法前x=" + x);//10
x = 50;
System.out.println("change方法后x=" + x);//50
}
}
没有new就不用在堆内存中操作,main方法将x复制一份给change方法,当change方法出栈后,main方法中的x还是原来的x=10没有改变。
public class test {
public static void main(String[] args)
{
int[] arr = new int[]{10,99};
System.out.println(arr[0] + "," + arr[arr.length -1]);//10,99
swap(arr);
System.out.println(arr[0] + "," + arr[arr.length -1]);//99,10
}
public static void swap(int[] arr)
{
int temp = arr[0];
arr[0] = arr[arr.length - 1];
arr[arr.length - 1] = temp;
}
}
new关键字会在堆中开辟一片内存区域,堆是线程共享的。当swap方法栈帧对0x1234内存区域修改之后退出栈,main方法栈帧指向的0x1234与swap中指向的0x1234是同一块内存区域,所以arr的引用已经被修改了。
public class Servant {
public String name;
public int age;
}
public class test {
public static void main(String[] args)
{
Servant s1 = new Servant();
s1.name = "Lily";
s1.age = 18;
Servant s2 = new Servant();
s2.name = "Lucy";
s2.age = 20;
s1 = s2;
}
}
对于堆中的对象,是利用栈中Renfence数据进行操作,访问方式有两种,句柄和直接指针(HotSpot采用直接指针)。上图改变引用数据指向的堆中的对象,对于原对象将等待被引用,new关键字是强引用,所以不会被垃圾回收掉。
1.static修饰的方法、字段和内部类是类级别的,而不是对象级别的。随所在类的加载而加载,当JVM把字节码加载进JVM的时候,static修饰的成员同时被加载至方法区中。
2.static修饰的成员要优先于对象,对象是我们手动通过new关键字创建出来的。
3.static修饰的成员被该类型的所有对象所共享。
表面上使用对象访问static成员,实际上编译后使用类名访问的。
public class Person {
String name;
int age;
static int totalNum=5;
public Person(String name, int age) {
this.name = name;
this.age = age;
totalNum++;
}
public void die(){
totalNum--;
System.out.println("死亡");
}
public void destroy(){
totalNum=0;
System.out.println("无了");
}
}
public class test {
public static void main(String[] args) {
System.out.println(Person.totalNum);
Person p1 = new Person("lik", 20);
System.out.println(Person.totalNum);
Person p2 = new Person("like", 22);
System.out.println(Person.totalNum);
p1.die();
System.out.println(Person.totalNum);
Bird bird = new Bird();
}
}
类成员与实例成员的访问:
类中的成员:字段,方法,内部类
类成员:使用static修饰的成员
实例成员:没有使用static修饰的成员
类成员只能访问类成员,实例成员只能访问实例成员(实例可以访问类成员,但是底层依然使用类名访问)。
private:只能在本类中访问,离开本类后不能直接访问
缺省:访问者的包必须和当前类所属包相同才可以访问
protected:表示子类访问权限,同包中的类可以访问,子类也可以访问。
public:当前工程任何地方都可以访问
在Java语言中,存在多个类的时候我们使用extends关键字表示子类和父类之间的关系。
在Java中,类和类之间的继承关系只允许单继承,不允许多继承,即A只能有一个直接的父类。继承解决了代码重复的问题。
1.如果父类的成员使用public修饰,子类继承
2.如果父类的成员使用protected修饰,子类也继承,即使父类和子类不在同一个包中
3.如果父类和子类在同一个包中,此时子类可以继承父类中缺省修饰符的成员
4.如果父类中的成员使用private修饰,子类打死都不继承,因为private只能在本类中访问。
5.父类的构造器子类不能继承,因为构造器必须和当前类名相同。
方法重写:override
子类重写父类中的方法,父类必须已经存在该方法。
方法重写和方法重载有什么区别?
方法重写和方法重载没有任何关系,只是名字类似而已。
方法重载:overload
作用:解决同一个类中功能相同,方法名不同不同的问题。(既然功能相同,方法名应该也相同)
规则:两同一不同
功能相同,方法名相同,方法参数列表不同(参数类型,参数个数,参数顺序)
方法重写:override
作用:解决子类继承父类之后,可能父类的某一个方法不满足子类的具体特征,此时需要在子类中重写定义该方法,并重写方法体。
在子类的方法中调用父类被覆盖的方法,使用super.方法名
子类初始化过程:创建子类对象的过程
在创建子类对象之前,会先创建父类对象。
调用子类构造器之前,在子类构造器中会首先调用父类构造器,默认调用父类无参数构造器。
如果父类不存在可以被子类访问的构造器,则不能存在子类。
public class Animal {
String name;
int age;
public Animal() {
System.out.println("animal构造器");
}
}
public class Bird extends Animal{
String eat;
public Bird() {
System.out.println("bird构造器");
}
}
public class test {
public static void main(String[] args) {
Bird bird = new Bird();
}
}
运行结果:
Animal a = new Dog();
对象具有两种类型:
编译类型:声明对象变量的类型。
运行类型:对象的真实类型。
编译类型必须是运行类型的父类或相同。
当编译类型和运行类型不同时多态就出现了。
所谓多态:对象具有多种形态,对象可以存在不同的形式。
如:
Animal a = null;
a = new Dog();//a此时表示Dog类型的形态
a = new Cat();//a此时表示Cat类型的形态
多态的前提:可以是继承关系(类和类),也可以是实现关系(接口和实现类),在开发中,多态一般指第二种。
多态的特点:把子类对象赋给父类变量,在运行时期会表现出具体的子类特征。
Q:多态有什么好处?
A:多态的好处:当不同子类的对象当做父类类型看待时,可以屏蔽不同子类之间的实现差异,从而写出通用代码达到通用编程,以适应不断变化的需求。
字段不存在多态
class SuperClass{
public String name = "Super";
public static void doWork(){
System.out.println("SuperClass doWork");
}
}
class SubClass extends SuperClass{
public String name = "Sub";
public static void doWork(){
System.out.println("SubClass doWork");
}
}
public class test {
public static void main(String[] args){
SuperClass s = new SubClass();
System.out.println(s.name);
s.doWork();
}
}
输出结果为
Super
SuperClass doWork
通过对象调用字段,在编译时期就决定了调用哪一块内存的数据,----字段不存在覆盖的概念,不能有多态特征。
当子类和父类存在相同字段的时候,无论修饰符是什么,都会在各自的内存空间中存储数据。