问题
JVM线程是用户态还是内核态
java线程在jdk1.2之前,是基于名为“绿色线程”的用户线程实现的,这导致绿色线程只能同主线程共享CPU分片,从而无法利用多核CPU的优势。
由于绿色线程和原生线程比起来在使用时有一些限制, jdk1.2中放弃绿色线程,转而使用原生线程。
在目前的jdk版本中,操作系统支持怎样的线程模型,很大程度上决定了java虚拟机的线程是怎样映射的,这点在不同的平台上都没有办法达成一致。
总的来说就是,虚拟机规范中并没有限定java线程需要使用哪种线程模型,要根据不同的平台来说,但是无论使用哪种线程模型,java程序的编码和运行都是没有差异的。
例如,Java SE最常用的JVM是Oracle/Sun研发的HotSpot VM。在这个JVM所支持的所有平台上都是采用一对一的线程模型的,除了Solaris平台。
简述直接内存
直接内存也称为堆外内存,就是把内存对象分配在JVM堆外的内存区域。这部分内存不是虚拟机管理,而是由操作系统来管理。
Java通过通过DriectByteBuffer对其进行操作,避免了在 Java 堆和 Native堆来回复制数据。
解释内存中的栈(stack)、堆(heap)和静态区(static area)的用法
栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间
String str = new String("hello");
上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而”hello”这个字面量放在静态区。
简述java的引用类型
强引用:被强引用关联的对象不会被回收。一般采用 new 方法创建强引用。
软引用:被软引用关联的对象只有在内存不够的情况下才会被回收。一般采用 SoftReference类来创建软引用。
弱引用:垃圾收集器碰到即回收,也就是说它只能存活到下一次垃圾回收发生之前。一般采用WeakReference 类来创建弱引用。
虚引用:无法通过该引用获取对象。唯一目的就是为了能在对象被回收时收到一个系统通知。虚引用必须与引用队列联合使用。
简述java创建对象的过程
1. 检查该指令的参数能否在常量池中定位到一个类的符号引用,并检查引用代表的类是否已被加载、解析和初始化,如果没有就先执行类加载。
2. 通过检查通过后虚拟机将为新生对象分配内存。
3. 完成内存分配后虚拟机将成员变量设为零值
4. 设置对象头,包括哈希码、GC 信息、锁信息、对象所属类的类元信息等。
5. 执行 init 方法,初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。
简述对象的内存布局
对象在堆内存的存储布局可分为对象头、实例数据和对齐填充。
对象头主要包含两部分数据: MarkWord、类型指针。MarkWord 用于存储哈希码(HashCode)、GC分代年龄、锁状态标志位、线程持有的锁、偏向线程ID等信息。类型指针即对象指向他的类元数据指针,如果对象是一个 Java 数组,会有一块用于记录数组长度的数据,实例数据存储代码中所定义的各种类型的字段信息。
对齐填充起占位作用。HotSpot 虚拟机要求对象的起始地址必须是8的整数倍,因此需要对齐填充。
java对象内存分配是如何保证线程安全的
1. 对分配内存空间采用CAS机制,配合失败重试的方式保证更新操作的原子性。该方式效率低。
2. 每个线程在Java堆中预先分配一小块内存,然后再给对象分配内存的时候,直接在自己这块"私有"内存中分配。一般采用这种策略。
常见内存分配策略
大多数情况下对象在新生代 Eden 区分配,当 Eden 没有足够空间时将发起一次 Minor GC。
大对象需要大量连续内存空间,直接进入老年代区分配。
如果经历过第一次 Minor GC 仍然存活且能被 Survivor 容纳,该对象就会被移动到 Survivor 中并将年龄设置为 1,并且每熬过一次 Minor GC 年龄就加 1 ,当增加到一定程度(默认15)就会被晋升到老年代。
如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 的一半,年龄不小于该年龄的对象就可以直接进入老年代。
空间分配担保。MinorGC 前虚拟机必须检查老年代最大可用连续空间是否大于新生代对象总空间,如果满足则说明这次 Minor GC 确定安全。如果不,JVM会查看HandlePromotionFailure 参数是否允许担保失败,如果允许会继续检查老年代最大可用连续空间是否大于历次晋升老年代对象的平均大小,如果满足将Minor GC,否则改成一次 FullGC。