1。Java对象的创建过程
- 类加载检查:遇到一条new指令的时候,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,必须先执行相应的类加载过程。
- 分配内存:在类加载检查通过后,接下来虚拟机会为新生对象分配内存。分配方式有“指针碰撞”和“空闲列表”两种,选择哪种分配方式由Java堆是否规整决定。在创建对象的时候有一个很重要的问题,就是线程安全。虚拟机采用两种方式来保证线程安全:CAS+失败重试;TLAB
- 初始化零值:内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)
- 设置对象头:初始化零值完成后,虚拟机要对对象进行必要的设置,例如这个对象是哪各个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象头中。
- 执行init方法:执行new指令后会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
2.对象的访问定位有哪两种方式?
使用句柄:如果使用句柄的话,那么Java堆中将会划分出一块内存作为句柄池,句柄中包含了对象实例数据与类型数据各自的具体地址信息;
直接指针:如果使用直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接指针就是对象的地址。
使用句柄访问的最大好处是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。使用直接指针访问方式的最大的好处就是速度快,他节省了一次指针定位的时间开销。
3.堆内存中对象的分配的基本策略
对象优先在Eden区分配:
目前主流的垃圾回收集器都会采用分代回收算法,因此需要将堆内存分为新生代和老年代。
大多数情况下,对象在新生代Eden区分配。当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC
大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)
为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率
长期存活的对象将进入老年代
如果对象在Eden出生并经过Minor GC后仍然能够存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1.对象在Survivor中每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中,对象晋升到老年代的年阈值,可以通过参数 -XX:Max Tenuring Threshold设置。
动态对象年龄判定
4.如何判断对象是否死亡(两种方法)
- 引用计数法
- 可达性分析算法
5.如何判断一个常量是废弃常量?
如果没有任何对象引用该常量的话,就说明该常量为废弃常量
6.如何判断一个类是无用的类
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收
- 该类对应的java.langClass对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法