一文读懂jvm

1.什么是JVM?

JVM 是java virtual machine ,java虚拟机,提供java运行环境
(1)jvm是一种用于计算设备的规范,它是一个虚构出来的机器,是通过在实际的计算机上仿真模拟各种功能实现的。
(2)jvm包含一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个存储方法域。
(3)JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。

2.JVM内存模型

3.类加载的流程

加载,验证,准备,解析,初始化,使用,卸载

4.类加载器

1.applicationClassLoader,
应用类加载器----主要加载自己写的类
2.bootstrapClassLoader,
根加载器—加载lib包下的jar,包括java.lang下的String,Long,Boolean等包装类型,用于数学计算的math包
3.extensonClassLoader
扩展类加载器–负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
自定义加载器
想要自定义类加载器,必须继承ClassLoader类,重写里面的findClass()方法,使用时只需创建自定义ClassLoader对象,调用父类ClassLoader的loadClass方法

package com;
 
import java.io.*;
 
public class MyClassLoader extends ClassLoader{
    private String codePath;
 
    public MyClassLoader(ClassLoader parent, String codePath) {
        super(parent);
        this.codePath = codePath;
    }
    public MyClassLoader( String codePath) {
        this.codePath = codePath;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        BufferedInputStream bis=null;
        ByteArrayOutputStream bos=null;
        /*
            File.separator代表的是//
            codePath是 C:\\Users\\19878\\IdeaProjects\\learn\\
            假如 测试时传过来的name是 com.TwoNum 是包名+类名
            codePath 拼接后是 C:\\Users\\19878\\IdeaProjects\\learn\\com\\TwoNum.class
            需要把编译后的TwoNum.class 文件放在项目下自己建个com包,把TwoNum.class放进去
        */
        codePath=codePath+name.replace(".", File.separator)+".class";
        byte[] bytes=new byte[1024];
        int line=0;
        try{
            //读取编译后的文件
            bis=new BufferedInputStream(new FileInputStream(codePath));
            bos=new ByteArrayOutputStream();
            while((line= bis.read(bytes))!=-1){
                bos.write(bytes,0,line);
            }
            bos.flush();
            bytes=bos.toByteArray();
            return defineClass(null,bytes,0,bytes.length);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                bis.close();
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
 
        }
        return super.findClass(name);
    }
 
}

5.什么是双亲委派?

当一个类加载器收到了类加载的请求的时候,
他不会直接去加载指定的类,
而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。

6.如何反双亲委派模式?

使用自定义的classLoader去加载class

变量存放的位置:
一、方法中声明的变量,即该变量是局部变量,其所在方法中声明的变量就放在方法栈中,当方法
结束系统会释放方法栈,方法栈被销毁。
​ (1)当声明是基本类型的变量的时,其变量是放在JAVA虚拟机栈中。
​ (2)当声明的是引用类型变量时,使用new 创建的对象都是在堆上保存,堆是随程序开始运行而创建,随着程序的推出而销毁。
二:在类中声明的变量是成员变量,也叫全局变量,放在堆中的。
​ (1)当声明的是基本类型的变量其变量放在堆内存中的
​ (2)引用类型时,其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。
引用变量名和对应的对象仍然存储在相应的堆中

JVM内存参数的分类

堆:-xms
新生代: -Xmn

栈:-Xss 默认大小为 1M

方法区(元空间):-XX:MetaSpaceSize,默认初始值为21M,
-XX:MaxMetaSpaceSize 默认最大值为无限大

5.逃逸分析与标量替换是什么

判断方法有无返回值,如果有返回值,要返回的对象逃逸出了该方法,对于此类的对象要放入堆内存当中。
如果没有返回值,该对象应当仅在该方法内部存活,这时将他拆成成员变量分散为基本数据类型放入栈中,分散的过程叫做标量替换。

6.常用的调优参数有哪些?

-xx:不稳定的,后续可能被(jdk)优化掉

-xms:堆的最大值

-xmx:堆内存的最大大小

-xmn:年轻代大小

-xss:栈内存大小

7.垃圾清理算法都有哪些?

