【2019春招准备:4.JVM java编译器 java解释器】

51 篇文章 0 订阅
序号考点链接备注(公司、年份)
1.classLoader 类加载的流程https://blog.csdn.net/qq_33907408/article/details/848981132018.11 招银网络java 1 面
2.加载器双亲委派模型及破坏https://blog.csdn.net/qq_33907408/article/details/85211420阿里云2018.10 java2面

jvm
参考:深入JVM内核:原理、诊断与优化(葛一鸣)、《深入理解JV虚拟机》
【内容】
【补充】

  • 常量池
  • 元空间(Metaspace):从JDK 8开始,Java开始使用元空间取代永久代,元空间并不在虚拟机中,而是直接使用本地内存。
  • 年轻代-老年代转移时机:①年龄计数器到达一定数量(可以设置)②动态判定:Survivor中相同年龄对象查过一半,大于这个年龄的对象进入老年区。③大对象(大小可以设置)

【内容】

1. 简介

jvm,和vmware visualbox不同,没有对应的硬件
jvm使用软件模拟java字解码指令集
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
使用最广泛的虚拟机hotspot,还有jRokit(oracle将二者合并到hotspot)
在这里插入图片描述

java和jvm是相对独立的 只要满足jvm规范,都能在jvm上面跑
scala,Groovy,clojure都可以在jvm上面运行

  • 整数的表达:源码 反码 补码
    将整数在jvm中的存储形式(二进制)打印出来
int a=-6;
for(int i=0;i<32;++i){
	int t=(a & 0x80000000 >>> i) >>> (31-i);
	System.out.println(t);
}
  • 为什么需要用补码?(在jvm内部二进制没有歧义的表示0;符号位直接参与运算)
    在这里插入图片描述
    在这里插入图片描述
    32 64位只有指针长度和long变了,其他不变!
    在这里插入图片描述
    在这里插入图片描述

相同的二进制串在jvm内部怎么知道是浮点数还是整数呢?这就需要看相关的操作,在汇编层面,如加法对于整数类型是add,对于浮点数类型是fadd,自然能够区分。

2. jvm运行机制

2.1 jvm启动流程

在这里插入图片描述

2.2 jvm基本结构

在这里插入图片描述

【pc寄存器】:
每个线程拥有一个pc寄存器,在线程穿件的时候创建
指向下一条指令的地址,如果下一个方法是本地方法,pc的值是undefined)

【方法区】
保存的内容:类型的常量池、字段方法信息、方法字解码
基本都属于永久代(可以理解成和方法区是同一个东西)

【java堆】
全局共享(所有线程共享的)
堆分代(见补充)

【java栈】
线程私有
由一系列的帧组成(帧可以含有多个帧片段,片段的大小是32位)
保存的是:常量池指针,局部变量、操作数、返回地址等等。
每一次的方法调用,就是将该方法的帧压到总的线程帧栈中,执行完毕就移出去。

  1. 局部变量表:如果是类的局部变量表就是各个成员变量,如果是方法,第一个是this指针
  2. 操作数栈:java中没有寄存器,参数传递都是栈实现
    在这里插入图片描述

【栈上分配 vs 堆上分配】

  1. 栈分配(<1M):小对象,在没有逃逸的情况下可以直接分配在栈上;可以自动回收,减少GC压力;大对象和逃逸对象无法栈上分配
  2. 堆分配:相反

【堆、栈、方法区的交互例子】

public class AppMain{
	public static void main(String[] args){
		Sample test1=new Sample("测试1");
		Sample test2=new Sample("测试2");
		test1.printName();
		test2.priintName();
	}
}
public class Sample{
	private name;
	public Sample(String name){
		this.name=name;
	}
	public void printName(){
		System.out.println(name);
	}
}

在这里插入图片描述

【本地方法栈】
只为native方法提供服务,但是hotspot并不区分本地方法栈和虚拟机栈

2.3 内存结构

在这里插入图片描述

在这里插入图片描述
当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作
每一个read\load\store\write操作都是原子的,但是之间是有空隙的
每个线程对主内存(堆)的共享变量都保留一个副本,对其操作或者修改都具有时延,如果立即线程之间可见,需要加上volatile关键字(见补充)

2.4 编译运行 && 解释运行

bytecode执行的两种方式

解释执行:读一句执行一句
编译执行:将字解码编译成机器码,运行机器码(性能有数量级的提升)

3. jvm常用配置参数

3.1 trace跟踪参数 (主要是对GC的跟踪)

打开GC:
-verbose:gc
-XX:+printGC
打印详细信息:-XX:+PrintGCDetails
打印信息加上时间戳:-XX:+PirintGCTimeStamps

-Xloggc:log/gc.log:log输出到文件

-XX:_printHeapAtGC 每一次GC之后都打印出堆的信息
-XX:+TraceClassLoading监控类的加载(跟踪调试 第一个肯定是object)
-XX:printClassHistogram :打印类的直方图

3.2 堆分配参数

