《深入理解Java虚拟机》读书笔记(JVM常识汇总一)

0:JVM的内存布局: (这里用百度到的图)


这应该是比较经典的一个JVM内存布局图。 如图上:堆和方法区是被线程共享的,虚拟机栈,本地方法栈,程序计数器,这是每个线程私有的,即每个线程都有1份。 

【1】堆:绝大多数对象都存放在这里,当然肯定有的对象活的久,寿命长,有的对象活的短,寿命短,所以堆里的内存又可分为:新生代(Young generation)和老年代(Old generation)。  默认 新生代占 1/3的堆空间,老年代占2/3的堆空间。   新生代又可分为:

1个Eden(伊甸园,就是对象出生的地方) 和 2个Survivor(from survivor 和 to survivor) 。它们的比例是 8:1: 1 。 但是survivor每次只用1个,留一个作为复制存活对象所用。(复制算法,下一篇会说) 。 所以新生代可用空间为整个新生代的90%。    

堆是线程共享的。

【2】方法区:用来存储 常量池, 类元信息 ,方法元信息 , 在JDK8 后,名字变成了 MetaSpace(元数据区)  ,方法区也是线程共享的。

【3】本地方法栈 : 类似于虚拟机栈,只不过这个栈主要是为了Native方法服务。   线程私有

【4】虚拟机栈: 执行方法时候,这个方法就会入栈,成为1个栈帧,栈帧(也就是方法)里面存放着 局部变量表 操作栈 动态链接, 方法返回地址 等方法信息。  线程私有。

【5】程序计数器:

          由于CPU时间片的轮流执行,执行某个线程中的指令时候,有时候可能会切换到另一个线程,这样就必然会导致线程间的切换,中断,以及恢复。为了保证这些操作可以不出差错,前后衔接。 每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器用来存放执行指令的偏移量和行号指示器等,线程在切换,中断,恢复,运行都需要这个“标识” 。  线程私有。

  

1、绝大部分的对象都分配在堆里,在垃圾收集器工作前,首先要做的就是判断哪些对象“存活”,哪些对象“死亡”。(即已经不能再被使用获取到的对象)
 

2、 2种判断方法:
2.1 : 引用计数算法:  给对象添加一个引用计数器,每当一个对象引用它时,计数器的值就加1,当引用失效时,计数器的值就减1,当某个对象上的计数器上的值为0时,说明这个对象已经不可能再被使用。(即意味着这个对象成为了即将被回收的对象,这个算法简单,而且效率也高,但是Java并没有采用这个算法,最主要的原因是因为它很难解决对象之间循环引用的问题。代码如下:)

public class ReferenceCountingGC {
    /**
     * 《深入理解Java虚拟机》  -zzm
     */
    public Object instance = null ;
    private static final int _1MB = 1024*1024 ;

    /**
     * 这个数组(成员属性)的作用就是占点内存
     */
    private byte[] bigSize = new byte[2 * _1MB];


    public static void main(String[] args) {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        /*通过成员属性互相引用对方,导致objA和objB的引用计数器都不为0*/
        objA.instance = objB ;
        objB.instance = objA ;


        objA = null ;
        objB = null ;

        //假设在此发生GC,objA和objB被回收了!
        System.gc();
    }
}

接下来是GC打印出来的信息:

[GC (System.gc()) [PSYoungGen: 7434K->648K(38400K)] 7434K->648K(125952K), 0.4162578 secs] [Times: user=0.48 sys=0.00, real=0.42 secs] 
[Full GC (System.gc()) [PSYoungGen: 648K->0K(38400K)] [ParOldGen: 0K->607K(87552K)] 648K->607K(125952K), [Metaspace: 3207K->3207K(1056768K)], 0.0095349 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 38400K, used 998K [0x00000000d5d00000, 0x00000000d8780000, 0x0000000100000000)
  eden space 33280K, 3% used [0x00000000d5d00000,0x00000000d5df9b20,0x00000000d7d80000)
  from space 5120K, 0% used [0x00000000d7d80000,0x00000000d7d80000,0x00000000d8280000)
  to   space 5120K, 0% used [0x00000000d8280000,0x00000000d8280000,0x00000000d8780000)
 ParOldGen       total 87552K, used 607K [0x0000000081600000, 0x0000000086b80000, 0x00000000d5d00000)
  object space 87552K, 0% used [0x0000000081600000,0x0000000081697f98,0x0000000086b80000)
 Metaspace       used 3262K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 356K, capacity 388K, committed 512K, reserved 1048576K

看打印信息:PSYoungGen: 7434K->648K(38400K)  新生代使用的内存从7434->648K ,意味着虚拟机将这2个对象回收了。
2.2:可达性分析算法
       这个算法的基本思路就是通过一系列的被称之为“GC Roots”的对象作为起始点,从这些几点开始向下搜索,搜索所通过的路径称之为引用链,引用链上有的对象证明是可达的,有用的对象。当一个对象到GC Roots 没有任何引用链可达,即GC Roots的引用链到达不了这个对象,关联不起来。证明这个对象是不可用的,所以这个对象就会判定为可回收的对象。

在Java语言中,可作为GC Roots的对象包括以下几种:
1、虚拟机栈(栈中的本地变量表)中引用的对象
2、方法区中类静态属性引用的对象
3、方法区中常量引用的对象
4、本地方法栈中JNI(即一般所说的Native)引用的对象。

注意: 即使在可达性分析算法中被标记为不可达的对象,也不是“非死不可”。 当它被第一次标记时候,还会被筛选,筛选的条件就是 这个对象是否有必要执行finalize()方法,如果finalize()方法已经被虚拟机调过或者没有被这个对象重写,证明它就没必要再执行这个方法了,因为任何一个对象的finalize()方法只会被执行一次。它就会被等待下一次标记,至少被标记2次,这个对象才会被真正宣告死亡了。  如果有必要执行finalize()方法,那么这个对象就可以在finalize()方法里,重新与引用链上的任何一个对象建立关系即可,就可以取消“即将被回收”的标记,换言说,finalize()方法,是一个对象避免回收(摆脱死亡命运的最后一次机会!)


 

猜你喜欢

转载自blog.csdn.net/Akanarika520/article/details/83374376
今日推荐