概述
- 加载检查
- 分配内存,具体是使用指针碰撞还是空闲列表要看采用的GC方式
- 内存空间初始化(如默认值)
- 设置(对象头、元数据、分代年龄)
- 对象初始化(constructor)
对象的内存布局
对象头
对象自身运行数据
- hashcode
- gc垃圾回收年龄
- 锁状态标志
- 线程持有的锁
- 偏向线程ID
- 偏向时间戳等等
类型指针:对象是哪个类的实例拥有的
实例数据
程序中定义的各种类型字段
对其填充
对齐填充仅仅起着占位符的作用。由于HotSpot VM 的自动内存管理系统要求对象的大小必须是8 字节的整数倍。所以当对象其他数据部分(对象实例数据)没有对齐时,就需要通过对齐填充来补全。
对象的访问方式
Sun HotSpot 使用直接指针访问方式进行对象访问的。
句柄
在堆中划分一块区域作为句柄池,reference存储的是句柄地址,而句柄则是包含了对象实例的地址
直接指针
reference存储的是对象地址,速度更快
堆内存分配策略
-XX:+PrintGCDetails 打印垃圾回收日志,程序退出时输出当前内存的分配情况
- 对象优先在Eden分配
当Eden区没有足够空间分配时,JVM发起一次Minor GC。 - 大对象直接进入老年代
最典型的大对象是长字符串和数组,在老年代分配的目的是a.避免大量内存复制 b.避免提前垃圾回收,明明有足够的内存空间 - 长期存活的对象进入老年代
如果对象在Eden 创建并经过第一次Minor GC 后仍然存活,并且能被Survivor 容纳的话,将被移动到Survivor 空间中,并将对象年龄设为1,对象在Survivor区中每熬过一次Minor GC,年龄就增加1,当它的年龄增加到一定程度(默认为15)时,就会被晋升到老年代中 - 动态对象年龄判定
如果在Survivor 空间中相同年龄所有对象大小的综合大于Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代 - 空间分配担保
在发生Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立那么Minor GC 可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure 设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC 是有风险的,如果担保失败则会进行一次Full GC;如果小于,或者HandlePromotionFailure 设置不允许冒险,那这时也要改为进行一次Full GC。
Java泛型
最典型的是List中存储的数据类型。
引入一个类型变量T(其他大写字母都可以,不过常用的就是T,E,K,V 等等)
泛型类
public class TestGeneric <T>{
private T data;
public T getData() {
return data;
}
}
泛型接口
public interface TestGenericInterface <T>{
public T doSomething();
}
public class TestGenericImpl<T> implements TestGenericInterface<T>{
private T data;
@Override
public T doSomething() {
return null;
}
public static void main(String[] args) {
TestGenericImpl<String> testGeneric = new TestGenericImpl<>();
}
}
或者:
public class TestGenericImpl2 implements TestGenericInterface<String>{
@Override
public String doSomething() {
return null;
}
}
泛型方法
public class TestGenericMethod {
public <T> T test(T t){
return t;
}
}
Java中的泛型,只存在于源码中,在字节码文件中已经被替换成原来的原生类型(Raw Type),并且在相应位置插入了强制转型代码。因此对于运行期来说,<Integer> 和<String>是一样的,所以泛型实际上是语法糖,类型擦除。
将一段代码编译成class文件,在反编译之后就发现泛型不见了,泛型类型变成了原生类型。
擦除仅仅是对属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据