jvm

jvm工具

命令行工具
jps,查看虚拟机进程id
jstat,查看内存,gc信息
jinfo,查看jvm参数值
jmap,生成堆转储快照
jhat,分析jmap生成的堆转储快照。访问localhost:7000
jstack,生成线程快照

可视化工具
jconsole,查看内存变化
visualvm,生成堆转储快照

jvm内存结构和存储内容

sof:StackOverFlowError
oom: OutOfMemoryError

程序计数器:线程私有。
如果是java方法,存储当前线程的字节码指令地址。如果是本地方法,计数器为空
唯一没有oom的区域

虚拟机栈:线程私有。存储基本类型变量值,对象的引用。
可能sof和oom。请求的栈深度大于虚拟机允许的栈深度,报sof。
虚拟机动态扩展时,没有足够的内存,报oom。

本地方法栈:线程私有。存储本地方法。和虚拟机栈一样,报sof和oom

每个线程都有一个程序计数器,虚拟机栈,本地方法栈。

堆:线程共享。存储对象。报oom

方法区:存储类,类的静态变量,常量。报oom

常量池:方法区内。报oom

怎么打出线程栈信息

jstack [option] pid
-l long listings,会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况
-m mixed mode,不仅会输出Java堆栈信息,还会输出C/C++堆栈信息(比如Native方法)

吞吐量优先和响应优先的垃圾收集器选择

吞吐量优先的并行收集器
吞吐量:运行用户代码时间/(运行用户代码时间+垃圾回收时间)
参数配置:
1, -Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseParallelGC -XX:ParallelGCThreads=8
说明:选择Parallel Scavenge收集器,然后配置多少个线程进行回收,最好与处理器数目相等。

2,-Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseParallelGC -XX:ParallelGCThreads=8 -XX:+UseParallelOldGC
说明:配置老年代使用Parallel Old

响应时间优先的并发收集器
1, -Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
说明:设置老年代的收集器是CMS,年轻代是ParNew

强引用、软引用、弱引用、虚引用以及他们之间和gc的关系

强引用:new出的对象之类的引用,
只要强引用还在,永远不会回收
软引用:引用但非必须的对象,内存溢出异常之前,回收
弱引用:非必须的对象,对象能生存到下一次垃圾收集发生之前。
虚引用:对生存时间无影响,在垃圾回收时得到通知。

简单说说你了解的类加载器,可以打破双亲委派么,怎么打破

双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。
在这里插入图片描述

一般的场景中使用Java默认的类加载器即可,但有时为了达到某种目的又不得不实现自己的类加载器,例如为了达到类库的互相隔离,例如为了达到热部署重加载功能。这时就需要自己定义类加载器,每个类加载器加载各自的类库资源,以此达到资源隔离效果。在对资源的加载上可以沿用双亲委派机制,也可以打破双亲委派机制。
一、沿用双亲委派机制自定义类加载器很简单,只需继承ClassLoader类并重写findClass方法即可
二、打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法,如下例子:

JVM内存模型的相关知识了解多少,比如重排序,,happen-before,主内存,工作内存等

重排序:jvm虚拟机允许在不影响代码最终结果的情况下,可以乱序执行。

happens-before原则:

1:一个线程的A操作总是在B之前,那多线程的A操作肯定实在B之前。
2:monitor 再加锁的情况下,持有锁的肯定先执行。
3:volatile修饰的情况下,写先于读发生
4:线程启动在一起之前 strat
5:线程死亡在一切之后 end
6:线程操作在一切线程中断之前
7:一个对象构造函数的结束都该对象的finalizer的开始之前
8:传递性,如果A肯定在B之前,B肯定在C之前,那A肯定是在C之前。

主内存:所有线程共享的内存空间
工作内存:每个线程特有的内存空间

内存屏障

内存屏障,又称内存栅栏,是一个CPU指令,基本上它是一条这样的指令:
1、保证特定操作的执行顺序。
2、影响某些数据(或则是某条指令的执行结果)的内存可见性。

编译器和CPU能够重排序指令,保证最终相同的结果,尝试优化性能。插入一条Memory Barrier会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。
Memory Barrier所做的另外一件事是强制刷出各种CPU cache,如一个 Write-Barrier(写入屏障)将刷出所有在 Barrier 之前写入 cache 的数据,因此,任何CPU上的线程都能读取到这些数据的最新版本。

