Java与C++的区别?
Java有自动内存管理,C++没有。
运行时数据区
- 程序计数器
作用:执行Java方法,存放正在执行的字节码指令地址;执行本地方法,则为Undefined。
大小:很小,存放正在执行的字节码指令地址。
异常:存储内容固定,没有任何异常。
线程私有:是线程私有的。
注意:
程序计数器与线程的上下文切换有关,进行上下文切换时,工作内容保存在程序计数器中。所以程序计数器必须是线程私有的。
- Java虚拟机栈
作用:代表Java方法执行的内存模型,方法执行入栈,方法结束出栈,代表一个线程执行的所有方法过程。
大小:动态可扩展
异常:StackOverflowError和OutOfMemoryError
线程私有:是线程私有的
注意:
栈帧
作用:一个栈帧代表一个方法的执行,存放局部变量表,操作数栈和动态链接等。
大小:编译期确定。
异常:无。
线程私有:是线程私有的。
局部变量表存放各种局部变量,64位变量占用两个slot,其它一个;使用下标代表局部变量在代码中的位置,从0开始,一般0代表this。
操作数栈:存放各种操作数以及中间结果。
- Java堆
作用:存放对象实例。
大小: 动态扩展
异常: OutOfMemoryError
线程私有: 不是线程私有,是线程共享的
注意:
Java堆是垃圾回收的主要区域,因为存放各实例对象,回收价值大。
Java堆可以划分为新生代和老年代,新生代可以划分为Eden区和From Survivor和To Survivor。
控制参数: -Xmx 最大值;-Xms 最小值。
- 方法区
作用:存放常量,静态变量,类信息以及即时编译后的代码,是堆的逻辑区域。
大小:可扩展。
异常:OutOfMemoryError
线程私有:不是线程私有,是线程共享的。
注意:
字符串常量池已经从方法区中移出。
JDK1.8以前使用永久代来进行垃圾回收,1.8之后使用元方法区替代。
- 运行时常量池
作用:存放各种字面量和符号引用。
大小:动态扩展
异常:OutOfMemoryError
线程私有:不是线程私有,是线程共享的。
Java对象的创建
- 首先在常量池中,能否定位到符号引用,并检查是否加载解析初始化过,不通过则执行加载解析初始化。
- 检查通过,为对象分配内存。
如果内存规整,使用指针碰撞,将指针移动对象所需空间大小即可。
内存不规整,使用空闲列表法,维护所有空闲的空间,从中查找符合大小的空间。
并发安全可以使用本地线程分配缓冲,为每个线程分配独立的空间,在自己的空间中创建对象。
也可以使用CAS保证线程安全。
- 分配完成,初始化零值。
- 设置对象头。
- 执行方法。
对象内存布局
分为三部分,对象头,示例数据和对其填充。
对象头
对象头中第一部分存储自身运行时数据,称为Mark Word
状态 | 25bit hashCode | 4bit 年龄分代 | 1bit 是否偏向锁 | 2bit 锁标记 |
---|---|---|---|---|
无锁 | hashCode | age | 0 | 01 |
状态 | 25bit hashCode 4bit 年龄分代 1bit 是否偏向锁 | 2bit 锁标记 |
---|---|---|
GC | 空 | 11 |
轻量级锁 | 指向栈中锁记录的指针 | 00 |
重量级锁 | 指向重量锁的指针 | 10 |
状态 | 23bit hashCode | 2bit Espoh | 4bit 年龄分代 | 1bit 是否偏向锁 | 2bit 锁标记 |
---|---|---|---|---|---|
偏向锁 | 指向偏向的线程Id | Espoh | age | 1 | 00 |
对象头第二部分存储类型指针,来确定是哪个类的实例。
如果是数组,还要有第三部分存放数组长度。
实例数据
存放各种类型的字段内容,父类的在前,子类在后,相同宽度的字段放在一起。
对象的访问定位
句柄池
优势是对象实例指针发生变化,栈中局部变量表到句柄池的引用不会变化,始终保持稳定。
缺点是需要两次指针定位。
直接指针
优势是只需要一次访问定位,速度快。