JVM 遇到了 new 对象之后做了什么?
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 的时候:
- 首先去常量池中看能否根据这个类的全路径找到这个类的信息,查看是否加载过,解析,初始化过。如果没有,则先进行类加载过程;
- 接下来JVM为新的 Person对象分配内存,对象所需的内存大小在类加载的时候就可以完全确定,因为一个对象具有什么属性,属性大小(int,byte,long等所占字节大小)都可以进行确定;
分配内存 = 从虚拟机设置的堆内存里面划分一块新对象所需的确定大小的内存
具体的划分方式根据各家垃圾收集器不同也采用不同的划分方式,主要两种:
方式一:指针碰撞(假设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;
new Person()
JVM堆、栈和方法区
局部变量 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 面试高频题,其实都是靠推导出来的,记永远是记不清的:
- 创建了几个对象?
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
网友评论