volatile是基于Memory Barrier实现的。
如果一个变量是volatile修饰的,JMM会在写入这个字段之后插进一个Write-Barrier指令,并在读这个字段之前插入一个Read-Barrier指令。
这意味着,如果写入一个volatile变量a,可以保证:
1、一个线程写入变量a后,任何线程访问该变量都会拿到最新值。
2、在写入变量a之前的写入操作,其更新的数据对于其他线程也是可见的。因为Memory Barrier会刷出cache中的所有先前的写入。

happens-before

在JMM中,如果一个操作的执行结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,这个的两个操作既可以在同一个线程,也可以在不同的两个线程中。
与程序员密切相关的happens-before规则如下:
1、程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意的后续操作。
2、监视器锁规则:对一个锁的解锁操作,happens-before于随后对这个锁的加锁操作。
3、volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读。
4、传递性规则:如果 A happens-before B,且 B happens-before C,那么A happens-before C。
注意:两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行!仅仅要求前一个操作的执行结果,对于后一个操作是可见的,且前一个操作按顺序排在后一个操作之前。

当出现了内存溢出,你怎么排错。

1.首先控制台查看错误日志
2.然后使用jdk自带的jvisualvm工具查看系统的堆栈日志
3.定位出内存溢出的空间:堆,栈还是永久代(jdk8以后不会出现永久代的内存溢出)。
4.如果是堆内存溢出,看是否创建了超大的对象
5.如果是栈内存溢出,看是否创建了超大的对象,或者产生了死循环。

垃圾回收算法的实现原理。

1.引用计数算法
常用的垃圾回收算法有两种: 引用计数和可达性分析

对于一个A对象,只要有任何一个对象引用了A,则A的引用计算器就加1,当引用失效时,引用计数器减1.只要A的引用计数器值为0,则对象A就不可能再被使用。
虽然其思想实现都很简单(为每一个对象配备一个整型的计数器),但是该算法却存在两个严重的问题:
1) 无法处理循环引用的问题,因此在Java的垃圾回收器中,没有使用该算法。
2) 引用计数器要求在每次因引用产生和消除的时候,需要伴随一个加法操作和减法操作,对系统性能会有一定的影响。

可达性分析:就是通过一系列GC ROOT的对象作为起点,向下搜索,搜索所有没有与当前对象GC ROOT 有引用关系的对象。这些对象就是可以GC的

2.复制算法
将原有的内存空间分为两块相同的存储空间,每次只使用一块,在垃圾回收时,将正在使用的内存块中存活对象复制到未使用的那一块内存空间中,之后清除正在使用的内存块中的所有对象,完成垃圾回收。

3.标记清除算法
标记阶段和清除阶段。
在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象,因此未被标记的对象就是未被引用的垃圾对象。然后在清除阶段,清除所有未被标记的对象。这种方法可以解决循环引用的问题,只有两个对象不可达,即使它们互相引用也无济于事。也是会被判定位不可达对象。
标记清除算法可能产生的最大的问题就是空间碎片。

标记清除算法执行完成后,再进行一次内存碎片的整理
5.分代收集算法
新生代的特点是:对象朝生夕灭,大约90%的对象会很快回收,因此,新生代比较适合使用复制算法。
老年代的存活率是很高的,如果依然使用复制算法回收老年代,将需要复制大量的对象。这种做法是不可取的,根据分代的思想,对老年代的回收使用标记清除或者标记压缩算法可以提高垃圾回收效率。
6.分区算法
整个堆空间划分为连续的不同小区间,每一个小区间都独立使用,独立回收。

你知道哪几种垃圾收集器,各自的优缺点,重点讲下 cms,包括原理,流程,优缺点

串行垃圾收集器:收集时间长,停顿时间久
并发垃圾收集器:碎片空间多
CMS:并发标记清除。他的主要步骤有:初始收集,并发标记,重新标记,并发清除(删除),重置
G1:主要步骤:初始标记,并发标记,重新标记,复制清除(整理)

CMS的缺点是对cpu的要求比较高。G1是将内存化成了多块,所有对内段的大小有很大的要求
CMS是清除,所以会存在很多的内存碎片。G1是整理,所以碎片空间较小

JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的JVM参数

对象晋升老生代一共有三个可能:
1.当对象达到成年,经历过15次GC(默认15次,可配置),对象就晋升为老生代
2.大的对象edian去放不下,会直接在老生代创建
3.新生代跟幸存区内存不足时,对象可能晋升到老生代