1.标记-清除
找到有垃圾的地址,先标记再清除
这时候会产生大量内存碎片,导致再为新对象分配内存时需要不断寻找连续空间(进而导致效率低下)
要删除的东西比较多,效率低
2.复制算法
将一侧的非垃圾对象移动到另一侧,再对该侧进行格式化。
因为年轻代中可能90%都是垃圾对象,所以年轻代专用
3.标记整理
将非垃圾对象挪到垃圾对象的地址上进行覆盖(通过内存的写屏障来完成),只需改变指针的引用
STOP THE WORLD机制:
进行垃圾收集工
作的时候必须暂停其他所有的工作线程( “Stop The World” ),

8.常见的垃圾收集器有哪些?

Serizal 收集器: (-XX:+UseSerialGC(年轻代) -XX:+UseSerialOldGC(老年代))
新生代采用复制算法,老年代采用标记-整理算法。
串行化单线程回收,性能太差
Parallel Scavenge:
Parallel Scavenge收集器(-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代))
Parallel收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算
法、回收策略等等)和Serial收集器类似。默认的收集线程数跟cpu核数相同,当然也可以用参数(-
XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改。
Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是用户线程的停
顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值
ParNew:(-XX:+UseParNewGC)
ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。
新生代采用复制算法,老年代采用标记-整理算法。
它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器(真正意义上的并发收
集器,后面会介绍到)配合工作。

9.CMS收集器(-XX:+UseConcMarkSweepGC(old))

初始标记->并发标记->重新标记->并发清理->重新重置

初始标记: 暂停所有的其他线程(STW),并记录下gc roots直接能引用的对象,速度很快。
并发标记: 并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但
是不需要停顿用户线程, 可以与垃圾收集线程一起并发运行。因为用户程序继续运行,可能会有导致已经标记过的
对象状态发生改变。
重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对
象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要用到三
色标记里的增量更新算法(见下面详解)做重新标记。
并发清理: 开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑
色不做任何处理(见下面三色标记算法详解)。
并发重置:重置本次GC过程中的标记数据。

10.三色标记法

因为并发标记是多线程的,而且此时用户线程还在工作,此时会产生新的垃圾,所以在这个过程中是为了解决并发标记过程中的多标与漏标 的问题
白色:未扫描过,如果到扫描结束某一区域还是白色,它就是垃圾
灰色: 已经被垃圾收集器访问过,但是这个对象的引用还没有完全扫描到
黑色:访问过,已经安全的对象(不是垃圾的对象).如果有别的对象指向了该对象,那么就不用去扫描这些对象.
所有区域默认为白色,当进行gc时,根据gcroot算法,找到所有的区域,分为黑白灰三色,具体步骤如下:
找到一个非垃圾对象,连同它的引用都标为黑色,
当一个对象还有引用未扫描到就先置为灰色
一个对象如果还没扫描到就是白色.

多标-浮动垃圾?
1.并发标记开始后(还有并发清理),又产生了新的对象.置黑,下一次GC再整他
2.方法执行结束,导致局部变量被销毁,但引用的对象已经被标记未黑色了,下一轮再清理

漏标?-读写屏障

b.d=null,表示b对d没有引用
但是在程序执行过程中A.d=d,又引用到了D这个对象,这时候就会产生漏标的情况,d会被删除
ps:怎么去解决漏标的问题?
增量更新(CMS用):

原始快照(G1用):

11.g1与ZGC等是什么?实现原理及优缺点

g1默认将区域划分为2048个region,他的eden\survive区\old区是无序的,另外还引入了Humongou区,专门用于存储大对象.
另外它是针对stw时间做垃圾收集的

默认年轻代对堆内存的占比是5%,如果堆大小为4096M,那么年轻代占据200MB左右的内存,对应大概是100个Region,可以通过“-XX:G1NewSizePercent”设置新生代初始占比,在系统运行中,JVM会不停的给年轻代增加更多的Region,但是最多新生代的占比不会超过60%,可以通过“-XX:G1MaxNewSizePercent”调整。年轻代中的Eden和Survivor对应的region也跟之前一样,默认8:1:1,假设年轻代现在有1000个region,eden区对应800个,s0对应100个,s1对应100个。
一个Region可能之前是年轻代,如果Region进行了垃圾回收,之后可能又会变成老年代,也就是说Region的区域功能可能会动态变化。

主流程
初始标记->并发标记->最终标记->筛选回收

什么场景适合使用G1
50%以上的堆被存活对象占用
对象分配和晋升的速度变化非常大
垃圾回收时间特别长,超过1秒
8GB以上的堆内存(建议值)
停顿时间是500ms以内

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值