思考:java桌面级产品,需要打包jre进去,但是打包的部分只是产品用到的部分 怎么做优化呢?
官方推荐:新生代占整个堆的3/8,幸存代占整个新生代的1/10,oom的时候一定要将堆内存dump出来,不然没有办法排查现场的问题
就算堆空间没有用完也能抛出oom,原因是永久区的溢出(如cflib生成的hibernate等动态代理的对象的时候,永久区的溢出,没有更为永久的区域存放这些对象了)

-Xmx -Xms 指定最大堆空间 指定最小堆空间

System.out.println(Runtime.getRuntime().maxMemory()/1024.0/1024+"M")```

在这里插入图片描述
在这里插入图片描述

-XX:PermSize

  • 例子:
    新生代太小:全部分配到了老年代
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

3.3 栈分配参数

通常只有几百K 决定了函数调用的深度
每个线程都会分配一个空间,所以每一个不会大空间
m
在这里插入图片描述

4. GC算法和种类

GC的对象时堆空间(年轻代和老年代)和永久区
老年代的对象:可能是开始分配的时候空间不够,直接从新生代担保进来的,其他的应该都是长命对象

4.1 引用计数法

古老的垃圾回收算法
为每一个对象标记一个使用数量,用过引用数量作为参考是否进行回收(0)
伴随加加减减 影响性能
很难处理循环引用
在这里插入图片描述

4.2 标记清除法

是现代垃圾回收的思想基础
两个阶段:标记阶段 + 清除阶段
从根节点开始标记,能够遍历到的标记的都不会回收,其他的回收。
在这里插入图片描述

4.3 标记压缩法

适用于存活对象比较多的场合
在标记清除算法上面优化:清除的时候是将存活对象复制压缩到内存的一端,之后清理边界外的所有对象
在这里插入图片描述

4.4 复制算法

高效,但是不适合于对象多的场景(年老代)
在这里插入图片描述
改进:大对象尽量直接进老年 不要放到复制空间;老年对象直接进入老年代(计数一直都有,说明被长期使用);剩余对象,小的 年轻的 进入复制空间
在这里插入图片描述

4.5 可触及性

三种对象

可触及对象:从根节点可以触及到这个对象
可复活对象:当下阶段是不可触及的,但是在finalize()中可能复活的对象,也是不可以回收的
不可触及对象:回收的都是不可触及的对象(不可触及对象不能被复活)

在这里插入图片描述

  1. 新建对象首先处于[reachable, unfinalized]状态(A)
  2. 随着程序的运行,一些引用关系会消失,导致状态变迁,从reachable状态变迁到f-reachable(B, C, D)或unreachable(E, F)状态
  3. 若JVM检测到处于unfinalized状态的对象变成f-reachable或unreachable,JVM会将其标记为finalizable状态(G,H)。若对象原处于[unreachable, unfinalized]状态,则同时将其标记为f-reachable(H)。
  4. 在某个时刻,JVM取出某个finalizable对象,将其标记为finalized并在某个线程中执行其finalize方法。由于是在活动线程中引用了该对象,该对象将变迁到(reachable, finalized)状态(K或J)。该动作将影响某些其他对象从f-reachable状态重新回到reachable状态(L, M, N)
  5. 处于finalizable状态的对象不能同时是unreahable的,由第4点可知,将对象finalizable对象标记为finalized时会由某个线程执行该对象的finalize方法,致使其变成reachable。这也是图中只有八个状态点的原因
  6. 程序员手动调用finalize方法并不会影响到上述内部标记的变化,因此JVM只会至多调用finalize一次,即使该对象“复活”也是如此。程序员手动调用多少次不影响JVM的行为
  7. 若JVM检测到finalized状态的对象变成unreachable,回收其内存(I)
  8. 若对象并未覆盖finalize方法,JVM会进行优化,直接回收对象(O)
  9. 注:System.runFinalizersOnExit()等方法可以使对象即使处于reachable状态,JVM仍对其执行finalize方法

4.6 Stop-The-World

全局暂停,所有java代码都不能执行,native还可以执行(因为不和jvm交互)
只有全局停顿,才不会在清理的过程中产生新的垃圾
新生代的GC比较短,老年代的GC可能长达分钟级别,根据具体大小不同

全局暂停的原因:(基本是因为GC引起的)

  • dump线程
  • 死锁检查
  • 堆dump

5. GC参数

5.1 串行收集器

最老 最稳定 效率最高
只是用一个线程,没有充分发挥好多核的优势
在新生代使用复制算法;在老年代使用标志-压缩算法
-XX:+UseSerialGC

5.2 并行收集器

【ParNew】
-XX:+UseParNewGC
-XX:ParallelGCThreads 指定回收线程的并行数量
新生代采用并行回收,老年代依旧是串行回收

【Parallel】
同样是新生代复制算法,老年代标志压缩算法
-XX:UseParallelGC 使用parallel收集器+老年代
-XX:UseParallelOldGC 使用parallel收集器+老年代
-XX:MaxGCPauseMills GC时候尽量不超过的时间,不能完全保证,只能作为一个目标值
-XX:GCTimeRatio::(0-100 default99) 1-垃圾回收占总时间的比例(默认1%的时间做GC)
上面两个参数是矛盾的,不能同时局部最优,只能整体更优

5.3 CMS收集器

Concurrnet Mark Sweep(并发标记清除)

-XX:+UseConcMarkSweepGC
清理不太彻底,因为没有STOP-THE-WROLD
标记清除算法(前面都是标记压缩):及时会产生碎片,但是由于是并发的,用户线程也在执行着业务,不能挪动内存的数据
注意并行并发:这里是并发,表示回收和业务一起执行(不一定并行,可能串行)
停顿会对减少些,吞吐量相对会降低(因为cpu的总量是一定的)

过程:(不能完全消除全局停顿)
在这里插入图片描述

在这里插入图片描述

5.4 tomcat实例

JMeter吞吐量测试工具

6. 类装载器classLoader???(不太懂)

6.1 class装载验证流程

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

【加载】
获取类的二进制流,转为方法区的数据结构,在java堆中生成对应的java.lang.Class对象

【链接】

验证:是为了保证Class流的格式是正确的(比如在方法区是都有对应的类啊什么的,final 被继承啊肯定是不行的)

准备:分配内存,并为类设置初始值(在方法区中)
public static int v=1;在准备阶段v=0,在接下来的初始化阶段clinit才是1
但如果是public static final int v=0,在准备阶段就已经变成1了

解析:符号引用替换为直接引用

【初始化 】
clinit:class init
static变量赋值语句 static{}语句执行一次
子类的clinit调用之前父类的clinit必须已经调用了
clinit是线程安全的

java.lang.NoSuchFieldError?

6.2 什么是类装载器ClassLoader

是一个抽象类
classLoader的实例读入java字节码,将类装 载到JVM中,负责装载过程的加载阶段
在这里插入图片描述

6.3 JDK中ClassLoader默认设计模式

有哪些ClassLoader呢?
BootStrap ClassLoader(启动ClassLoader)启动rt jar包中的类
Extension ClassLoader(扩展CLassLoader)启动javahome/lib/ext下面的jar包中的类
App ClassLoader(引用ClassLoader、系统ClassLoader) 启动自己classPath下面的类
Custom ClassLoader(自定义ClassLoader)
每一个ClassLoader都有一个Parent作为父亲,启动的没有
在这里插入图片描述

在加载的时候 boot没有办法看到下一层的extension 因为每一个类里面只有一个Parent(去寻找父类的委托对象)
解决方式:Thread.setContextClassLoader

7 java堆分析

在这里插入图片描述

【补充】

  • 常量池
    java虚拟机缓存了Integer、Byte、Short、Character、Boolean包装类在-128~127之间的值,如果取值在这个范围内,会从int常量池取出一个int并自动装箱成Integer,超出这个范围就会重新创建一个。

  • 年轻代、年老代和永久代(持久代)
    young generation \\ old(tenured) generation \\ perm area
    对堆内存的一个划分,划分目的是:优化GC性能
    在eden诞生,满了并还存活进入survivor1,满了并还存活进入survivor2,满了并还存活进入tenured。
    生命周期比较长的能够存活到最后
    perm:不属于堆空间。用于存放静态文件,java的类或者方法等等,基本不参与GC(例外:如热加载的类数据)
    survivor0 survivor1之间会有个互斥算法(一般一个是from 一个是to)
    在这里插入图片描述

  • 逃逸对象:线程之间的公有对象

  • volatile关键字
    线程对共享变量的修改,其他线程如果需要实时可见,需要对该共享变量加上volatile关键字
    在这里插入图片描述
    如果不加volatile,只在线程的自己的栈区进行查看值,没有办法进行更新。

  • oom和sof

oom:outOfMemoryException && sof:stackOutFlowException
https://blog.csdn.net/wenjieyatou/article/details/79131371

OOM主要是内存泄漏和内存溢出
在这里插入图片描述

SOF主要是发生在递归过程中,线程请求的栈深度大于虚拟机所允许的深度
在这里插入图片描述

  • 可见性、有序性、

【可见性】
方法往往是final,synchronized,volatile

【有序性 (指令重排、主内存同步延时)】
在线程内部都是有序的,在外部的线程观看,是无序的
可重排语句(读后读)如a=1,b=2编译器可以根据需要,和对性能的提升角度进行重排
但很多语句是不能重排(读后写,写后读,写后写)的,如a=1;b=a;
在这里插入图片描述
解决方式:在write函数前面加上synchronized关键字(性能不一定好,因为syn基本是串行)

  • finalize()方法 && 对象复活
    object中有finalize方法(protected):
protected void finalize() throws Throwable { }

java不保证finalize方法被及时执行,甚至不保证被执行,因为GC是不受程序控制的,可以用try_catch_finally代替

对象的finalize只能调用一次(不要理解对等于c++中的析构函数)

  • GC参数整理
    在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值