Java核心基础(1)——JVM

Java为何能跨平台

.java(代码) ——> .class(字节码) ——> 运行代码
       javac        java
javap -c反编译.class字节码至指令码(可查找指令码手册看指令含义)

由于JVM,不同平台的JVM生成不同系统的字节码与机器码
代码运行在不同的JVM上,生成不同系统机器码,实现了跨平台,JVM从软件层面屏蔽了不同操作系统在底层硬件和指令上的区别

JVM内部结构

结构图

image
线程私有:栈、程序计数器、本地方法栈
线程共享:堆、方法区

栈(stack)

可叫线程栈,每一个线程运行时,JVM会分配一块独立的内存空间,其中包括栈空间,栈中存放线程运行中的局部变量等。

栈帧

JVM为每一个方法分配一块独立内存空间,这个空间即是这个方法对应的栈帧,一个方法对应一个栈帧

JVM中的栈和数据结构栈的区别

JVM使用类似数据结构中的栈存储组成栈空间,达到先进后出

先运行的方法,会先创建栈帧,后运行的方法后创建栈帧;但是后运行的方法会先pop,先运行结束,栈帧出栈便销毁,后进先出

栈帧内部组成

局部变量表,动态链接,操作数栈,方法出口

局部变量表

存储线程中的局部变量。

操作数栈

存放线程运行中的操作数,使用字节码操作,并与局部表量表交互,是一个临时内存空间。

方法出口

存储方法的回调位置,JVM在程序进入方法之前,就将此方法的出口位置存储在此方法栈帧的方法出口内存空间中。

动态链接

每个栈帧都保存了一个可以指向当前方法所在类的运行时常量池, 目的是: 当前方法中如果需要调用其他方法的时候, 能够从运行时常量池中找到对应的符号引用, 然后将符号引用转换为直接引用,然后就能直接调用对应方法, 这就是动态链接。

局部表量表与操作数栈的交互

如何查看与阅读字节码?
首先javac XXX.java —> XXX.class
然后javap -c XXX.class ——> 字节码(指令码)可打开阅读

进行举例:
image

程序计数器

为线程每一步程序计数,方便线程被抢占后,回来继续执行程序,每一个线程有一个程序计数器。

栈和堆的联系

栈中局部变量如果是对象类型,其值为堆中对应对象的内存地址,对象创建在堆上,栈中局部变量仅存储其内存地址

举例:
Math math = new Math();
image

方法区(元空间)

