内存自动管理思维导图
本文的相关代码和笔记都放在了我的github,希望大家多多挑错。欢迎各位大佬批评。
本文的代码在github中,。里面也包含了JVM实验的大部分代码,希望大家多多支持。
1.对象问题
1.1 对象的判亡
- 使用标记(可达性分析)算法,从GC root中找到有引用的对象和没有被引用的对象。
- 对被引用的对象进行一次标记,之后判断是否有必要执行finalize()方法
- 如果finalize()被调用过,或者没有被重写,就没有必要执行。
- 所以说一个对象到达死亡最多会进行两次标记,一般来说只会进行一次标记。
package gcAndDistribution;
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive() {
System.out.println("yes i am still alive");
}
/*调用这个方法之后把引用重新给挂到自己身上,
那么这个对象就又有了强引用,
就可达,
就不会被垃圾回收器回收。
*/
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws Throwable{
SAVE_HOOK = new FinalizeEscapeGC();
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no, i am dead");
}
// 只能调用一个finalize方法,所以就算再次把这个对象指向也没有什么用了
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null) {
System.out.println("I am still alive");
} else {
System.out.println("no i am dead");
}
}
}
- 可以作为GC root的
- 静态变量
- 常量池中内容
- 栈
- 锁
- 跨区域的引用。比如进行Eden收集的时候,把老年代。
1.2 对象的内存布局
-
对象头
- markword
-
对象头->类型指针
- 指针压缩4B
- 不压缩8B
-
其他变量
1.3 对象引用
看上面对象内存布局。
-
强引用
就是普通的应用
-
软引用:可以实现缓存机制和引用队列一起使用
ReferenceQueue referenceQueue = new ReferenceQueue(); String s = new String("软引用,你好华为\n"); SoftReference<String> sr = new SoftReference<String>(s, referenceQueue); System.out.printf(sr.get()); s = null; System.gc(); System.out.printf(sr.get()); /* * 可以看出,虽然引用强引用已经指向了null, 但是内存仍旧没有释放,这是由于有 * 弱引用的存在。弱引用只有在内存不够的时候在进行回收。 * 为了防止内存泄漏,可以配合引用队列进行 * */ try{ // referenceQueue.remove(); if (referenceQueue.poll() != null) { System.out.printf("1:被回收\n"); } else System.out.printf("0:没被回收\n"); }catch ( Exception e) { System.out.println("你好,出错了"); e.printStackTrace(); } System.out.printf(sr.get());
-
弱应用,
ReferenceQueue reference2 = new ReferenceQueue(); WeakReference weakReference = new WeakReference(new String("weakReference hello 科兴"), reference2); System.out.println(weakReference.get()); System.gc(); System.out.println(weakReference.get()); try { if (reference2.poll() != null) { //使用Poll 如果使用remove会进入阻塞态 System.out.printf("弱引用失去引用\n"); } else System.out.printf("弱引用仍旧在被引用\n"); }catch ( Exception e) { e.printStackTrace(); }
-
虚引用: 用于管理堆外内存,netty中的zerocopy
private static final String TAG = "PhantomReferenceDemo"; // 1、定义为成员变量 防止PhantomReference被回收 private ReferenceQueue<String> mQueue = new ReferenceQueue<>(); private PhantomReference<String> mReference; // 2、定义为成员变量 方便手动控制回收 private String mTest; @Test public void test() { // 4、开启线程 监控回收情况 new Thread(() -> { while (true) { System.out.println("线程开启"); Reference<? extends String> poll = mQueue.poll(); if (poll != null) { System.out.println("引用被回收"); } try { Thread.sleep(1000); } catch (Exception e) { System.out.println(e.getMessage()); } } }).start(); // 3、 // 直接用双引号定义的 存储在方法区 // new出来的 存储在JVM堆 // 使用System.gc进行强制垃圾回收,之后内存被回收,就可以看到 mTest = new String("test"); mReference = new PhantomReference<>(mTest, mQueue); try { Thread.sleep(1000); mTest = null; System.gc(); System.out.println("手动制空完成"); Thread.sleep(2000); } catch ( Exception e) { System.out.println(e.getMessage()); } }
1.4 对象创建
- 首先去看能否在常量池种定位到一个类符号引用,并判断是否经过了加载、解析和初始化的过程。(首先创建类)
- 分配内存,内存的大小在类加载完成之后就可以确定了。
- 指针碰撞法
- 空闲链表发
- 是否有内存压缩(compact) 决定的(parNew和serial是使用指针碰撞)。(CMS理论上是空闲链表,但是可以在拿到的大区域中进行指针碰撞)。
- 设置对象头,(类型指针,hash, 对象的分带年龄)
- 执行方法
考虑并发
- CAS失败重试
- 线程本地分配缓冲TLAB(Thread Local Allocation Buffer) -XX +/-UseTLAB
考虑是否执行了方法。
- new和 invokespecial是两条字节码指令。
- 如果字节码中在之后生成了invokespecial的命令,就会,就是说invokespecial调用的是方法。
- Java编译器编译的字节码文件一般都会。
注意
- 首先类加载:父类的静态和字面量加载进去
- 其次类分配内存
- 然后父类的域和初始化块进行初始化,调用父类的构造函数
- 然后子类的域和初始化块进行初始化,调用子类的构造函数。