jvm参数:
-Xmn:设置年轻代
-Xms:初始堆大小
-Xmx:堆最大内存
-Xss:栈内存
-XX:PermSize 初始永久带内存
-XX:MaxPermSize 最大永久带内存

JVM为什么有1个Eden区和2个Survivor区

为了保证任何时候总有一个survivor是空的。
因为将eden区的存活对象复制到survivor区时,必须保证survivor区是空的,如果survivor区中已有上次复制的存活对象时,这次再复制的对象肯定和上次的内存地址是不连续的,会产生内存碎片,浪费survivor空间。

如果只有一个survivor区,第一次GC后,survivor区非空,eden区空,为了保证第二次能复制到一个空的区域,新的对象必须在survivor区中出生,而survivor区是很小的,很容易就会再次引发GC。

而如果有两个survivor区,第一次GC后,把eden区和survivor0区一起复制到survivor1区,然后清空survivor0和eden区,此时survivor1非空,survivor0和eden区为空,下一次GC时把survivor0和survivor1交换,这样就能保证向survivor区复制时始终都有一个survivor区是空的,也就能保证新对象能始终在eden区出生了。

什么情况下会发生栈内存溢出,堆内存溢出

递归方法的递归调用,发生栈内存溢出

    private int i = 0;
    public void a(){
        System.out.println(i++);
        a();
    }
    public static void main(String[] args) {
        SofTest j = new SofTest();
        j.a();
    }

在这里插入图片描述

不停new对象添加到list中,发生堆内存溢出。
引用一直在list中用到,所以对象不会被jvm回收

    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        while (true) {
            Object o = new Object();
            list.add(o);
        }
    }

在这里插入图片描述

gc roots根搜索对象

栈中变量引用的对象
类静态变量引用的对象
常量引用的对象

垃圾回收算法

年轻代:复制算法
老年代:标记清除算法和标记整理算法

垃圾回收器

3.1.串并行分类
3.1.1.串行收集器
单线程处理所有gc, 会暂停程序
Serial年轻代, 复制算法
Serial Old老年代, 单线程, 标记-整理算法
3.1.2.并行收集器
多线程处理. 也会暂停程序
ParNew年轻代, Serial的多线程版本. 复制算法.

Parallel Scavenge年轻代, 复制算法, 吞吐量优先.
吞吐量=程序时间/总时间=(程序时间+gc时间), 如99%.
Parallel Scavenge有自适应调节策略, 使用-XX:+UseAdaptiveSizePolacy, 并行收集器自行分配年轻代和伊甸区的大小

Parallel Old, 多线程, 标记-整理算法
3.1.3.并发收集器
多线程
用户线程和gc线程同时执行, 程序不停止.
3.1.3.1.Cms并发标记清除收集器
停顿时间短, 适用于对响应时间要求高的大规模应用.
标记-清除算法.
初始标记,并发标记, 标记GC Roots关联对象,需要停止用户程序, 时间很短.
重新标记, 并发清除, 不需要停止用户程序.

参数:
-XX:+UseConcMarkSweepGC, 启动cms.
-XX:+UseCMSCompactAtFullcollection
打开老年代压缩, 影响性能, 但是会消除碎片.
-XX:UseCMSFullGCsBeforeCompaction=20
老年代cms并发收集器, gc20次以后压缩老年代
-XX:+CMSParallelRemarkEnabled, 为了减少第二次暂停的时间, 应该并发标记吧, 开启并行remark.
-XX:+CMSScavengeBeforeRemark, 在remark之前开启minor gc, 但是remark之后又会minor gc.
-XX:CMSInitiatingOccupancyFraction=80, cms在老年代沾满80%时gc, 默认是68%.

3.1.4.G1收集器
标记-整理算法. 年轻代, 老年代都能使用.

3.2.内存分类
3.2.1.年轻代收集器
年轻代收集器都使用复制算法.
Serial串行收集器, 单线程
ParNew并行收集器, 多线程
Parallel Scavenge并行收集器, 多线程, 吞吐量高, 内存分配自适应策略
3.2.2.老年代收集器
老年代收集器都使用标记-清除或标记-整理算法.
Serial Old串行收集器, 单线程
Parallel Old并行收集器, 多线程.
Cms并发收集器, 多线程, 停顿时间短