jdk8后元空间不在放在JVM运行时数据区中,而是存放在物理内存里。
用于存储常量(常量池)、静态变量类信息(由类装载子系统将类信息加载到方法区java a.class

static User user = new User();
静态变量存储在方法区,指向存储在堆里的对象,方法区存储对象的地址信息。
image

本地方法栈

native修饰的方法叫本地方法,由c语言构成,当线程运行到本地方法时,JVM为线程分配本地方法栈。
存放本地方法执行中的内存数据。

字节码执行引擎

方法的实现、代码的运行都是字节码执行引擎负责执行字节码。

JVM线程管理内存图

image

堆构造

堆由年轻代、老年代组成。
年轻代由Eden、Survivor组成,Survivor由S0、S1组成。
image
年轻代占总堆内存的1/3,老年代占总堆内存的2/3。
年轻代中,Eden占年轻代内存的8/10,S0、S1分别占年轻代内存的1/10。

内存管理

如何找到垃圾对象

引用计数法

为每一个对象添加一个引用计数,记录被引用次数,每减少一次引用,则计数减1,当计数为0时,表示无引用,为垃圾对象。
无法解决循环引用,导致内存泄漏

可达性分析算法

使用GC Roots进行搜索,GC Roots可以是栈的本地局部变量、常量池中常量、方法区的静态变量、本地方法栈中JNI指针。

GC可达性分析

GC Roots根节点:可以是栈的本地局部变量、常量池中常量、方法区的静态变量、本地方法栈的变量等。
定义:将GC Roots作为起点,以这些节点开始搜索引用的对象,直到找不到下一个引用,将之前这一条链路上的对象都标记为非垃圾变量,其余未标记对象都是垃圾对象。
image

常见的垃圾回收算法

标记——清除算法

为每一个对象做标记,标记需要回收的对象,之后回收对象所占用空间。
容易产生垃圾碎片,导致后续空间不够,无法存入大对象以使用连续空间。

复制算法

将内存分为大小两块,每次使用一块,当这一块用完了,将上面的存活的对象赋值到空着的另一半,再将前一块全部清空,不会出现碎片。
会浪费空间!

标记——压缩算法

与标记——清除算法相同,先标记,再将存活对象向一段移动,然后清理边界外内存,即标记后整理再清理。
但是效率低!

分代算法

利用对空间,按照分代年龄进行清理。

minor GC与full GC工作原理(分代算法)

image

JVM调优最终目的

减少full GC次数与full GC耗时。

进入老年代条件

对象达到阈值分代年龄(默认15)

为何15?对象头中用4bit表示分代年龄,最大1111=15。
可使用-XX:MaxTenuringThreshold = x调整阈值。

动态年龄判断

当Survior中年龄1 + 年龄2 + ... + 年龄n的多个对象内存相加超过Survivor总内存的50%,则大于年龄n的对象放入老年代,相当于提前将年龄大的对象放入老年代
image

大对象直接入老年代

内存大于阈值的大对象,如数组、字符串直接入老年代,可通过-XX:PretenureSizeThreshold = x设置阈值大小。
这样做避免大对象在Survivor中来回复制,提高回收效率。

STW(Stop the world)

在GC过程中,会先收集线程1 GC Root中的垃圾对象,之后再去收集线程2,若线程1突然结束,则GC之前收集的非垃圾对象也会变为垃圾对象,导致收集错误。

定义:将用户线程停止,直到GC工作完成,再继续线程。
会导致用户体验不佳、卡顿,但能保证GC正常工作。

常见垃圾分类器

Serial(年轻代)

利用STW,单线程穿行回收,会造成卡顿。
使用复制算法。

Parallel Scavenge(PS)(年轻代)

多线程并行收集。
复制算法清理。

ParNew(年轻代)

PS升级版,可以和CMS合作并行回收。

Serial Old(老年代)

Serial老年代版。
使用标记——压缩算法。

Parallel Old(老年代)

PS老年代版。
使用复制算法。

CMS(ConcurrentMarkSweep)(老年代)

并发收集,效率高,算法复杂。
使用标记——清除算法,降低STW时间。

G1分类器

不再区分老年代和年轻代。
基于标记——压缩算法,空间连续。

JDK8默认垃圾回收器:PS + Parallel Old

永久代(jdk1.7)与元空间(jdk1.8)的区别

①永久代必须指定大小限制;元空间可以设置,也可以不限,受限于物理内存大小。
②字符串常量:1.7存放在永久代,1.8存在堆。
③类文件常量池存放在元空间。
④方法区是一个逻辑概念,1.7实际对应永久代,1.8对应元空间。

四种引用

强引用

必须有,即使抛出异常,GC也不会回收

软引用

可有可无,如果内存不足,GC收集软引用对象,加速收集,避免溢出。

弱引用

可有可无,不管内存是否充足,GC扫描到弱引用都会收集

虚引用

用来跟踪对象被回收的活动,GC找到虚引用,会被对象放入引用队列,对象再引用队列中被回收前可进行操作。

类加载机制

通过反射实现类的加载。

类加载过程

加载 ————> 连接 ————> 初始化
    (验证—>准备—>解析)

其中,加载:①通过全限定类名获取此类二进制字节流
      ②将字节流的静态存储结构转换为方法区的运行时数据结构
      ③内存中生成该类的class对象,作为访问入口

   连接:包括验证、准备、解析三个过程

JVM内置ClassLoader:①BootstrapClassLoader,最顶层,C++实现,加载 /lib 下jar包
           ②ExtensionClassLoader,java实现,加载 java_home/lib/ext 目录下包和类
           ③AppClassLoader,加载当前应用 classpath下类和包

双亲委派机制

在类加载中,会首先判断当前类是否被加载,加载时先委派父类加载器处理,若父类加载器无法出理再自己加载,可以避免类的重复加载。

如何避免双亲加载?可以创建自定义加载器,继承java.lang.ClassLoader重写其中方法loadClass()

posted @ 2021-03-13 17:29  CQCx64  阅读(80)  评论(0)    收藏  举报