深入理解JAVA虚拟机(一、)

146 阅读7分钟

JVM的垃圾回收机制

// java 比 C 、C++ 最强大的地方 就是内存管理(不需要申请内存、释放内存,这些都由Java虚拟机来替我完成) 在 java 中,程序员是不需要手动的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

一、了解历史

JDK(java开发工具集合)包含JRE(java运行时环境)和JVM(java虚拟机),JRE包含JAVA api和 JVM
什么是JVM?(C 和 C++ 也是跨平台的)	
我们写一个HellO.java 文件,最终会编译成Hello.class字节码文件。这个字节码文件可以在windows环境运行,可以在Linux环境运行。就是我们在不同的平台都安装对应该平台的 JVM,从.java源文件到.class 字节码文件的过程就是JVM的工作     

二、内存结构

jvm 虚拟机 可以看成三部分
1、JVM运行时数据区(这个重要、也是平时知道的最多的)
2、类加载子系统
3、执行引擎:
	jvm里面有两套执行引擎:1、字节码解释器(java字节码-->c++-->机器码)
					    2、模板解释器(java字节码-->机器码)
类加载子系统,把1字节码文件加载到运行时数据区,也就是内存中。然后CPU分配一定时间调度,让执行引擎执行它。加载字节码文件到虚拟机栈,栈是数据结构(先进后出、后进先出),先加载main、然后add。


**运行时数据区:**
线程共享区:(线程不安全,任何一个线程都可以操作)
方法区:存储运行时常量池,类信息,常量,静态常量等
java堆:存储对象实例
线程独占区:
栈(虚拟机栈):存储运行时所需要是数据,局部变量表等
本地方法栈:为JVM所调用的本地方法服务
程序计数器:记录当前线程所执行到的字节码的行号

 **1、虚拟机栈**
虚拟机栈描述的是java方法执行的动态内存模型
1.虚拟机栈也就是我们平常所称的栈内存,它为 java 方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、运行时所需要是数据、操作数栈、动态链接和方法出口等信息。
2. 虚拟机栈是线程私有的,它的生命周期与线程相同。
 //局部变量表
存放基本数据类型、引用类型、returnAddress类型。
***栈里面存放对象的引用,堆存放对象的实体
**2、程序计数器**
是一块较小的内存空间,是当前线程所执行的字节码指令的行号指示器,就是索引。
处于线程独占区。
**3、本地方法栈**
虚拟机栈为虚拟机执行Java方法服务
本地方法栈和虚拟机栈类似,只不过本地方法栈为 Native 方法服务。
存放native方法的栈帧,与底层硬件方法接触
**4、方法区(元空间)**
1.7永久代是放在堆区的,1.8元空间是直接放在操作系统内存的
    为什么有这种改变? 1、gc问题:堆区是用来存放对象的,又要存放类的元信息,而类的元信息有很少改变,就把它单独提出来放在直接内存中了。增大堆内存,减少gc次数。

1. 有时候也称为永久代,在该区内很少发生垃圾回收,但是并不代表不发生 GC,在这里进行的 GC 主要是对方法区里的常量池和对类型的卸载
2. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。
3. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。
**5、堆**
对象和类的关系-->一个类对应多个对象
存放对象的实例··
垃圾回收、性能调优都是在堆里
堆分新生代和老年代(分这些区域主要是为了垃圾回收)
新生代分为Eden、from、to  (8:1:1  可用的空间是90%)  (复制算法)
老年代                   (新:老 1:2或者1:3 )	  (标记-清除算法)	
绝大部分对象放在新生代的Eden(伊甸园)区(因为詹姆斯.高斯林是基督教徒,基督教认为新生的人都是从伊甸园出生的),当Eden对象足够多达到临界值的时候,会触发GC(minor GC)。会进行可达性判断(DC roots)(判断这个对象有没有被别的地方引用,如果这个对象没有被别的地方引用,就会被抛弃。如果这个对象被别的地方引用着,那这个对象就会进入到from区,并且age+1).	然后继续创建对象,当再次触发Eden区的临界值,就会再次触发GC(minor GC),进行可达性判断,如果还在别的地方有引用,对象就会进入到from区,当from区满了之后内,会再次触发GC(minor GC),判断from区的对象是否处于游离状态,如果这个对象没有被其他地方用着,那么抛弃,释放内存。如果这个对象还被其他地方引用着,那么它会把这个对象的age+1(之前GC进入到from区的对象的age也+1,)并且拷贝到to区,此时from区变成to区,to区变成了from区。然后就这样一直循环,只要这个网站运行着,Eden区就会有源源不断的新对象,当minor GC一直被触发,触发到15次之后,有一些对象还没有被回收,那么它的age=15,JVM会认为这个对象是个老不死的对象,这个对象会进入到老年代。
当程序运行的时间越来越长,会有更多的对象进入到老年代,当达到临界值,再进入一个对象,老年代没有办法给它分配内存的时候,会触发(full GC,针对整个堆以及永久代的也会损耗一定的时间。所以我们在优化JVM的时候,减少Full GC的次数也是经常用到的办法。)就是STW(网站停顿),整个应用程序会被卡死,没有任何响应。当进入STW的时候,程序无法响应,垃圾回收也无法进行回收,
当老年代满了,会出现(OutOfMemoryError)表示堆内存溢出。

// `什么对象会进入老年代? 1、gc次数超过15次的,除了我理解的那个外还有 gc的年龄栈4个bit,就是2的4次方,1-15 2、空间担保(伊甸园区gc后,from区装不下,这个时候就放入老年代) 3、动态年龄判断(为了能更好地适应不同程度的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄)就是虽然没有到达15岁,但是8岁的所有对象+起来已经占到一半,那么8岁和大于8岁的都进入老年代,就不用等到15岁了。 4、大对象(对象大小超过Eden区一半,大小是动态计算的)