CMS和G1的区别

CMS:以获取最短回收停顿时间为目标的收集器,基于并发“标记清除”实现
过程:
1、初始标记:独占PUC,仅标记GCroots能直接关联的对象
2、并发标记:可以和用户线程并行执行,标记所有可达对象
3、重新标记:独占CPU(STW),对并发标记阶段用户线程运行产生的垃圾对象进行标记修正
4、并发清除:可以和用户线程并行执行,清理垃圾

1、3两个阶段,初始标记和重新标记是串行
2、4两个阶段,并发标记和并发标记是并行,名称都是以并发开头的

优点:
并发,低停顿

缺点:
1、对CPU非常敏感:在并发阶段虽然不会导致用户线程停顿,但是会因为占用了一部分线程使应用程序变慢
2、无法处理浮动垃圾:在最后一步并发清理过程中,用户线程执行也会产生垃圾,但是这部分垃圾是在标记之后,所以只有等到下一次gc的时候清理掉,这部分垃圾叫浮动垃圾
3、CMS使用“标记-清除”法会产生大量的空间碎片,当碎片过多,将会给大对象空间的分配带来很大的麻烦,往往会出现老年代还有很大的空间但无法找到足够大的连续空间来分配当前对象,不得不提前触发一次FullGC,为了解决这个问题CMS提供了一个开关参数,用于在CMS顶不住,要进行FullGC时开启内存碎片的合并整理过程,但是内存整理的过程是无法并发的,空间碎片没有了但是停顿时间变长了

CMS 出现FullGC的原因:
1、年轻带晋升到老年带没有足够的连续空间,很有可能是内存碎片导致的
2、在并发过程中JVM觉得在并发过程结束之前堆就会满,需要提前触发FullGC

G1:是一款面向服务端应用的垃圾收集器
特点:
1、并行于并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。

2、分代收集:分代概念在G1中依然得以保留。虽然G1可以不需要其它收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。也就是说G1可以自己管理新生代和老年代了。

3、空间整合:由于G1使用了独立区域(Region)概念,G1从整体来看是基于“标记-整理”算法实现收集,从局部(两个Region)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片。

4、可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用这明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

与其它收集器相比,G1变化较大的是它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留了新生代和来年代的概念,但新生代和老年代不再是物理隔离的了它们都是一部分Region(不需要连续)的集合。同时,为了避免全堆扫描,G1使用了Remembered Set来管理相关的对象引用信息。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏了。

如果不计算维护Remembered Set的操作,G1收集器的运作大致可划分为以下几个步骤:

1、初始标记(Initial Making)
2、并发标记(Concurrent Marking)
3、最终标记(Final Marking)
4、筛选回收(Live Data Counting and Evacuation)
2并发标记是并行的, 其他都是串行的

看上去跟CMS收集器的运作过程有几分相似,不过确实也这样。初始阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可以用的Region中创建新对象,这个阶段需要停顿线程,但耗时很短。并发标记阶段是从GC Roots开始对堆中对象进行可达性分析,找出存活对象,这一阶段耗时较长但能与用户线程并发运行。而最终标记阶段需要吧Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但可并行执行。最后筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,这一过程同样是需要停顿线程的,但Sun公司透露这个阶段其实也可以做到并发,但考虑到停顿线程将大幅度提高收集效率,所以选择停顿。下图为G1收集器运行示意图:
在这里插入图片描述

Minor GC、Major GC和Full GC之间的区别

Minor GC:年轻代
触发条件为:当产生一个新对象,新对象优先在Eden区分配。如果Eden区放不下这个对象,虚拟机会使用复制算法发生一次Minor GC,清除掉无用对象,同时将存活对象移动到Survivor的其中一个区(fromspace区或者tospace区)。虚拟机会给每个对象定义一个对象年龄(Age)计数器,对象在Survivor区中每“熬过”一次GC,年龄就会+1。待到年龄到达一定岁数(默认是15岁),虚拟机就会将对象移动到年老代。如果新生对象在Eden区无法分配空间时,此时发生Minor GC。发生MinorGC,对象会从Eden区进入Survivor区,如果Survivor区放不下从Eden区过来的对象时,此时会使用分配担保机制将对象直接移动到年老代。

Major GC和Full GC相同:整个堆,包括年轻代,老年代和永久代。常伴随着Minor GC

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值