1、在JDK1.6(HotSpot虚拟机)及之前,运行时常量池(属于方法区的一部分)是永久代的,而在JDK1.7之后运行时常量池(里面用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池当中存放)已经从永久代(Permanent Generation)移出。(问:那么方法区其他部分有没有移出永久代?)
2、关于String.intern()方法,在1.6及以前会把首次遇到的字符串实例复制到永久代当中去,返回的也是永久代中这个字符串实例的引用;在1.7之后intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,返回的就是那个实例引用。
3、Java的JVM并不是通过引用计数来进行GC,因为它很难解决对象之间相互循环引用的问题,譬如:
public class Test{ public Object instance = null; public static void testGC(){ Test objA = new Test(); Test objB = new Test(); objA.instance = objB; objB.instance = objA; objA=null; objB=null; //假设在这行发生了GC,objA 和 objB能否被回收? System.gc(); } }
虽然这两个对象都已经不可能再被访问了,但是由于他们相互引用这对方,导致它们的引用计数都不为0,于是引用计数算法无法通知GC收集器回收它们。
4、Java采用可达性分析来判断对象是否存活,一般通过”GC ROOTS“的对象来作为起始点,一般可作为“GC roots”的对象包括下面4种:
1)虚拟机栈(栈帧中的本地变量表)中引用的对象。
2)方法区中类静态属性引用的对象。
3)方法区中常量引用的对象。
4)本地方法栈中JNI(即一般说的Native方法)引用的对象。
5、JDK1.2之后Java对引用的概念进行了扩充,分为四种,引用强度依次从强到弱为强引用>软引用>弱引用>虚引用
强引用是普通的类似 Object obj = new Object()的对象引用
软引用(通过SoftReference类来实现)用来描述一些还有用但不是必须的对象,当发生内存溢出异常之前,将会把这一类对象列入回收范围之中进行第二次回收,如果这次回收之后还是不够,就报内存溢出异常。
弱引用(WeakReference类来实现)也是用来描述非必须对象,但它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前
虚引用(PhantomReference)最弱的一种引用关系,无法通过虚引用来取得一个对象实例,一般为对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收的时候收到一个系统通知。
6、Class文件当中的一些数据项目:
常量池容量计数值(constant_pool_count),用于表示常量池中常量的数值。需要注意的是这里容量计数是从1开始的,因为0有特别用途,比如0x0016表示十进制的22,这就代表常量池中有21项常量,索引范围为1~21.
常量池中主要存放两大类常量:字面量(如文本字符串,声明为final的常量值) 和 符号引用 (属于编译原理方面的概念,包括三类常量 见P168)
7、类从被加载到虚拟机内存到卸载出内存为止,整个生命周期包括哪些阶段?
一共包括7个阶段。加载->验证->准备->解析->初始化->使用->卸载。其中验证、准备、解析3个部分统称为连接。
需要注意的是,加载(Loading)是类加载(Class Loading)过程的一个阶段,别混淆了两个概念。类加载过程包括上述的前5个过程。
验证阶段大致上会完成4个阶段的验证动作:文件格式验证、元数据验证、字节码验证、符号引用验证。
8、使用volatile修饰的变量在并发下的运算一定是安全的吗?
由于java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的。由于volatile变量只能保证可见性,所以只有满足以下两条规则的运算场景才可以不用锁就能保证原子性。
1)运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
2)变量不需要与其他的状态变量共同参与不变约束。
volatile的可见性是通过Java内存模型对volatile变量定义的特殊规则,每次使用前都要read、load,每次用完都要store、write相关联,必须连续一起出现。
9、线程的实现一般有三种:内核线程实现(包括高级接口——轻量级进程)、用户线程实现、用户线程实现加轻量级进程混合实现
内核线程实现优点:即使有一个轻量级进程在系统调用中阻塞了,也不会影响整个进程继续工作
缺点:系统调用代价比较高,需要在两种态当中来回切换,由于每个LWP都需要一个内核线程的支持,因此会消耗内核资源,支持的数量有限。
用户线程实现优点:操作快,消耗少,支持规模更大的线程数量
缺点:阻塞问题如何处理,多处理器系统中如何将线程映射到其他处理器上,这类问题解决起来异常困难
混合实现:
10、线程调度方式主要分为两种:协同式和抢占式。
11、自旋锁(JVM层面的锁优化技术):在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得,所以如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求所的那个线程“稍等一下”,看看持有锁的线程是否很快就释放锁,这种让线程采用忙循环(自旋),这项技术就是所谓的自旋锁。
优点:避免线程切换的开销(内核态与用户态的上下文切换)
缺点:占用处理器的时间,对处理器数量有要求。
12、Java语言定义的GC Roots对象包括:
1)虚拟机栈(栈帧中的本地变量表)中引用的对象。
2)方法区中静态属性引用的对象
3)方法区中常量引用的对象
4)本地方法栈中JNI引用的对象