深入理解java虚拟机读书笔记——基础知识篇

运行时数据区

  • 程序计数器:同CPU的程序计数器,存储字节码运行到哪了
  • 栈:分为java虚拟机栈与本地方法栈,两个栈功能类似,java虚拟机栈用于java方法,本地方法栈用于native方法,即通过java调用其它语言的方法的栈
  • 堆:用于存放对象的实例
  • 方法区:存储加载了的类,静态变量,常量,以及即时编译后的代码的地方
  • 直接内存:绕过java虚拟机直接操作本机内存,如NIO

对象创建步骤

  1. 分配内存,根据情况将内存置为0
  2. 设置对象头,如对象的类的元数据,哈希码,分年代(分年代会用于GC)
  3. 执行init方法,即程序员自定义的构造函数

对象的查找

  1. 直接指针查找:引用的指针直接指向对象
  2. 间接句柄查找:引用的指针指向堆上的句柄,句柄中才包含指向对象的指针
  3. 对比:直接指针快,句柄只需要变动句柄中的指针的值,引用中指针的值不变

多线程下导致的栈的OOM

  • 描述:当操作系统分配给java虚拟机有限的内存时(比如xG),那么栈的大小=x - 堆内存 - 方法区。这是栈的总大小,同时,栈又是线程独占的,所以如果设定的栈容量越大,那么可建立的线程的线程数就越少,所以在建立大量线程时就会引发OOM,所以,此时可以考虑减少单个线程的栈大小,或者适当减少堆的大小

对象回收算法

  1. 引用计数法:通过统计每个对象的引用数来判断是否可以进行回收(python的垃圾回收貌似就是采用该算法)。但是当对象循环引用时,该算法就会出现内存泄漏的问题。
    ObjA objA = new ObjA();
    ObjB objB = new ObjB();
    objA.objB = objB;
    objB.objA = objA;
    objA = null;
    objB = null;

实际上,objA与objB已经不会再被用到了,但是他们两的引用数因为循环引用而部位0,所以就不会被判定为可回收
2. 可达性分析算法:就是从一个CG roots开始搜索引用,如果一个对象对于CG roots是不可达的,那么就被判定为可回收。
CG roots对象包括:

  1. 虚拟机栈中引用的的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈JNI中引用的对象

对象”死亡”的过程

对象在被杀死前要经历两次标记,首先,对象在经过可达性分析后,确认对象不可达,对象被标记一次。随后判断对象是否要执行finalize()方法,如果需要执行,那么该对象就会进入一个队列,随后由虚拟机自动创建一个低优先级的线程去执行该队列中对象的finalize()方法,但是不保证方法执行完,这是为了防止队列中某个对象的finalize()方法卡住。随后GC对队列中的对象进行第二次标记,所以如果对象让自己在finalize()方法中重新变得可达,那么它就能逃过一劫。不过我觉得这种骚操作基本没啥用,而且finalize()这个方法我觉得很鸡肋,能不用尽量不用吧


垃圾清理算法

  1. 标记——清理法:将要清理的对象标记出来,然后统一清理,就这样。该算法容易导致内存碎片化,从而无法分配大对象。
  2. 复制清理法:将可用内存分为两部分,A正常使用,B不用,清理时,将A要保留的对象复制到B,然后将A完全清理。当然,这种算法是对内存的极大浪费,尤其是现在内存条涨的这么猛的情况下(刚加完内存的作者泪如雨下)。事实上,HotSpot将内存分为一块Eden空间(不懂是这个名字的含义),和两块survivor空间,eden:survivor=8:1(默认,可调整),清理时将eden与一块survivor中的存活对象复制到另一块survivor中。这么设计的原因是,大佬们经过研究发现,对象基本都是朝生夕死的,所以没有必要保留那么大的空间去保留活着的对象。当然survivor内存也是有可能不足的,那么久直接把对象放入老年代的内存中。
  3. 标记——整理法:标记了,然后让活着的对象都向一端移动,然后就整齐了,没什么好说的。
  4. 复合算法,就是将对象分为老年代和新生代,分别采用不同的算法,新生代中的对象每次GC时都会有大量对象死去,所以用复制清理法,老年代则用标记法。

猜你喜欢

转载自blog.csdn.net/tjq980303/article/details/78153175