JVM内存与垃圾回收
jvm特点:内存动态分配和垃圾自动收集技术
jvmDOC网址:https://docs.oracle.com/javase/specs/index.html
jvm书籍:
跨语言平台JVM:
Kotlin、Clojure、Groovy、Scala、Jython、JRuby、JavaScript可以编译为JVM可识别的字节码文件,运行在JVM平台上
JVM属于程序虚拟机,常见的虚拟机是系统虚拟机
JVM整体结构:
JVM的指令集架构:是基于栈的指令集架构(指令集架构还包括基于寄存器的),是零地址指令,因为基于栈可以跨平台,但是性能差。
JVM生命周期: 启动、执行、退出
常见java虚拟机:Classic VM(只有解释器)、Exact VM、Hotspot虚拟机(有方法区(永久代/元空间))、JRockit(服务端,只有JIT)、J9虚拟机 (后为三大商用虚拟机) apache harmony(android SDK)、Microsoft JVM、Taobao JVM、Dalvik VM(Android)、ART VM、Graal VM(可能替代Hotspot)
类加载子系统:
Loading: 获取二进制字节流-->方法区运行时数据结构-->内存中生成大的Class对象(作为类的数据的访问入口)
Linking:
Verify: 保证class文件符合JVM要求,主要做文件格式验证、元数据验证、字节码验证、符号引用验证
Prepare: 为类变量分配内存、赋初始值
Resolve: 将常量池的符号引用转换为直接引用
Initialization: 执行类构造器方法clinit,完成类变量和静态代码块中变量的赋值,该方法在多线程下是同步加锁的
类加载器:
分为引导类加载器(BootStrap ClassLoader)和自定义加载器(User-Defined ClassLoader)
ClassLoader派生的加载器都是自定义加载器
Extention ClassLoader(扩展类加载器)和Application ClassLoader(应用/系统类加载器)就是自定义加载器
AppClassLoader(系统类加载器)的上层是ExtClassLoader,ExtClassLoader的上层是BootStrap ClassLoader(用C/C++编写,获取不到,为null)
用户自定义类的加载器是AppClassLoader
Java的核心类库(包名为java、javax、sun)由BootStrap ClassLoader加载
双亲委派机制:收到类加载请求,优先由父类加载器加载,双亲委派机制可以做到沙箱安全机制
运行时数据区
PC寄存器(Program Counter Register):
用来存储指向下一条指令的地址,线程私有,既没有GC,也没有OOM。作用:线程切换后,记录下一指令开始位置
虚拟机栈(JVM Stacks):
线程私有,由一个一个栈帧组成,一个栈帧就是一个方法
设置栈的大小: -Xxs1m
栈帧结构:局部变量表(Local Variables)、操作数栈(Operand Stack)、帧数据区:【动态链接(Dynamic Linking)、方法返回地址(Return Address)、附加信息】
局部变量表:基本单位是槽,32位(byte、short、char、boolean(0为false,非0为true)、引用类型 会被转换为int存储)占一个槽,64(long, double)位占两个槽;非静态方法的局部变量会多一个变量(this)
操作数栈:使用数组或链表实现,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时存储空间;栈顶缓存技术:频繁的执行内存读/写会影响执行速度,所以将栈顶元素全部缓存在物理CPU的寄存器中。
动态链接:指向运行时常量池的方法引用,将符号引用转换为直接引用
方法返回地址:存放该方法pc寄存器的值
附加信息:栈帧中还允许携带与虚拟机实现相关的附加信息(可选),例如对程序调试提供支持的信息
本地方法栈:
线程私有,用于管理本地方法的调用
本地方法: java调用非java接口(C/C++)
堆:进程(JVM实例)唯一,堆内存大小可调节,堆可以是物理不连续,逻辑上连续的;几乎所有的对象实例以及数组都应分配在堆上,是GC(Garbage Collection)回收的重点区域
堆空间逻辑上分为新生区/Young/New(Eden、s0、s1)+养老区/Old/Tenured,永久区jdk8之前/元空间jdk8物理上属方法区
参数设置:不写单位就是字节(bytes)
-Xms(memory start) :初始化大小,默认物理内存的1/64,
-Xmx :最大,默认物理内存的1/4
-XX:+PrintGCDetails:打印GC明细
-XX:NewRatio=2:表示新生代占1,老年代占2,默认为2
-XX:SurvivorRatio=8:Eden、S0、S1的比例8:1:1,默认是8,但是实际是自适应的,若要8需显示设置
-Xmn:设置新生代的大小,与-XX:NewRatio冲突,以-Xmn为准,不常用
-XX:MaxTenuringThreshold=:设置进入老年代的阈值
-XX:+PrintFlagsInitial:查看所有参数默认值
-XX:+PrintFlagsFinal:查看所有参数的最终值
-XX:HandlePromotionFailure:是否设置空间担保,为true时确认是进行Major GC还是Full GC,为false直接Full GC
-XX:+DoEscapeAnalysis开启逃逸分析
-XX:+PrintEscapeAnalysis查看逃逸分析结果
-XX:+EliminateAllocation标量替换
命令窗口查看参数命令:jps
jstat -gc 进程ID 查看内存占用
jinfo -flag SurvivorRatio 进程ID 查看参数值
Runtime .totalMemory()获取的是堆内存,此内存只包含一个s区,
OOM:OutOfMemoryError
年轻代(YoungGen):存放生命周期短的对象,又分为伊甸园(Eden)、幸存者0(Survivor0)、幸存者1(Survivor1)/(from区、to区(谁空谁是to)),80%都是朝生夕死
老年代(OldGen):存放生命周期长的对象,阈值为15,超过15去老年代
垃圾回收总结:频繁收集新生代,很少收集养老区,几乎不动永久代/元空间
Minor GC/Young GC、 Major GC/Old GC、 Full GC:Full GC收集java堆和方法区的垃圾收集;G1还有混合收集(Mixed GC)收集整个新生代以及部分老年代垃圾;Eden区满触发Minor GC,回收速度快;老年代空间不足触发Major GC,回收速度慢,比Minor GC慢10倍;System.gc()/老年代空间不足/方法区空间不足触发Full GC
常用调优工具:JDK自带、Eclipse的Memory Analyzer Tool、Jconsole、VisualVM、Jprofiler、Java Flight Recorder、GCViewer、GCEasy
堆内存分代的原因就是优化GC性能
TLAB( Thread Local Allocation Buffer):对Eden区划分,为每个线程分配一个私有缓存区域,优先使用TLAB,占Eden区的1%,
逃逸分析(只有server端才可用):如果一个对象没有逃逸出方法,就可能被优化成栈上分配,JDK6默认开启逃逸分析,尽量用局部变量,由JIT编译器优化;如果一个对象只有一个线程使用,省略同步考虑,同步省略;把没有逃逸的对象(聚合变量,不需要连续的内存结构也可以被访问)替换为标量,标量替换,可以存在寄存器(JVM栈)中;实际上对象是分配在堆上的
方法区(Hotspot中: 永久代/元空间):
线程共享,逻辑上是堆的一部分,物理上是独立于Java堆的内存空间,也叫Non-Heap
永久代使用的是虚拟机内存,JDK8废弃永久代,在本地内存中实现元空间
参数设置:
-XX:PermSize= 永久代初始分配空间大小,默认20.75M
-XX:MaxPermSize= 永久代最大分配空间,32位机器默认64M,64位机器默认82M
-XX:MetaspaceSize= 元空间分配内存大小,windows下是21M,超过该值就会触发Full GC,然后重置该值,使其不超过MaxMetaspaceSize
-XX:MaxMetaspaceSize= 元空间最大分配空间,windows下默认-1,没有限制
方法区内部结构:类信息(class、interface、enum、annotation、Field、Method)、运行时常量池、静态变量、即时编译器编译后的代码缓存
一个有效的字节码文件中包含类的版本信息、字段、方法、接口、常量池表(Constant Pool Table),常量池包括字面量和对类型、域和方法的引用
JDK1.7及以后将字符串常量池、静态变量保存到堆中,JDK1.8及以后的元空间使用的本地内存
对象实例: 由对象头、实例数据、对齐填充组成
对象头(Header): 运行时元数据(哈希值、GC分代年龄、锁状态)和类型指针(指向对象所属类型)
对象引用访问堆中对象:有句柄访问和直接指针两种方式;句柄访问:句柄池由到对象指针和到类型指针;直接指针(Hotspot用):直接指向对象,对象中有类型指针;后者效率高
直接内存:本地内存;直接分配本地内存空间:ByteBuffer.allocateDirect(1024*1024*1024),
访问直接内存的速度优于Java堆,读写性能高NIO,不受JVM内存回收管理,也会出现OOM,设置参数MaxDirectMemorySize,默认与堆内存-Xmx一样
执行引擎
将字节码指令编译为对应平台的本地机器指令