1. Full GC触发机制有如下5种情况。
- (1)调用System.gc()时,系统建议执行Full GC,但是不必然执行。
- (2)老年代空间不足。
- (3)方法区空间不足。
- (4)老年代的最大可用连续空间小于历次晋升到老年代对象的平均大小就会进行Full GC。
- (5)由Eden区、S0(From)区向S1(To)区复制时,如果对象大小大于S1区可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
2.大对象直接分配到老年代的几种方式
- 担保机制。minor gc后,survivor区空间不能容纳全部存活对象
- 大对象直接进入老年代。比如很长的字符串,或者很大的数组等。
- 长期存活的对象进入老年代。在堆中分配内存的对象,其内存布局的对象头中(Header)包含了 GC 分代年龄标记信息。如果对象在 eden 区出生,那么它的 GC 分代年龄会初始值为 1,每熬过一次 Minor GC 而不被回收,这个值就会增加 1 岁。当它的年龄到达一定的数值时(jdk1.7 默认是 15 岁),就会晋升到老年代中。
- 动态对象年龄判定。当 Survivor 空间中相同年龄所有对象的大小总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,而不需要达到默认的分代年龄。
3.java虚拟机中堆与栈的关系
堆中存放的是对象,栈帧中保存的是对象引用,这个引用指向对象在堆中的位置。
4.栈顶缓存(Top-of-Stack Cashing,ToS)技术
HotSpot虚拟机的设计者提出了栈顶缓存(Top-of-Stack Cashing,ToS)技术。所谓栈顶缓存技术就是当一个栈的栈顶或栈顶附近元素被频繁访问,就会将栈顶或栈顶附近的元素缓存到物理CPU的寄存器中,将原本应该在内存中的读、写操作分别变成了寄存器中的读、写操作,从而降低对内存的读、写次数,提升执行引擎的执行效率。
5.栈帧的内部结构
- 局部变量表(Local Variables)。
局部变量表最基本的存储单元是slot(变量槽)。局部变量表中存放编译期可知的各种基本数据类型(8 种)、引用(reference)类型、return Address类型的变量。
- 操作数栈(Operand Stack)(或表达式栈)。
- 动态链接(Dynamic Linking)(或指向运行时常量池的方法引用)。
- 方法返回地址(Return Address)(或方法正常退出或异常退出的定义)。
- 一些附加信息。例如,对程序调试提供支持的信息。
6、为对象分配内存:线程本地分配缓存区 TLAB(Thread Local Allocation Buffer,TLAB)
TLAB表示JVM为每个线程分配了一个私有缓存区域,这块缓存区域包含在Eden区内。简单说TLAB就是在堆内存中的Eden区分配了一块线程私有的内存区域。
(1)从内存模型角度来看,新生代区域继续对Eden区域进行划分,JVM为每个线程分配了一个私有缓存区域。
(2)多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称为快速分配策略。
(3)所有Open JDK衍生出来的JVM都提供了TLAB的设计。
为什么有TLAB呢?原因如下。
(1)堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据。
(2)由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的。
(3)为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度。
7 使用“+”或String类的concat()方法拼接字符串是否存在字符串常量池中
(1)字符串常量池中不会存在相同内容的字符串常量。
(2)字面常量字符串与字面常量字符串的“+”拼接结果仍然在字符串常量池。
(3)字符串“+”拼接中只要其中有一个是变量或非字面常量,结果不会放在字符串常量池中。
(4)凡是使用concat()方法拼接的结果也不会放在字符串常量池中。
(5)如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。
8. 对象创建的6种方式
使用new关键字、Class的newInstance()方法、Constructor类的newInstance()方法、clone()方法、反序列化、第三方库Objenesis。
每种创建对象方式的实际操作如下。
- 使用new关键字—调用无参或有参构造器创建。
- 使用Class的newInstance()方法—调用无参构造器创建,且需要是public的构造器。
- 使用Constructor类的newInstance()方法——调用无参或有参、不同权限修饰构造器创建,实用性更广。
- 使用clone()方法——不调用任何参构造器,且对象需要实现Cloneable接口并实现其定义的clone()方法,且默认为浅复制。
- 使用反序列化——从指定的文件或网络中,获取二进制流,反序列化为内存中的对象。
- 第三方库Objenesis——利用了asm字节码技术,动态生成Constructor对象。
9.对象创建的字节码解读
各个指令的含义如下。
new:首先检查该类是否被加载。如果没有加载,则进行类的加载过程;如果已经加载,则在堆中分配内存。对象所需的内存的大小在类加载完成后便可以完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。这个指令完毕后,将指向实例对象的引用变量压入虚拟机栈栈顶。
dup:在栈顶复制该引用变量,这时的栈顶有两个指向堆内实例对象的引用变量。
invokespecial:调用对象实例方法,通过栈顶的引用变量调用<init>方法。<init>是对象初始化时执行的方法,而<clinit>是类初始化时执行的方法。
从上面的四个步骤中可以看出,需要从栈顶弹出两个实例对象的引用。这就是为什么会在new指令下面有一个dup指令。其实对于每一个new指令来说,一般编译器都会在其下面生成一个dup指令,这是因为实例的初始化方法(<init>方法)肯定需要用到一次,然后第二个留给业务程序使用,例如给变量赋值、抛出异常等。如果我们不用,那编译器也会生成dup指令,在初始化方法调用完成后再从栈顶pop出来。