一.Java 内存区域(运行时数据区)
- 堆(Java堆):储存对象
- 栈:
- 虚拟机栈:储存局部变量,栈信息(方法出口)
- 本地方法栈:调用Native方法的栈信息,类似于虚拟机栈
- 方法区:储存类信息,静态变量,常量,及时编译器编译后的代码等。
- 其他:程序计数器,直接内存等。
二.Java 对象的创建过程
- 类加载检查
如果没有加载类,则先加载类,(类加载过程:加载(读数据)、验证、准备(分配内存)、解析(符合引用换成直接引用)、初始化(init方法,执行各种类变量赋值及静态语句块))。 - 分配内存
- 对象占用内存大小是确定的
- 分配内存有“指针碰撞”和“空闲列表”两种,取决于垃圾收集器是否有整理功能。
- 内存分配并发问题:要保证线程安全,一般通过Cas操作来分配内存,如果开启了TLAB会给每个线程预先分配一块内存。
- 初始化零值
- 设置对象头
这个对象是哪那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等。 - 执行init方法
三.对象访问定位的两种方式
- 句柄
栈中存的是句柄地址,堆中有一个句柄池,句柄池中存了对象实际地址和对象类型的指针。好处是对象移动(GC?)的时候,不需要改变栈中的refrence值(句柄池应该不需要经常移动整理)。坏处是访问的时候多了一次指针定位开销。实际上就是中间加了一层进行了解耦。 - 直接指针
栈中存的就是对象在堆中的地址。这种方式访问对象比较快,但是在对象移动的时候需要修改栈中的引用。
四.String类和常量池相关问题
- String str1 = "abc"; String str2 = new String("abc"); assert str1!=str2;
因为str1是在常量池中的对象,str2是堆中的对象,所以不相等。 - String str1 = "abc"; String str2 = new String("abc"); String str3 = str2.intern();
assert str1==str3; assert str2!=str3;
因为str3和str1都是常量池中的对象。 - String str1 = "abc"; String str2 = "def"; String str3 = "abc"+"def"; String str4 = str1+str2; String str5 = "abcdef" assert str3==str5; assert str3!=str4;
因为str3和str5都是常量池中相同对象。
五.Jvm中堆的划分
一共分为三个区:
- 新生代:主要是新对象和短生命周期对象。又划分为Eden区和两个幸存区,每次GC会把Eden区和一个幸存区中的存活对象挪到另外一个幸存区。
- 老年代:主要是大对象和存活时间长的对象。
- 永久代:存放类加载信息等(MaxPermSize)
六.什么时候对象会进入老年区?内存的分配与回收策略)
- 大对象会直接进入老年区(可配置)。
- 对象年龄超过设置的阈值。
- 如果Survivor空间中相同年龄所有对象大小总和大于Survior空间的一半,年龄大于或等于该年龄的对象就直接进入老年代。这个规则比较特别,可以理解成为了让对象进入老年代的过程更加平滑和均匀(如果没有这个规则,然后很多对象的年龄都一样的话,可能会出现某次minorGC过后大量的对象同时进入老年区)。
- minorGC的时候发现幸存区容量不够,则部分对象会进入老年区。
七.哪些情况下会minorGC,哪些情况下会majorGC?
1.minorGC:分配对象的时候新生代不够用了。
2.majorGC:
a. 老年代空间不足
b. 方法区空间不足
c. minorGC时,老年区连续内存大小如果小于新生代对象总和,且设置不允许担保分配失败,就会进行majorGC(因为minorGC的时候对象可能会进入老年区,需要确保老年区空间足够)(空间分配担保失败)。
d. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存。
八.描述GC进行过程。
主要有两种:标记 - 清理 和 标记 - 整理。
标记的过程:通过OopMap(了解Safe Region)找到GCRoot(主要是栈和方法区内的对象,Class,Thread,各种局部变量),找到各个引用的对象进行标记。标记的时候需要stop the world(否则活着的对象可能被回收),CMS采用了“初始标记”——“并发标记”——“重新标记”的方式进行了优化。
九.ClassLoader 双亲委派模型
主要有3种ClassLoader:
- BootstrapClassLoader,加载Java种最基本的类,无法被程序引用到,加载JAVA_HOME/lib下的类
2.ExtensionClassLoader,加载JAVA_HOME/lib/ext中的类
3.ApplicationClassLoader,加载用户路径下的类
十.静态分派和动态分派
- 静态分派
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,其典型应用是方法重载(根据参数的静态类型来定位目标方法)。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机执行的。 - 动态分派
在运行期根据实际类型确定方法执行版本。方法重写(@Override)。