目录
JVM 遇到了 new 对象之后做了什么?
举个栗子
new Person()
从虚拟机内存推导String面试高频题答案
❝
对象的创建,内存布局以及访问定位,推导String必问面试题答案
❞
package com.kuaizhan.web.utils;
/**
* @author by zengzhiqin
* 2020-07-15
*/
class Person {
String name;
public void say(String name) {
System.out.println("hello " + name);
}
}
public class TestPerson {
public static void main(String[] args){
Person person;
person = new Person();
person.say("特朗普");
}
}
以此为例,结合之前说到的类生命周期来说(不懂的朋友可以看前面的文章 Java类的生命周期,不懂这个都不好意思和别人说我是搞JAVA的 ),当执行到 new Person 的时候:
首先去常量池中看能否根据这个类的全路径找到这个类的信息,查看是否加载过,解析,初始化过。如果没有,则先进行类加载过程;
分配内存 = 从虚拟机设置的堆内存里面划分一块新对象所需的确定大小的内存
具体的划分方式根据各家垃圾收集器不同也采用不同的划分方式,主要两种:
方式一:指针碰撞(假设JAVA堆内存是整整齐齐的,用过的站一起,没用过的站一起,那么在中间分界地方放个指针,每次新分配往没用过的那边移动一点就好了。这种方式需要垃圾收集器维护这个用过的和没用过的内存,因为运行过程中可能中间随时有使用过的内存被回收了就出现了缺口,需要垃圾收集器进行整理内存)
方式二:空闲列表(JAVA堆空闲内存之间是这里缺一块那里缺一块的,虚拟机维护一个空闲内存列表,记录下来哪些空闲哪些占用了,分配的时候从空闲列表里面找)
分配完内存,JVM 将拿到内存的数据类型都初始化为默认值;
JVM 对对象头进行设置,例如对象是哪个类的实例,如何找到类的元数据信息,对象hash 码,GC分代年龄信息等,都属于对象头信息;
”clinit“方法统一赋值对象的属性,前面第三步初始化是默认值,例如 int i = 4 d在第三步是初始化为int的默认值0,这里是将其赋值为你定义的4;
在栈中新建对象引用,并将其指向堆中新建的对象实例。
还是这段代码:
package com.kuaizhan.web.utils;
/**
* @author by zengzhiqin
* 2020-07-15
*/
class Person {
String name;
public void say(String name) {
System.out.println("hello " + name);
}
}
public class TestPerson {
public static void main(String[] args){
Person person;
person = new Person();
person.say("特朗普");
}
}
调用过程:
JVM去方法区寻找Person类信息
如果找不到,Classloader加载Person类信息进入内存方法区
在堆内存中创建Person对象,并持有方法区中Person类的类型信息的引用
把person添加到执行main()方法的主线程java调用栈中,指向堆空间中的内存对象
执行person.sayHello()时,JVM根据person定位到堆空 间的Person实例
根据Person实例在方法区持有的引用,定位到方法区 Person类型信息,获得sayHello() 字节码,执行此方法。执行,打印出结果。
注意:
当 new Person() 的时候,虚拟机做了两件事情:
在堆上产生一个实例,假设实例地址是 0x22;
将变量地址 0x11 指向堆实例的地址 0x22;
局部变量 person 其实就是一个reference引用,说白了就是一个类似 0x11 的地址,存在于局部变量表里面(局部变量表在栈区,不记得的朋友可以看看上一篇讲JVM内存分布的文章 Java跨平台根本原因,面试必问JVM内存模型白话文详解来了 )。
引用指向关系: 0x11 => 0x22 => 0x33
reference1 就是局部变量person,指向堆区的实例;然后 new Person() 指向方法区里面的 Person类元数据信息包括sayHello方法。
当调用sayHello的时候,步骤如下三步:
首先能拿到 person 也就是reference1,其地址是0x11;
0x11 地址指向堆里面的 new Person(),new Person 拥有方法区类存放地址假设 0x22;
0x22 指向的是方法区常量池的 Person类元数据信息,元数据信息里面是包括 sayHello() 方法的地址,最后完成调用。
String 面试高频题,其实都是靠推导出来的,记永远是记不清的:
创建了几个对象?
String str = "1" + "2" + "3";
答案:一个对象,编译时候会进行字符串折叠,算是一个优化,以前确实是四个对象,“1”,“2”,“3”,“123”
字符串折叠:如果是常量相加,通俗理解就是先加,然后去常量池找,有直接返回,没有就创建
打印结果?
String s1 = "hello";
String s2 = "world";
String s3 = "hello world";
System.out.print(s3 == "hello" + "world")
true,根据上面的分析,都是比较常量池的值,是同一个,true
System.out.print(s3 == s1 + s2);
false,变量相加,只要有一个变量,那么都要先给变量开空间,就成了地址比较
打印结果?
String str2 = new String("Trump"); 创建了几个对象?
String str1 = "Trump";
直接去常量池创建一个Trump,栈里面保存引用直接指向常量池”Trump“
String str2 = new String("Trump");
创建几个对象分情况(String 不可变):
1. 一个对象(如果常量池中已经存在”Trump”,堆里面创建个对象就可以,栈里面来个引用指向堆)
2. 两个对象(如果常量池中不存在”Trump“,先在常量池里面创建”Trump“,然后堆里面new一个对象,最后栈里面来个引用指向堆)
System.out.print(str1 == str2);
比较的栈里面的引用地址,指向的东西都不一样怎么可能是一样的,果断 false
System.out.print(str1.equals(str2));
比较的常量池里面的值,都是Trump, true
公众号下篇内容预告:
垃圾回收,JAVA程序员的福音呐
往期推荐:
Java跨平台根本原因,面试必问JVM内存模型白话文详解来了
从JVM设计者的角度来看.class文件结构,一文弄懂.class文件的身份地位
Java类的生命周期,不懂这个都不好意思和别人说我是搞JAVA的
欢迎批评指正,有收获的朋友点个在看或者分享鼓励一下吧,十分感谢~