关于jvm:Java虚拟机JVM面试题2021最新版

Java内存区域说一下 JVM 的次要组成部分及其作用? JVM蕴含两个子系统和两个组件,两个子系统为Class loader(类装载)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。 Class loader(类装载):依据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。Execution engine(执行引擎):执行classes中的指令。Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。Runtime data area(运行时数据区域):这就是咱们常说的JVM的内存。作用 :首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的办法区内,而字节码文件只是 JVM 的一套指令集标准,并不能间接交给底层操作系统去执行,因而须要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层零碎指令,再交由 CPU 去执行,而这个过程中须要调用其余语言的本地库接口(Native Interface)来实现整个程序的性能。 上面是Java程序运行机制具体阐明 Java程序运行机制步骤 首先利用IDE集成开发工具编写Java源代码,源文件的后缀为.java;再利用编译器(javac命令)将源代码编译成字节码文件,字节码文件的后缀名为.class;运行字节码的工作是由解释器(java命令)来实现的。 从上图能够看,java文件通过编译器变成了.class文件,接下来类加载器又将这些.class文件加载到JVM中。其实能够一句话来解释:类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的办法区内,而后在堆区创立一个 java.lang.Class对象,用来封装类在办法区内的数据结构。 说一下 JVM 运行时数据区Java 虚拟机在执行 Java 程序的过程中会把它所治理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用处,以及创立和销毁的工夫,有些区域随着虚拟机过程的启动而存在,有些区域则是依赖线程的启动和完结而建设和销毁。Java 虚拟机所治理的内存被划分为如下几个区域: 不同虚拟机的运行时数据区可能稍微有所不同,但都会听从 Java 虚拟机标准, Java 虚拟机标准规定的区域分为以下 5 个局部: 程序计数器(Program Counter Register):以后线程所执行的字节码的行号指示器,字节码解析器的工作是通过扭转这个计数器的值,来选取下一条须要执行的字节码指令,分支、循环、跳转、异样解决、线程复原等根底性能,都须要依赖这个计数器来实现;Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动静链接、办法进口等信息;本地办法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 办法的,而本地办法栈是为虚拟机调用 Native 办法服务的;Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,简直所有的对象实例都在这里分配内存;办法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、动态变量、即时编译后的代码等数据。深拷贝和浅拷贝浅拷贝(shallowCopy)只是减少了一个指针指向已存在的内存地址, 深拷贝(deepCopy)是减少了一个指针并且申请了一个新的内存,使这个减少的指针指向这个新的内存, 应用深拷贝的状况下,开释内存的时候不会因为呈现浅拷贝时开释同一个内存的谬误。 ...

April 29, 2021 · 3 min · jiezi

关于jvm:JVM学习笔记五垃圾回收器和内存分配

1 起源起源:《Java虚拟机 JVM故障诊断与性能优化》——葛一鸣章节:第五章本文是第五章的一些笔记整顿。 2 概述本文次要讲述了JVM中的常见垃圾回收器,包含: 串行回收器并行回收器CMSG1另外还提及了内存调配的一些细节以及一个简略的JVM调优实战。 3 串行回收器串行回收器是指应用单线程进行垃圾回收的回收器,每次回收时,串行回收器只有一个工作线程。串行回收器作为最古老的一种回收器,特点如下: 仅仅应用单线程进行垃圾回收独占式的垃圾回收形式在串行回收器进行垃圾回收的时候,利用线程须要暂停工作直到回收实现,这种景象就是驰名的Stop-The-World,也就是STW。 串行回收器的相干参数如下: -XX:+UseSerialGC:新生代与老年代都应用串行回收器-XX:+UseParNewGC:新生代应用ParNew回收器,老年代应用串行回收器(JDK9+版本已删除该参数,因为CMS被G1代替)-XX:+UseParallelGC:新生代应用ParallelGC回收器,老年代应用串行回收器4 并行回收器并行回收期在串行回收器的根底上进行了改良,应用多个线程同时对垃圾进行回收,常见的并行回收器有: 新生代ParNew回收器新生代ParallelGC回收器老年代ParallelOldGC回收器4.1 ParNewParNew是一个工作在新生代的垃圾回收器,只是简略地将串行回收器多线程化,回收策略、算法、参数和新生代串行回收器一样。同时,ParNew也是独占式的回收器,回收过程中会STW。尽管ParNew采纳了多线程进行垃圾回收,然而在单CPU或者并发能力较弱的零碎中,并行回收器的成果有可能还要比串行回收器差。 开启ParNew能够应用如下参数: -XX:+UseParNewGC:新生代应用ParNew,老年代应用串行回收器(JDK9+已删除)-XX:+UseConcMarkSweepGC:新生代应用ParNew,老年代应用CMS(JDK9+不倡议,倡议应用默认的G1)ParNew工作时的线程数量能够应用-XX:ParallelGCThreads指定。 4.2 ParallelGCParallelGC是应用复制算法的回收器,与ParNew的相同点是,都是多线程、独占式的回收器,然而,ParallelGC会关注零碎的吞吐量,能够通过如下参数启用ParallelGC: -XX:+UseParallelGC:新生代应用ParallelGC,老年代应用串行回收器-XX:+UseParallelOldGC:新生代应用ParallelGC,老年代应用ParallelOldGCParallelGC提供了两个参数控制系统的吞吐量: -XX:+MaxGCPauseMills:设置最大垃圾回收进展工夫,一个大于0的整数。ParallelGC在工作的时候会调整Java堆大小或者其余参数,尽可能把进展工夫管制在MaxGCPauseMills以内,如果心愿把进展工夫设置得很小,那么可能会应用一个较小的堆,因为较小的堆回收速度快于较大的堆,但结果是可能会导致垃圾回收的次数增多,有可能会升高吞吐量-XX:+GCTimeRatio:设置吞吐量大小,是一个0-100的整数,假如为n,那么零碎将破费不超过1/(1+n)的工夫进行垃圾回收,默认值为99,也就是用于垃圾回收的工夫不得超过1/(1+99)=1%另外还有一个-XX:+UseAdaptiveSizePolicy的参数,能够开启自适应策略,开启后,新生代大小、eden区和survivor区比例、降职老年代的对象年龄等参数都会被主动调整。 4.3 ParallelOldGC从名字就能够晓得这是一个工作在老年代的ParallelGC,一样关注零碎吞吐量,应用了标记压缩法,JDK 1.6+可用。相干参数如下: -XX:+UseParallelOldGC:指定在老年代应用ParallelOldGC(同时新生代应用ParallelGC)-XX:ParallelGCThreads:设置垃圾回收时的线程数量5 CMSCMS是Concurrent Mark Sweep的缩写,能够翻译为并发标记革除,一个应用标记革除法的多线程回收器,不会回收新生代。CMS与ParallelGC/ParallelOldGC不同,CMS次要关注的是零碎进展工夫。 5.1 工作流程 具体阐明如下: 初始标记:STW,作用是标记存活的对象,内容包含老年代中的所有GC Roots(Java中的GC Roots包含虚拟机栈援用的对象、办法区中类动态属性援用的对象、办法区中的常量援用的对象、本地办法栈中JNI援用的对象),以及新生代中援用到老年代对象的对象并发标记:从初始标记阶段标记的对象开始找出所有存活的对象预清理:因为并发标记并不能标记出老年代全副的存活对象(标记的同时应用程序会扭转一些对象的援用),这个阶段是用于解决并发标记阶段因为援用关系扭转而导致没有标记到的存活对象的(能够应用-XX:-CMSPrecleaningEnabled敞开)从新标记:STW,指标是实现标记整个老年代的所有存活对象。如果此阶段破费工夫过长,能够应用-XX:+CMSScavengeBeforeRemark,在从新标记之前进行Yong GC,不过该参数有可能会导致频繁的CMS GC,起因能够戳这里并发清理:革除没有标记的对象并回收空间,当然因为这个过程是并发的,也就是用户线程也会运行,而此时产生的垃圾无奈被清理,只能留到下一次GC再清理,这部分垃圾就称为“浮动垃圾”并发重置:从新设置CMS外部的数据后果,筹备下一次CMS应用5.2 主要参数-XX:+UseConcMarkSweepGC:开启CMS-XX:ConcGCThreads/-XX:ParallelCMSThreads:设置并发线程数-XX:CMSInitiatingOccupancyFraction:回收阈值,当老年代使用率超过该值的时候就进行回收,默认为68,如果内存应用增长率过快,导致CMS执行过程中呈现内存不足的状况,CMS就会回收失败,JVM会启动老年代串行回收器进行回收,同时会触发STW,直到回收实现-XX:+UseCMSCompactAtFullCollection:因为CMS是一个并发回收器,回收后很大可能会呈现大量的内存碎片,导致离散的可用空间无奈调配给大对象,并再次触发CMS GC。应用该参数后,会在回收实现后进行一次内存压缩(体现为整顿内存碎片,非并发)-XX:CMSFullGCsBeforeCompaction:用于设定多少次CMS后,进行一次内存压缩6 G1G1是JDK7引入的垃圾回收器,在JDK9+作为默认回收器,特点包含: 并行性:回收期间能够由多个GC线程同时工作并发性:局部工作能够和应用程序同时执行,个别不会在整个回收期阻塞应用程序分代GC:兼顾新生代与老年代空间整顿:回收过程中会有适当的对象挪动可预见性:只选取局部区域进行内存回收,放大了回收范畴,同时能够管制STW工夫6.1 G1工作流程G1的回收过程可能有4个阶段: 新生代GC并发标记周期混合回收(可选)Full GC上面来别离看一下。 6.1.1 新生代GC新生代GC的工作区域是eden区以及survivor区,一旦eden区占满,新生代GC就会启动。新生代GC后,所有的eden区会被清空,老年代的区域有可能增多(因为局部survivor区或eden区的对象降职到老年代)。 比方上面是新生代G1 GC日志的一部分: [1.076s][info][gc,start ] GC(0) Pause Young (Normal) (G1 Evacuation Pause)[1.076s][info][gc,task ] GC(0) Using 2 workers of 10 for evacuation[1.079s][info][gc,phases ] GC(0) Pre Evacuate Collection Set: 0.0ms[1.079s][info][gc,phases ] GC(0) Evacuate Collection Set: 2.4ms[1.079s][info][gc,phases ] GC(0) Post Evacuate Collection Set: 0.1ms[1.079s][info][gc,phases ] GC(0) Other: 0.2ms[1.079s][info][gc,heap ] GC(0) Eden regions: 9->0(7)[1.079s][info][gc,heap ] GC(0) Survivor regions: 0->2(2)[1.079s][info][gc,heap ] GC(0) Old regions: 0->1[1.079s][info][gc,heap ] GC(0) Humongous regions: 0->0[1.079s][info][gc,metaspace ] GC(0) Metaspace: 3473K->3473K(1056768K)[1.079s][info][gc ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 9M->2M(20M) 2.689ms[1.079s][info][gc,cpu ] GC(0) User=0.00s Sys=0.00s Real=0.01s能够看到eden区域被清空,survivor区与老年区增多。 ...

April 28, 2021 · 11 min · jiezi

关于jvm:我所知道JVM虚拟机之字节码指令集与解析六操作数栈管理指令

一、操作数栈治理指令的概述如同操作一般数据结构中的堆栈那样,JVM提供的操作数栈治理指令能够间接用于操作操作数栈 常见指令包含如下内容: 将一个或两个元素从栈顶弹出,并且间接废除,采纳:pop、pop2指令 将复制栈顶的一个或两个数值,并将复制值或双份的复制值从新压入栈顶,采纳dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2指令 将栈最顶端的两个Slot数值地位替换,采纳:swap指令,Java虚拟机没有提供替换两个64位数据类型(long、 double)数值的指令 非凡指令:nop,它的字节码为ex00。和汇编语言中的nop一样,它示意什么都不做。这条指令个别可用于调试、占位等 这些指令属于通用型,对栈的压入或者弹出无需指明数据类型 不带_x 的dup指令阐明dup是复制栈顶数据并压入栈顶,个别包含两个指令:dup、dup2,系数代表要复制的slot个数 dup结尾的指令用于复制1个Slot的数据。例:1个int 或者 1个referencedup2结尾的指令用于复制2个Slot的数据。例:1个long,或2个int,或1个int + 1个float带_x 的dup指令阐明dup_x是复制栈顶数据并插入栈顶以下的某个地位,共有四个指令:dup_x1,dup2_x1,dup_x2,dup2_x2,只有将指令的dup和x的系数相加,后果即为须要插入的地位。 dup_x1插入地位:1+1=2,即栈顶2个Slot上面dup_x2插入地位:1+2=3,即栈顶3个Slot上面dup2_x1插入地位:2+1=3,即栈顶3个Slot上面dup2_x2插入地位:2+2=4,即栈顶4个slot上面pop指令与pop2指令阐明pop:将栈顶的1个s1ot数值出栈。例如1个short类型数值pop2:将栈顶的2个Slot数值出栈。例如1个double类型数值,或者2个int类型数值接下来咱们针对于操作数栈指令的根本测试,请看以下示例代码 public class stackOperateTest { public void print( ){ object obj = new object(); string info = bj.toString(); } //相似的 public void foo(){ bar(); } public long bar(){ return 0; } private long index = 0; public long nextIndex() { return index++; }}接下来咱们编译该代码,看看print办法的字节码是什么状况? 接下来咱们依据字节码指令进行图示解析剖析,具体做了哪些操作? 若咱们不须要采纳info变量接管,间接调用obj.toString办法会是什么字节码呢? 接下来咱们看看foo办法的字节码是什么状况? 接下来咱们看看nextIndex办法的字节码是什么状况? 接下来咱们依据字节码指令进行图示解析剖析,具体做了哪些操作? ...

April 27, 2021 · 1 min · jiezi

关于jvm:CMS前世今生

CMS始终是面试中的常考点,明天咱们用通俗易懂的语言简略介绍下。 垃圾回收器为什么要分辨别代? 如上图:JVM虚拟机将堆内存区域分代了,学生代是朝生夕死的区域,老年代是老不死的区域,不同的年代对象有不同个性,因而须要不同的垃圾收集器去解决。如下图,黑竖线右边的区域都是分代垃圾收集器,G1之后内存就不分代了。 单线程垃圾收集器:Serial + Serial OldSerial(SY),Serial Old(SO)是单线程垃圾收集器组合,垃圾收集线程是单线程的,随着古代内存区域越来越大,SY+SO组合曾经越来越少了。垃圾收集的单线程须要STW工夫无疑越长。这种组合比拟适合较早JDK版本。如下图,用户线程示意应用程序处理过程,垃圾收集线程示意垃圾线程清理垃圾过程,此阶段应用程序是须要期待垃圾线程STW的。 多线程垃圾收集器:PS+PO后面咱们说了,单线程垃圾收集器毛病就是当内存区域变大,收集效率会很低,那OK,摇身一变,如下图,多线程垃圾处理器。 值得注意的是:PS+PO组合是JDK1.7,JDK1.8默认垃圾收集器。通过java -XX:+PrintCommandLineFlags命令能够在Dos界面查看。如下图,该命令能够查看JVM初始化的默认参数。比方:-XX:InitialHeapSize示意初始化堆大小。 为啥蹦出来个CMS+ParNew并行处理有了,CMS+ParNew又是干嘛的?其实PO关注是吞吐量,而CMS关注是缩短STW工夫。而CMS解决流程更简单,至于ParNew,其实约等于PS,如果你留神最下面一个图,你会发现PS年老代无奈和CMS组合。所以就多进去了一个ParNew。 介绍CMS阶段CMS,全名称Concurrent Mark Sweep,中文释义并发标记革除,从名字上能够看出算法思维应用标记革除算法,上面咱们看看CMS简化解决流程。 初始标记。只标记GC root可达的第一个节点。会短暂的STW。并发标记。用户线程和垃圾线程同时进行。垃圾线程会持续向下寻找GCroot,不会有STW。但也会有两个问题。多标:之前不是垃圾,当初线程出栈援用断开了变成了垃圾。也称为浮动垃圾。错标:之前曾经被标注是垃圾,但当初从新援用。从新标记。STW工夫个别低于200毫秒。并发革除。并发革除时,因为用户线程和垃圾线程一起工作,如果CMS线程异样,可能会触发SO单线程执行。程序可能会特地迟缓。劣势:碎片重大。 总结次要简略介绍了分代垃圾回收器,特地介绍了cms执行过程,G1留下次再说吧。好了,文章有中央还写的不清晰心愿亲们加以斧正和点评,喜爱的请点赞加关注哦。点关注,不迷路,我是叫练,边叫边练,公众号【叫练】,微信号【jiaolian123abc】。祝大家生存欢快。

April 1, 2021 · 1 min · jiezi

关于jvm:ThreadLocal-慌不慌

当初略微大点的公司面试,可能会问到ThreadLocal源码实现,不过在介绍它之前,咱们先介绍JVM中援用的概念。所谓这些概念就是我所说的根底了。援用强弱关系到内存垃圾回收机会,用好援用能够加重内存压力。JVM援用一共分为4种,别离是强援用,软援用,弱援用和虚援用。 JVM援用强援用: 如上图:根援用list指向堆,始终向list增加512K的字节数组,程序几秒后会呈现堆溢出,代码中list援用称为强援用。强援用内存始终不会被开释,直到内存溢出。 软援用: 如上图代码:主函数中,软援用须要用SoftReference包装user对象,运行程序先配置初始化参数,-Xmx10M 设置堆空间为10m,设置user=null,user援用隐没,主函数第一次调用GC时,控制台输入User对象信息,阐明堆中User对象还存在,user对象并没有被回收,这是因为软援用softRef指向堆中User 对象,申请内存byte[] bytes = new byte[10249856];bytes 约6M,bytes援用对象会进入老年代,此时老年代会GC,此时控制台会输入null,阐明此时userRef失去援用,堆中的user对象都被当成垃圾回收了。为什么要设置bytes 约6M,是因为想测试一个论断:当内存不足时,GC会回收软援用。 特地留神:调试时,985设置bytes援用执行堆内存的大小,须要本人调试,才可能呈现上述后果。 弱援用 如上图代码:弱援用对象须要用WeakReference包装,GC后,弱援用对象被回收。总结一句:当GC时,不论内存够不够,弱援用会被回收。咱们的ThreadLocal就是被WeakRefernece包装。 虚援用:若有若无援用。实用于堆之外的内存。疏忽了。 小结下:强援用,软援用,弱援用,虚援用的生命力是从高往低的。生命力越低越容易被回收,强援用则无奈被回收,软援用比拟实用于缓存的场景,软援用只有内存缓和时才会被回收,弱援用只有产生GC会被回收。 ThreadLocal解析ThreadLocal是线程平安的,因为它能让每个线程都领有本人独享变量。它也能够让一个线程领有多个变量。底层应用hash表实现的数组,说白了就是一个HashMap,其中key是ThreadLocal,value就是值。应用起来很不便。 如上图代码:创立一个ThreadLocal,当调用set时,主线程就领有了本人的公有变量“叫练”了,通过get就能够取出来。但这里有个问题,源码中ThreadLocal是被WeakReference包装的。为什么要这么做!这样做的目标是为了节约内存,上面咱们具体理解下! 我问本人上面几个问题: ThreadLocal会主动回收吗?不会,当线程完结,ThreadLocal才有可能回收,留神是有可能,因为还有其余的线程援用了以后ThreadLocal。 ThreadLocal设置为弱援用的目标是什么?避免内存透露,为了回收内存。 为什么不将整个Entry设置成弱援用?因为Entry中的value可能是一个对象,而这个对象可能被其余线程援用,一旦设置Entry为WeakReference,可能导致其余线程空指针。 正确应用ThreadLocal姿态?每次应用完ThreadLocal之后,须要调用remove办法,革除以后线程的threadLocal。 总结学习不是欲速不达的,大家看如果你不去理解JVM援用,你就无奈搞清楚ThreadLocal源码。好了,文章有中央还写的不清晰心愿亲们加以斧正和点评,喜爱的请点赞加关注哦。点关注,不迷路,我是叫练,边叫边练,公众号【叫练】,微信号【jiaolian123abc】。

March 22, 2021 · 1 min · jiezi

关于jvm:JVM进阶之路二Java内存区域

1、运行时数据区Java 虚拟机定义了若干种程序运行期间会应用到的运行时数据区,其中有一些会随着虚拟机启动而创立,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和完结而创立和销毁。 依据《Java虚拟机标准》的规定,Java虚拟机所治理的内存将会包含以下几个运行时数据区域: 1.1、程序计数器程序计数器(Program Counter Register)也被称为PC寄存器,是一块较小的内存空间。 它能够看作是以后线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里,字节码解释器工作时就是通过扭转这个计数器的值来选取下一条须要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异样解决、线程复原等根底性能都须要依赖这个计数器来实现。 每一条 Java虚拟机线程都有本人的程序计数器。在任意时刻,一条 Java 虚拟机线程只会执行一个办法的代码,这个正在被线程执行的办法称为该线程的以后办法 如果这个办法不是 native 的,那 PC 寄存器就保留 Java 虚拟机正在执行的字节码指令的地址,如果该办法是 native 的,那 PC 寄存器的值是 undefined。 1.2、Java虚拟机栈与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stack)也是线程公有的,它的生命周期与线程雷同。虚拟机栈形容的是Java办法执行的线程内存模型:每个办法被执行的时候,Java虚拟机都 会同步创立一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动静连贯、办法进口等信息。每一个办法被调用直至执行结束的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 把Java内存区域能够粗略地划分为堆内存(Heap)和栈内存(Stack),其中,“栈”通常就是指这里讲的虚拟机栈,或者更多的状况下只是指虚拟机栈中局部变量表局部。 局部变量表寄存了编译期可知的各种Java虚拟机根本数据类型(boolean、byte、char、short、int、 float、long、double)、对象援用(reference类型,它并不等同于对象自身,可能是一个指向对象起始地址的援用指针,也可能是指向一个代表对象的句柄或者其余与此对象相干的地位)和returnAddress 类型(指向了一条字节码指令的地址)。 这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来示意,其中64位长度的long和 double类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间实现调配,当进入一个办法时,这个办法须要在栈帧中调配多大的局部变量空间是齐全确定 的,在办法运行期间不会扭转局部变量表的大小。 Java 虚拟机栈可能产生如下异常情况: 如果线程申请调配的栈容量超过 Java 虚拟机栈容许的最大容量时,Java 虚拟机将会抛出一个 StackOverflowError 异样。如果 Java 虚拟机栈能够动静扩大,并且扩大的动作曾经尝试过,然而目前无奈申请到足够的内存去实现扩大,或者在建设新的线程时没有足够的内存去创立对应的虚拟机栈,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异样。1.3、本地办法栈本地办法栈(Native Method Stacks)与虚拟机栈所施展的作用是十分类似的,其区别只是虚拟机栈为虚拟机执行Java办法(也就是字节码)服务,而本地办法栈则是为虚拟机应用到的本地(Native)办法服务。 Java 虚拟机标准容许本地办法栈被实现成固定大小的或者是依据计算动静扩大和膨胀的。 本地办法栈可能产生如下异常情况: 如果线程申请调配的栈容量超过本地办法栈容许的最大容量时,Java 虚拟机将会抛出一个StackOverflowError 异样。如果本地办法栈能够动静扩大,并且扩大的动作曾经尝试过,然而目前无奈申请到足够的内存去实现扩大,或者在建设新的线程时没有足够的内存去创立对应的本地办法栈,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异样。1.4、Java堆对于Java应用程序来说,Java堆(Java Heap)是虚拟机所治理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创立。此内存区域的惟一目标就是寄存对象实例,Java里“简直”所有的对象实例都在这里分配内存。 Java堆是垃圾收集器治理的内存区域,因而一些材料中它也被称作“GC堆”(Garbage Collected Heap,)。从回收内存的角度看,因为古代垃圾收集器大部分都是基于分代收集实践设计的,所以Java堆中常常会呈现“新生代”“老年代”“永恒代”“Eden空间”“From Survivor空间”“To Survivor空间”等名词,须要留神的是这些区域划分仅仅是一部分垃圾收集器的独特个性或者说设计格调而已,而非某个Java虚拟机具体 实现的固有内存布局,更不是《Java虚拟机标准》里对Java堆的进一步粗疏划分。 ...

March 20, 2021 · 2 min · jiezi

关于gc:图解垃圾算法No捡垃圾算法

对象生与死明天不是给大家介绍对象的,给大家介绍下垃圾,因为垃圾会霸占内存,需清理之,明天咱们聊聊JVM用什么形式回收垃圾的!先上图吧,咱们看看对象的生命周期。 先解释几个名词: 新生代:疾速成长,寄存年纪比拟小的对象。老生代:寄存年纪比拟大的对象。Surviror:回收新生代内存后包容其余存活的对象,分为From区和to内存区。新生的对象都在eden区,当eden区满时包容不了大的对象,会触发GC,如果对象还活着,小对象进入From区或者是to区,这两块区域有一块是空的,假如当初装对象是from区,那么,当GC后from区所有对象会复制到to区,并且清空from区域,存活对象年纪会增大一岁,当对象达到肯定年纪之后,就会进入老年代了。如果对象比拟大,Surviror装不下,会间接进入老年代,如果老年代也装不下,会报错:堆内存溢出。 简略介绍垃圾生命周期后,咱们看看垃圾清理算法。 援用计数法援用计数法怎么判断一个对象是垃圾?就看是否有援用指向该对象。援用计数法示意如果一个对象有1个援用计数器就是1,2个援用计算器是2,如果没有援用,计数器为0,也就是垃圾对象。毛病是对象互相援用,对象无奈回收。画图举个简略案例。 如上图代码:teacher持本身援用同时持有student的援用,计数器为2,student持本身援用同时持有teacher的援用,计数器为2,这叫互相援用,最终导致teacher或者student对象都无奈被回收。所以当初垃圾回收器个别不采纳援用计数法。 标记-革除法标记-革除分两步,GC线程先标记可达对象,当产生GC时,革除不可达对象。毛病是回收后内存碎片。 如上图,咱们晓得分配内存都是间断的,垃圾对象回收后,内存很不规则,不利于内存应用效率。垃圾对象是不可达的?那什么叫可达对象呢,什么叫不可达对象呢? 可达对象:从根援用搜寻,能达到的对象叫可达对象,如绿色存活对象叫可达对象。如果从根援用搜寻,不可达对象:不能达到的对象叫不可达对象。如黄色局部,就是垃圾对象,特地留神:此黄非彼黄。根援用:也叫GC root,寄存在栈中,指向堆的援用,个别用参数或者局部变量示意。理论性的货色还是比拟难了解,咱们画图示意下。 假如:new Object()对应的堆地址是0xJL。 Object object = new Object(); 栈object援用指向new Object()对应的0xJL地址,new Object()对象可达,其中object就叫做根对象。 object = null; 通知gc线程,没有援用指向0xJL了,那这块内存就有可能被标记为垃圾对象。 复制算法复制算法须要将一块空白的内存一分为二,GC后,将可达对象全副挪动到另一块内存。新生代对象朝生夕死,GC后将活着的对象挪动到另一块空内存,并将以后应用的内存清空。每次GC,周而复始。 如上图:新生代采纳复制算法,GC回收前应用from区域,GC后应用to内存区域。 标记整顿法标记整顿算法基于标记革除算法做了肯定优化,gc线程首先从根节点开始标记可达对象,并将可达对象压缩到内存顶部,最初革除边界区域,老年代对象生命周期长,比拟实用于标记整顿算法。 如上图:当老年代满了,会触发Full GC,将内存压缩整顿。 总结画图解释了几种GC算法的含意和毛病,心愿对你有帮忙,喜爱的请点赞加关注哦。点关注,不迷路,我是【叫练】公众号,微信号【jiaolian123abc】边叫边练。

March 18, 2021 · 1 min · jiezi

关于JVM:JVM进阶之路一Java虚拟机概览

1、Java简史Java语言是一门通用的、面向对象的、反对并发的程序语言。寰球从事Java相干开发的人员曾经数以百万计。 从1995年“Java”正式呈现以来,Java曾经经验了二十几年的倒退。 Java语言之所以能广受欢迎,其中的起因之一是Java是一门能够跨平台的语言。 而跨平台的个性就是通过Java虚拟机(JVM)是实现的。 2、JVM简介JVM是整个Java平台的基石。 JVM能够看作形象的计算机。编译器将Java文件编译为Java字节码文件(.class),接下来JVM对字节码文件进行解释,翻译成特定底层平台匹配的机器指令并运行。 JVM和Java语言没有必然的分割,它只与class文件格式关联。也就是任何语言,只有能编译成符合规范的字节码文件,都是能被Jvm运行的。也就是说JVM是跨语言的平台。 3、Java虚拟机标准咱们还要意识到,Java虚拟机是一种标准,它指定了Java虚拟机构造、class文件格式、类加载过程等。咱们平时所提到的Java虚拟机个别指的是一种具体的Java虚拟机的实现,例如最出名的hotspot,遵循Java虚拟机标准,甚至能够本人实现Java虚拟机。 4、Java虚拟机常见实现4.1、HotSpot VMHotSpot虚拟机是当初利用最宽泛的虚拟机,它是Sun/OracleJDK和OpenJDK中的默认Java虚拟机。 然而这款虚拟机在最后并非由Sun公司所开发,而是由一家名为“Longview Technologies”的小公司设计;甚至这个虚拟机最后并非是为Java语言而研发的,它来源于Strongtalk虚拟机。 Oracle收买Sun当前,建设了HotRockit我的项目来把原来BEA JRockit中的优良个性交融到HotSpot之中。到了2014年的JDK 8期间,外面的HotSpot就已是两者交融的后果,HotSpot在这个过程 里移除掉永恒代,排汇了JRockit的Java Mission Control监控工具等性能。 得益于Sun/OracleJDK在Java利用中的统治位置,HotSpot天经地义地成为全世界应用最宽泛的Java 虚拟机,是虚拟机家族中毫无争议的“武林盟主”。 4.2、BEA JRockit/IBM J9 VM历史上除了Sun/Oracle公司以外,也有其余组织、公司开发过虚拟机的实现。除了HotSpot之外,BEA JRockit和IBM J9 VM已经与HotSpot并称“三大商业Java虚拟机”,它们别离是BEA System公司和 IBM公司开发。 除BEA和IBM公司外,其余一些大公司也号称有本人的专属JDK和虚拟机,然而它们要么是通过从Sun/Oracle公司购买版权的形式取得的(如HP、SAP等),要么是基于OpenJDK我的项目改良而来的 (如阿里巴巴、Twitter等),都并非本人独立开发。 5、JDK&JRE&JVMJDK&JRE&JVM三者经常被用来比拟。 JDK(Java Development Kit Java 开发工具包),JDK 是提供给 Java 开发人员应用的,其中蕴含了 Java 的开发工具,也包含了 JRE。其中的开发工具包含编译工具(javac.exe) 打包工具(jar.exe)等。JRE(Java Runtime Environment Java 运行环境) 是 JDK 的子集,也就是包含 JRE 所有内容,以及开发应用程序所需的编译器和调试器等工具。JRE 提供了库、Java 虚拟机(JVM)和其余组件,用于运行 Java 编程语言、小程序、应用程序。JVM(Java Virtual Machine Java 虚拟机),JVM 能够了解为是一个虚构进去的计算机,具备着计算机的根本运算形式,它次要负责把 Java 程序生成的字节码文件。三者关系简图如下: ...

March 17, 2021 · 1 min · jiezi

关于jvm:叫练手把手教你读JVM之GC信息

案例家喻户晓,GC次要回收的是堆内存,堆内存中蕴含年老代和老年代,年老代分为Eden和Surivor,如下图所示。咱们用案例剖析下堆的GC信息【版本:HotSpot JDK1.8】。 /** * @author :jiaolian * @date :Created in 2021-03-15 15:02 * @description:新生代内存测试 * @modified By: * 公众号:叫练 */public class NewGenTest { public static void main(String[] args) { //每次在Eden申请1M空间 byte[] bytes = null; for (int i=0; i<5; i++) { bytes = new byte[1024*1024]; } }}案例很简略,for循环运行5次,每次在Eden申请1M空间,假如咱们调配堆内存空间是20m,并打印GC详细信息,配置过程:-XX:+PrintGCDetails -Xmx20m。 -XX:+PrintGCDetails:打印GC详细信息。-Xmx20m:调配最大堆内存空间是20m。GC详细分析运行程序,IDEA控制台打印后果如下: 第一句话:年老代GC了一次,因为第五次循环,Eden满了,年老代内存约6M(6144K),Eden回收前约5M(5057K),回收后是489K,年老代内存回收前约是5M,回收后约是2M(1783K),总堆内存大小约是20M(19968K),GC耗时0.0016002 secs。第二句话:年老代总内存约6M,应用约5M。第三句话:eden空间总内存约5M(5632K),使用率是94%。第四句话:from是512K,使用率是95%。第五句话:老年代总空间约14M(13824K),应用了1294K,这个应用空间是因为程序在Eden申请1M空间,判断空间不够,就申请from或者to空间,发现只有512K,就触发monitor GC,将1M内存申请在老年代。第六句话:元空间内存。 -Xms 初始堆大小,不够时,会主动扩大,所以个别Xms空间和Xmx最大堆空间设置成一样的。下面程序不变,设置JVM参数,-XX:+PrintGCDetails -Xmx20m -Xms5m,运行程序,局部后果如下图所示。 如上图所示:初始化堆大小是5M,新生代内存一共产生了4次GC,从上图能够剖析,Eden只有1M多内存能够被申请,所以第二次for循环申请1M空间就触发了GC,数据就被丢进老年代,间断3次后,GC堆的空间由5M变为了6M,阐明初始化堆空间不够使,能够主动扩大堆内存。 当然咱们还能够通过-Xmn 设置年老代大小。上面咱们看看年老代中,Eden和from/to区域怎么划分。 下面程序不变,设置JVM参数,-XX:+PrintGCDetails -Xmx20m -Xmn10m -XX:SurvivorRatio=2,运行程序,局部后果如下图所示。 -Xmn10m:设置新生代内存为10m。-XX:SurvivorRatio=2:设置新生代中eden和Survivor比例是2:1。 咱们设置新生代是10M,这里显示新生代大小是7.5M(7680K),实际上from/to是有一块空间是每次GC做替换的区域(不便垃圾回收),所以实际上7680K=5120+2560。5120/2560=2,也就是新生代和和Survivor空间比例。 另外还有:-XX:NewRatio:设置老年代和新生代比例,个别是1/3)。比方设置-XX:NewRatio=2 -XX:+PrintGCDetails -Xmx30m ...

March 15, 2021 · 1 min · jiezi

关于jvm:这可能是最清晰易懂的-G1-GC-资料

概述G1 (Garbage-First) 于JDK 6u14版本公布,JDK 7u4版本发行时被正式推出,在JDK9时曾经成了默认的垃圾回收器,算是CMS回收器的代替 计划(CMS在JDK9当前曾经废除) G1是一款分代的 (generational),增量的 (incremental),并行的 (parallel),移动式(evacuating)的,软实时的垃圾回收器。其最大特点是暂停工夫可配置,咱们能够配置一个最大暂停工夫,G1就会尽可能的在回收的同时保障程序的暂停工夫在容许范畴内,而且在大内存环境下体现更好。 在正式介绍G1 细节之前,先简略说说垃圾回收的一些基本知识: 垃圾回收的一些基础知识mutator在垃圾回收的里,mutator指应用程序。至于为什么叫这个奇怪的名字? mutator 是 Edsger Dijkstra 推敲进去的词,有“扭转某物”的意思。说到要扭转什么,那就是 GC 对象间的援用关系。不过光这么说可能大家还是不能了解,其实用一句话概括的话, 它的实体就是“应用程序”。这样说就容易了解了吧。GC 就是在这个mutator 外部精神饱满地 工作着。增量垃圾回收增量式垃圾回收(Incremental GC)是一种通过逐步推动垃圾回收来管制mutator 最 大暂停工夫的办法。像一些晚期的年老代垃圾回收器,都是齐全暂停的,比方Serial GC简略的说,增量垃圾回收,就是让GC程序和Mutator交替运行的方法,交替执行时,实际上垃圾是一点点回收的,所以叫“增量(Incremental)” G1就属于一款增量垃圾回收器,它通过和mutator交替运行的形式来升高因GC导致的程序暂停工夫 并行GC和并发GC并行GC(Parallel) 和并发(concurrent)GC是垃圾回收里的一个基本概念。这两个词很含糊,容易弄混同,不过在GC的畛域里,就用GC里的解释吧。 一般来说,以多线程执行的GC被称为并行/并发GC,不过这两个词在GC里意思齐全不同。 并行的GC会先暂停mutator,而后开启多个线程并行的执行GC,如下图所示: 而并发GC是在不暂停Mutator运行的同时,开启GC线程并行的执行GC,如下图所示: 并行GC的目标是晋升GC效率,缩短暂停工夫,而并发GC的目标是彻底干掉暂停工夫 可预测G1的执行流程G1 GC中的堆构造和其余回收器的有所不同,在G1中,堆被划分为N个大小的相等的区域(Region),每个区域占用一段间断的地址空间,以区域为单位进行垃圾回收,而且这个区域的大小是可配置的。在调配时,如果抉择的区域曾经满了,会主动寻找下一个闲暇的区域来执行调配。 G1是一个分代的垃圾回收器,同样的它将堆分为年老代(young)和老年代(old),将划分的区域又分为年老代区域和老年代区域。不过和其余垃圾回收器不同,G1中不同代的区域空间并不是间断的。 这里解释一下,为什么G1中不同代应用不间断的区域。因为G1 Heap中初始时只划分了区域,并没有给区域分类,在对象调配时,只须要从闲暇区域集(free-list)中选取一个存储对象即可,这样区域调配更灵便。 当产生GC时,EDEN区被清空,而后会作为一个闲暇的区域,这个区域待会可能会作为老年代,也可能会被作为Survivor区域。不过在G1在调配时还是会查看新生代的区域总大小是否超过新生代大小限度的,如果超出就会进行GC 尽管每个区域的大小是无限的,不过针对一些占用较大的大对象(humongous object),还是会存在跨区域的状况。对于跨区域的对象,会调配多个间断的区域。 G1中的区域,次要分为两种类型: 年老代区域 Eden区域 - 新调配的对象Survivor区域 - 年老代GC后存活但不须要降职的对象老年代区域 降职到老年代的对象间接调配至老年代的大对象,占用多个区域的对象G1中的堆构造如下图所示: 和其余的垃圾回收形式有所不同,G1的年老代/老年代的回收算法都是统一的,属于挪动/转移式回收算法。比方复制算法,就属于移动式回收算法,长处是没有碎片,存活的越少效率越高 RememberedSet比方在对某个区域进行回收时,首先从GC ROOT开始遍历可中转这些区域中的对象,可因为降职或者挪动的起因,这些区域中的某些对象挪动到了其余区域,可是挪动之后依然放弃着对原区域对象的援用;那么此时原区域中被援用的对象对GC ROOT来说并不能“中转”,他们被其余对象的区域援用,这个发动援用的其余对象对于GC ROOT可达。这种状况下,如果想正确的标记这种GC ROOT不可中转但被其余区域援用的对象时就须要遍历所有区域了,代价太高。 如下图所示,如果此时堆区域A进行回收,那么须要标记区域A中所有存活的对象,可是A中有两个对象被其余区域援用,这两个灰色的问号对象在区域A中对GC ROOTS来是不可达的,然而实际上这两个对象的援用对象被GC ROOTS援用,所以这两个对象还是存活状态。此时如果不将这两个对象标记,那么就会导致标记的脱漏,可能造成误回收的问题 RememberedSet(简称RS或RSet)就是用来解决这个问题的,RSet会记录这种跨代援用的关系。在进行标记时,除了从GC ROOTS开始遍历,还会从RSet遍历,确保标记该区域所有存活的对象(其实不光是G1,其余的分代回收器里也有,比方CMS) 如下图所示,G1中利用一个RSet来记录这个跨区域援用的关系,每个区域都有一个RSet,用来记录这个跨区援用,这样在进行标记的时候,将RSet也作为ROOTS进行遍历即可 所以在对象降职的时候,将降职对象记录下来,这个存储跨区援用关系的容器称之为RSet,在G1中通过Card Table来实现。 留神,这里说Card Table实现RSet,并不是说CardTable是RSet背地的数据结构,只是RSet中存储的是CardTable数据 ...

March 14, 2021 · 8 min · jiezi

关于jvm:JVM笔记-JVM的生命周期介绍

Github仓库地址:https://github.com/Damaer/Jvm... 文档地址:https://damaer.github.io/JvmN...JVM生命周期 启动执行退出启动Java虚拟机的启动时通过疏导加载器(bootstrap class loader)创立一个初始类(initial class)来实现的,这个类是由Java虚拟机的具体实现指定的。 自定义的类是由零碎类加载器加载的。自定义类的顶级父类都是Object,Object作为外围api中的类,是须要被疏导加载器(bootstrap class loader)加载的。父类的加载是优先于子类加载的,所以要加载自定义的之前,会就加载Object类。 执行Java虚拟机执行的时候有一个清晰的工作:执行Java程序。真正执行程序的是一个叫Java虚拟机的过程。退出虚拟机的退出有以下几种状况: 程序失常执行完结程序执行过程中遇到了异样或者谬误而异样终止因为操作系统呈现谬误而导致Java虚拟机过程终止某线程调用Runtime类或者System类的exit办法,或者Runtime类的halt()办法,并且Java平安管理器也容许这次操作的条件下。JNI(java native Interface):用JNI 的api加载或者卸载Java虚拟机的时候,Java虚拟机可能异样退出。System.exit()和Runtime.halt()上面剖析System.exit()和Runtime.halt(): System.exit()其实调用的是Runtime对象的exit()办法,Runtime.getRuntime()获取的是以后的运行时状态,也就是Runtime对象。 public static void exit(int status) { Runtime.getRuntime().exit(status); }看Runtime的exit()办法,外面调用的是Shutdown.exit(status)。 public void exit(int status) { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkExit(status); } Shutdown.exit(status); }咱们看Shutdown的exit()办法,当status不为0的时候,调用的是halt(status)。 static void exit(int status) { boolean runMoreFinalizers = false; synchronized (lock) { if (status != 0) runFinalizersOnExit = false; switch (state) { case RUNNING: /* Initiate shutdown */ state = HOOKS; break; case HOOKS: /* Stall and halt */ break; case FINALIZERS: if (status != 0) { /* Halt immediately on nonzero status */ halt(status); } else { /* Compatibility with old behavior: * Run more finalizers and then halt */ runMoreFinalizers = runFinalizersOnExit; } break; } } if (runMoreFinalizers) { runAllFinalizers(); halt(status); } synchronized (Shutdown.class) { /* Synchronize on the class object, causing any other thread * that attempts to initiate shutdown to stall indefinitely */ sequence(); halt(status); } }而halt(int status)实质上调用的是一个本地办法halt0(int status),暂停虚拟机过程,退出。 ...

March 9, 2021 · 1 min · jiezi

关于jvm:JVM-解构类装载子系统

一:JVM虚拟机类装载子系统在虚构中的地位: 二:类装载子系统类装载子系统三个阶段:加载 -> 链接 -> 初始化 阶段一:加载通过全限定名获取类的二进制流,把文件的静态数据构造转换为办法区运行时数据结构,并在内存中生成 java.lang.class 对象,提供拜访入口。 Java零碎提供了三种加载器:疏导类加载器,扩大类加载器,利用类加载器;用户还能够自定义本人的加载器。依照官网定义又能够分为:疏导类加载器,自定义加载器(继承自 java.lang.ClassLoader) 1.1 疏导类加载器疏导类加载器由c或c++实现的,没有父加载器。出于平安思考用来加载外围类库,只加载蕴含 java javax sum  结尾的类。 因为是c或c++实现的打印进去的类加载器名字是 null 如: java.lang.String  通过代码打印类加载正好是null 1.2 扩大类加载器 ExtClassLoader扩大类加载器由java语言编写,继承自 java.lang.ClassLoader , 父亲为疏导类加载器。从 java.ext.dirs 零碎属性或者jdk的 jre/lib/ext 子目录下加载。 咱们打印java.ext.dirs 能够看到加载的门路: 1.3 利用类加载器(零碎类加载器) AppClassLoader 利用类加载器又叫做零碎类加载器, 也是咱们编写java代码最罕用到的加载器,其父亲是 扩大类加载器 。 通过下图可知 ClassLoadTest 是由 AppClassLoad 加载器加载,甚至晓得了AppClassLoad 的父加载器是 ExtClassLoader ,ExtClassLoader 的父加载器是 null  也就是下面说的疏导类加载器。 1.4 双亲委派机制 在加载某个class文件时,默认先交给父加载器去加载, 如果父加载器加载到就实现,当父加载器加载不到时候就会交给子加载器去加载,这就是双亲委派机制。 益处: 防止反复加载。爱护程序平安,避免外围api被串改。1.5 为什么须要自定加载类?隔离加载类批改类的加载形式扩大加载源避免源代码透露阶段二:链接链接阶段蕴含:验证 -> 筹备 -> 解析 2.1 验证class文件须要合乎虚拟机的要求,验证类的正确性,保障虚构平安。如其一:class文件须要 cafebabe 结尾的魔术词。 2.2 筹备为动态变量(static润饰的变量)分配内存,并且为它设置默认初始化值。援用类型: null  浮点类型: 0.0  整型: 0  布尔型: false 。 如果动态变量被 final 润饰,那么该变量就是常量。因为 final 润饰的变量会在编译的时候 javac xx.java 就会调配值,筹备阶段就会显式初始化。如: final static int age = 18  筹备阶段间接将 age = 18  而非 age = 0  ...

March 6, 2021 · 1 min · jiezi

关于jvm:JVM学习笔记三JVM基本参数

1 起源起源:《Java虚拟机 JVM故障诊断与性能优化》——葛一鸣章节:第三章本文是第三章的一些笔记整顿。 2 GC日志:-Xlog:gc要打印GC日志的话,能够加上-Xlog:gc参数(JDK8及以下请应用-XX:+PrintGC),开启GC打印后,每次GC就会打印如下的日志(OpenJDK11 -Xlog:gc): [0.126s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 25M->0M(502M) 1.902ms[0.205s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 300M->0M(502M) 4.174ms[0.236s][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 300M->0M(502M) 2.067ms[0.268s][info][gc] GC(3) Pause Young (Normal) (G1 Evacuation Pause) 300M->0M(502M) 2.362ms其中结尾的工夫示意产生GC的时刻,25M->0M(502M)示意: GC前,堆使用量为25MGC后,堆使用量为0M堆空间总和约为502M开端的工夫示意本次GC的耗时。 另外如果须要更加具体的参数,能够应用-Xlog:gc*(JDK8及以下请应用-XX:+PrintGCDetails),比方上面是一部分的GC日志(-Xlog:gc*): [0.137s][info][gc,start ] GC(0) Pause Young (Normal) (G1 Evacuation Pause)[0.138s][info][gc,task ] GC(0) Using 10 workers of 10 for evacuation[0.147s][info][gc,phases ] GC(0) Pre Evacuate Collection Set: 0.0ms[0.147s][info][gc,phases ] GC(0) Evacuate Collection Set: 8.8ms[0.147s][info][gc,phases ] GC(0) Post Evacuate Collection Set: 0.2ms[0.147s][info][gc,phases ] GC(0) Other: 0.8ms[0.147s][info][gc,heap ] GC(0) Eden regions: 25->0(300)[0.147s][info][gc,heap ] GC(0) Survivor regions: 0->1(4)[0.147s][info][gc,heap ] GC(0) Old regions: 0->0[0.147s][info][gc,heap ] GC(0) Humongous regions: 0->0[0.147s][info][gc,metaspace ] GC(0) Metaspace: 6633K->6633K(1056768K)[0.147s][info][gc ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 25M->0M(502M) 9.878ms[0.147s][info][gc,cpu ] GC(0) User=0.05s Sys=0.00s Real=0.01s行首的工夫:事件产生的时刻GC(0):这是第1次GC,接着会有GC(1)、GC(2)Pause Young(Normal):这次GC回收了新生代Using 10 workers:应用10个工作线程Pre Evacuate Collection Set/Evacuate Collection Set/Post Evacuate/Other:示意G1垃圾回收标记,革除算法不同阶段所破费的工夫Eden/Survivor/Old/Humongous/Metaspace:别离示意eden区、存活区、老年区、巨型对象区(就是很大很大的对象所在的区域)、元数据区在GC前后的大小25M-0M(502M):GC前堆占用25M,GC后为0M,可用堆空间为502MUser/Sys/Real:别离示意用户态CPU耗时、零碎CPU耗时、GC实在经验工夫如果想查看更全面的堆信息,能够应用Visual VM,将在后续文章中叙述。 ...

March 5, 2021 · 7 min · jiezi

关于jvm:从JVM底层原理分析数值交换那些事

根底数据类型替换这个话题,须要从最最根底的一道题目说起,看题目:以下代码a和b的值会替换么: public static void main(String[] args) { int a = 1, b = 2; swapInt(a, b); System.out.println("a=" + a + " , b=" + b); } private static void swapInt(int a, int b) { int temp = a; a = b; b = temp; } 后果预计大家都晓得,a和b并没有替换: integerA=1 , integerB=2然而起因呢?先看这张图,先来说说Java虚拟机的构造: 运行时区域次要分为: 线程公有: 程序计数器:Program Count Register,线程公有,没有垃圾回收虚拟机栈:VM Stack,线程公有,没有垃圾回收本地办法栈:Native Method Stack,线程公有,没有垃圾回收线程共享: 办法区:Method Area,以HotSpot为例,JDK1.8后元空间取代办法区,有垃圾回收。堆:Heap,垃圾回收最重要的中央。和这个代码相干的次要是虚拟机栈,也叫办法栈,是每一个线程公有的。生命周期和线程一样,次要是记录该线程Java办法执行的内存模型。虚拟机栈外面放着好多栈帧。留神虚拟机栈,对应是Java办法,不包含本地办法。 一个Java办法执行会创立一个栈帧,一个栈帧次要存储: 局部变量表操作数栈动静链接办法进口每一个办法调用的时候,就相当于将一个栈帧放到虚拟机栈中(入栈),办法执行实现的时候,就是对应着将该栈帧从虚拟机栈中弹出(出栈)。 每一个线程有一个本人的虚拟机栈,这样就不会混起来,如果不是线程独立的话,会造成调用凌乱。 大家平时说的java内存分为堆和栈,其实就是为了简便的不太谨严的说法,他们说的栈个别是指虚拟机栈,或者虚拟机栈外面的局部变量表。 局部变量表个别寄存着以下数据: 根本数据类型(boolean,byte,char,short,int,float,long,double)对象援用(reference类型,不肯定是对象自身,可能是一个对象起始地址的援用指针,或者一个代表对象的句柄,或者与对象相干的地位)returAddress(指向了一条字节码指令的地址)局部变量表内存大小编译期间确定,运行期间不会变动。空间掂量咱们叫Slot(局部变量空间)。64位的long和double会占用2个Slot,其余的数据类型占用1个Slot。 下面的办法调用的时候,实际上栈帧是这样的,调用main()函数的时候,会往虚拟机栈外面放一个栈帧,栈帧外面咱们次要关注局部变量表,传入的参数也会当成局部变量,所以第一个局部变量就是参数args,因为这个是static办法,也就是类办法,所以不会有以后对象的指针。 如果是一般办法,那么局部变量表外面会多出一个局部变量this。 如何证实这个货色真的存在呢?咱们大略看看字节码,因为局部变量在编译的时候就确定了,运行期不会变动的。上面是IDEA插件jclasslib查看的: ...

March 5, 2021 · 2 min · jiezi

关于jvm:JVM学习笔记二JVM基本结构

1 起源起源:《Java虚拟机 JVM故障诊断与性能优化》——葛一鸣章节:第二章本文是第二章的一些笔记整顿。 2 JVM基本参数-Xmxjava命令的个别模式如下: java [-options] class [args..]其中-options示意JVM启动参数,class为带有main()的Java类,args示意传递给main()的参数,也就是main(String [] args)中的参数。 个别设置参数在-optinos处设置,先看一段简略的代码: public class Main { public static void main(String[] args) { for(int i=0;i<args.length;++i) { System.out.println("argument "+(i+1)+" : "+args[i]); } System.out.println("-Xmx "+Runtime.getRuntime().maxMemory()/1024/1024+" M"); }}设置应用程序参数以及JVM参数: 输入: 能够看到-Xmx32m传递给JVM,使得最大可用堆空间为32MB,参数a作为应用程序参数,传递给main(),此时args.length的值为1。 3 JVM根本构造 各局部介绍如下: 类加载子系统:负责从文件系统或者网络中加载Class信息,加载的类信息寄存在一个叫办法区的内存空间中办法区:除了蕴含加载的类信息之外,还蕴含运行时常量池信息,包含字符串字面量以及数字常量Java堆:在虚拟机启动时建设,是最次要的内存工作区域,简直所有的Java对象实例都存在于Java堆中,堆空间是所有线程共享的间接内存:是在Java堆外的,间接向零碎申请的内存区域。NIO库容许Java程序应用间接内存,通常间接内存的访问速度要优于Java堆。另外因为间接内存在堆外,大小不会受限于-Xmx指定的堆大小,然而会受到操作系统总内存大小的限度垃圾回收零碎:能够对办法区、Java堆和间接内存进行回收,Java堆是垃圾收集器的工作重点。对于不再应用的垃圾对象,垃圾回收零碎会在后盾默默工作、默默查找,标识并开释垃圾对象Java栈:每个JVM线程都有一个公有的Java栈,一个线程的Java栈在线程创立时被创立,保留着帧信息、局部变量、办法参数等本地办法栈:与Java栈相似,不同的是Java栈用于Java办法调用,本地办法栈用于本地办法(native method)调用,JVM容许Java间接调用本地办法PC寄存器:每个线程公有的空间,JVM会为每个线程创立PC寄存器,在任意时刻一个Java线程总是执行一个叫做以后办法的办法,如果以后办法不是本地办法,PC寄存器就会指向以后正在被执行的指令,如果以后办法是本地办法,那么PC寄存器的值就是undefined执行引擎:负责执行JVM的字节码,古代JVM为了进步执行效率,会应用即时编译技术将办法编译成机器码后执行上面重点说三局部:Java堆、Java栈以及`` 4 Java堆简直所有的对象都存在Java堆中,依据垃圾回收机制的不同,Java堆可能领有不同的构造,最常见的一种是将整个Java堆分为新生代和老年代: 新生代:寄存新生对象或年龄不大的对象,有可能分为eden、s0、s1,其中s0和s1别离被称为from和to区域,它们是两块大小相等、能够调换角色的内存空间老年代:寄存老年对象,绝大多数状况下,对象首先在eden调配,在一次新生代回收后,如果对象还存活,会进入s0或s1,之后每通过一次新生代回收,如果对象存活则年龄加1。当对象年龄达到肯定条件后,会被认为是老年对象,就会进入老年代 5 Java栈5.1 简介Java栈是一块线程公有的内存空间,如果是Java堆与程序数据密切相关,那么Java栈和线程执行密切相关,线程执行的根本行为是函数调用,每次函数调用都是通过Java栈传递的。 Java栈与数据结构中的栈相似,有FIFO的特点,在Java栈中保留的次要内容为栈帧,每次函数调用都会有一个对应的栈帧入栈,每次调用完结就有一个对应的栈帧出栈。栈顶总是以后的帧(以后执行的函数所对应的帧)。栈帧保留着局部变量表、操作数栈、帧数据等。 这里说一下题外话,置信很多读者对StackOverflowError不生疏,这是因为函数调用过多造成的,因为每次函数调用都会生成对应的栈帧,会占用肯定的栈空间,如果栈空间有余,函数调用就无奈进行,当申请栈深度大于最大可用栈深度时,就会抛出StackOverflowError。 JVM提供了-Xss来指定线程的最大栈空间。 比方,上面这个递归调用的程序: public class Main { private static int count = 0; public static void recursion(){ ++count; recursion(); } public static void main(String[] args) { try{ recursion(); }catch (StackOverflowError e){ System.out.println("Deep of calling = "+count); } }}指定-Xss1m,后果: ...

March 3, 2021 · 3 min · jiezi

关于jvm:SATB的一些理解

啃G1的时候,SATB(Snapshot At The Beginning)这个术语看我的很是迷糊。简略解释是:“GC开始时对象关联的快照”,但这个解释……貌似有点歧义。查阅了不少材料,也全都一笔带过不解释 上面联合一些前置的垃圾回收常识,总结一下我对SATB的一些了解 前置常识增量式垃圾回收增量(incremental)式垃圾回收,是指GC和Mutator交替运行的一种垃圾回收工作形式,如下图所示 Hotsopt中的G1就是一款增量式的垃圾回收器,老一代的CMS也有增量模式。在增量式垃圾回收下,能够升高Mutator的暂停工夫,只是吞吐量没那么高 三色标记法三色标记法(Tri-color marking)是Edsger W. Dijkstra 等人提出的,在增量式垃圾回收里十分有用,用于标记GC过程中不同阶段的对象的状态。 红色:还未搜寻过的对象灰色:正在搜寻的对象彩色:搜寻实现的对象GC 开始运行前所有的对象都是红色。GC 一开始运行,所有从根能达到的对象都会被标记,而后被堆到栈里。GC 只是发现了这样的对象,但还没有搜寻完它们,所以这些对象就 成了灰色对象。灰色对象会被顺次从栈中取出,其子对象也会被涂成灰色。当其所有的子对象都被涂成 灰色时,对象就会被涂成彩色。 当 GC 完结时曾经不存在灰色对象了,流动对象全副为彩色,垃圾则为红色。 这就是三色标记算法的概念。 有一点须要咱们留神,那就是为了体现彩色对象和灰色对象,不肯定要在对象头里设置标记(事实上也有通过标记来体现彩色对象和灰色对象的状况)。 在这里咱们依据对象的状况,更抽象地把对象用三个色彩体现进去。每个对象是什么样的状 况,意味着什么色彩,这些都依据算法的不同而不同。 比方在增量式的标记-革除(Mark-Sweep)算法中,能够分为以下3个阶段: 根查找阶段标记阶段革除阶段在根查找阶段,把所有能从根间接援用的对象标记为灰色。在标记阶段查找灰色对象,将其援用的对象也标记为灰色,查找完结(tracing实现)后将灰色对象标记为彩色。在最初的革除阶段,将彩色对象再标记为红色 标记脱漏如果在标记阶段执行一半暂停后,Mutator更新了对象的援用关系,就可能会导致流动对象(reachable objects)的“标记脱漏”,一旦产生了标记脱漏,就可能会在最初的革除阶段造成误回收流动对象的重大问题**比方上面这个场景,在标记过程中暂停之后,Mutator批改了援用关系: (a)是刚暂停的状态,A被标记为彩色,B被标记为灰色,接下来会对B进行遍历。此时继续执行Mutator 在(b)中,Mutator将A->B的援用,批改为A->C,而后删除了B->C的援用关系,原本是A->B->C,变成了A-C,就成了(c)图的状况 这个时候如果进行从新标记阶段就会呈现问题:B原本是灰色对象,通过遍历后就被标记为了彩色。尽管此时C是流动对象,但也不会进行搜寻了,因为没有一个灰色对象关联它,所以C就不会被标记为流动对象;那么在最初的革除阶段时,就会造成误革除。这种状况就成为标记脱漏 在下面这个例子里,造成标记脱漏的要害起因是,B->C的援用被移除了 写入屏障写入屏障(Write Barrier)在GC里并不是一个具体的算法,只是一个“形象”,其作用是在对象援用更新时减少一个“屏障”,在屏障中做一些加强操作。 上面是一段Edsger W. Dijkstra 等人提出的写入屏障实现(伪代码),在更新对象之间的援用时,须要调用write_barrier函数,从而实现“屏障”的性能。 write_barrier(obj, field, newobj){ if(newobj.mark == FALSE) //标记新对象 newobj.mark = TRUE //将新对象记录至标记栈 push(newobj, $mark_stack) *field = newobj}在write_barrier函数中,更新援用的同时,将新对象标记后再记录至标记栈里,这样的话在待会的革除阶段,产生援用变动的对象流动对象也会被失常的标记了 通过这个写入屏障的形式,就能够解决下面的标记脱漏问题,因为记录了新的援用对象,新的援用对象也会被标记为流动对象,就不会呈现标记脱漏了 汤浅太一 的算法汤浅太一 在1990年开发了另一种增量垃圾回收算法,应用汤浅太一的写入屏障算法的GC也称为“Snapshot GC”。 这是因为这种算法是以 GC 开始时对象间的援用关系(snapshot)为根底来执行 GC 的。因而,依据汤浅的算法,在 GC 开始时回收垃圾,保留 GC 开始时的流动对象和 GC 执行过程中被调配的对象。刚看这段时有点乱,SATB/Snapshot GC/Write Barrier几个概念有点混同了 ...

February 28, 2021 · 1 min · jiezi

关于jvm:深入理解Java虚拟机是怎么实现synchronized的

文章收录地址:Java-Bang专一于零碎架构、高可用、高性能、高并发类技术分享在 Java 程序中,咱们能够利用 synchronized 关键字来对程序进行加锁。它既能够用来申明一个 synchronized 代码块,也能够间接标记静态方法或者实例办法。 当申明 synchronized 代码块时,编译而成的字节码将蕴含 monitorenter 和 monitorexit 指令。这两种指令均会耗费操作数栈上的一个援用类型的元素(也就是 synchronized 关键字括号里的援用),作为所要加锁解锁的锁对象。   public void foo(Object lock) {    synchronized (lock) {      lock.hashCode();    }  }  // 下面的 Java 代码将编译为上面的字节码  public void foo(java.lang.Object);    Code:       0: aload_1       1: dup       2: astore_2       3: monitorenter       4: aload_1       5: invokevirtual java/lang/Object.hashCode:()I       8: pop       9: aload_2      10: monitorexit      11: goto          19      14: astore_3      15: aload_2      16: monitorexit      17: aload_3      18: athrow      19: return    Exception table:       from    to  target type           4    11    14   any          14    17    14   any我在文稿中贴了一段蕴含 synchronized 代码块的 Java 代码,以及它所编译而成的字节码。你可能会留意到,下面的字节码中蕴含一个 monitorenter 指令以及多个 monitorexit 指令。这是因为 Java 虚拟机须要确保所取得的锁在失常执行门路,以及异样执行门路上都可能被解锁。 ...

February 23, 2021 · 2 min · jiezi

关于jvm:聊一聊Java垃圾回收与卡表技术

文章收录地址:Java-Bang专一于零碎架构、高可用、高性能、高并发类技术分享在读博士的时候,我已经写过一个统计 Java 对象生命周期的动态分析,并且用它来跑了一些基准测试。 其中一些程序的后果,恰好验证了许多钻研人员的假如,即大部分的 Java 对象只存活一小段时间,而存活下来的小局部 Java 对象则会存活很长一段时间。(pmd 中 Java 对象生命周期的直方图,红色的示意被逃逸剖析优化掉的对象) 之所以要提到这个假如,是因为它造就了 Java 虚拟机的分代回收思维。简略来说,就是将堆空间划分为两代,别离叫做新生代和老年代。新生代用来存储新建的对象。当对象存活工夫够长时,则将其挪动到老年代。 Java 虚拟机能够给不同代应用不同的回收算法。对于新生代,咱们猜想大部分的 Java 对象只存活一小段时间,那么便能够频繁地采纳耗时较短的垃圾回收算法,让大部分的垃圾都可能在新生代被回收掉。 对于老年代,咱们猜想大部分的垃圾曾经在新生代中被回收了,而在老年代中的对象有大概率会持续存活。当真正触发针对老年代的回收时,则代表这个假如出错了,或者堆的空间曾经耗尽了。 这时候,Java 虚拟机往往须要做一次全堆扫描,耗时也将不计成本。(当然,古代的垃圾回收器都在并发收集的路线上倒退,来防止这种全堆扫描的状况。) 明天这一篇咱们来关注一下针对新生代的 Minor GC。首先,咱们来看看 Java 虚拟机中的堆具体是怎么划分的。 Java 虚拟机的堆划分后面提到,Java 虚拟机将堆划分为新生代和老年代。其中,新生代又被划分为 Eden 区,以及两个大小雷同的 Survivor 区。 默认状况下,Java 虚拟机采取的是一种动态分配的策略(对应 Java 虚拟机参数 -XX:+UsePSAdaptiveSurvivorSizePolicy),依据生成对象的速率,以及 Survivor 区的应用状况动静调整 Eden 区和 Survivor 区的比例。 当然,你也能够通过参数 -XX:SurvivorRatio 来固定这个比例。然而须要留神的是,其中一个 Survivor 区会始终为空,因而比例越低节约的堆空间将越高。通常来说,当咱们调用 new 指令时,它会在 Eden 区中划出一块作为存储对象的内存。因为堆空间是线程共享的,因而间接在这里边划空间是须要进行同步的。 否则,将有可能呈现两个对象共用一段内存的事变。如果你还记得前两篇我用“停车位”打的比如的话,这里就相当于两个司机(线程)同时将车停入同一个停车位,因此产生剐蹭事变。 Java 虚拟机的解决办法是为每个司机事后申请多个停车位,并且只容许该司机停在本人的停车位上。那么当司机的停车位用完了该怎么办呢(假如这个司机代客泊车)? 答案是:再申请多个停车位便能够了。这项技术被称之为 TLAB(Thread Local Allocation Buffer,对应虚拟机参数 -XX:+UseTLAB,默认开启)。 具体来说,每个线程能够向 Java 虚拟机申请一段间断的内存,比方 2048 字节,作为线程公有的 TLAB。 这个操作须要加锁,线程须要保护两个指针(实际上可能更多,但重要也就两个),一个指向 TLAB 中空余内存的起始地位,一个则指向 TLAB 开端。 ...

February 21, 2021 · 2 min · jiezi

关于jvm:JVM学习之路4类文件结构及加载机制

继上一篇JVM学习之路3-GC机制和GC收集器剖析介绍完垃圾回收相干内容后,这篇说说类的加载机制。咱们平时在写代码的时候更多的是和对象打交道,很少去关怀类的相干信息是怎么来的,在之前的系列文章中咱们介绍过类信息是寄存在哪的、对象是如何找到本人的类等内容,都没有具体介绍过类是如何来的,它是怎么被加载到虚拟机里的。知识点1、类文件构造2、虚拟机加载类机制 类文件构造这里整顿了一幅图先做整体展现下面图曾经对各个构造做了简略的阐明,字段表汇合是不蕴含办法的局部变量,这里再介绍一下常量池。 常量池就是指字面量和符号援用,字面量即java语言层的常量概念,符号援用如:类和接口全限定名、字段办法名称和描述符等。每种常量项都有本人的一个表构造,具体能够再看一下书里的表,这里不再做援用。个别java用Constant_utf8_info项来存类名和办法名,所以咱们平时定义类和办法的时候不能过长(65535),当然个别也不会那么长。 虚拟机加载类机制一个类从加载进虚拟机内存到从内存中卸载,整个生命周期如下:加载、验证、筹备、初始化、卸载这五个阶段的程序是确定的,以下6种状况下必须先对类进行初始化:1、遇到new、getstatic、putstatic、invokestatic指令,基本上就是new对象、静态方法调用和动态字段拜访。2、反射调用。3、父类没初始化过先做初始化。4、虚拟机启动时执行主类要先初始化。5、动静语言最终对应的办法动态调用须要先初始化。6、有默认办法的接口要在类实例化前进行初始化。 加载此阶段把类从文件的二进制流转换到办法区的运行时数据结构并生成class对象。除了从.class文件中加载类信息,咱们还能够通过网络、zip等压缩包、或者在代码中动静生成类信息。这里须要留神的是:数组类不是由类加载器加载,数组类如果放的是援用类型,该援用类型是由类加载器来加载,该数组类会被标识在该类加载器的类名称空间上,如果数组类放的不是援用类型,则会被标识在疏导类加载器上。 验证这是连贯的第一个阶段,次要是为了保障加载的类字节流合乎虚拟机标准,避免恶意代码的侵入,爱护虚拟机的平安,为什么说java是类型平安的,这部分就是次要起因,次要有如下几局部校验:1、文件格式校验,比方文件的魔数是否合乎定义。2、元数据校验,对字节码形容的信息进行校验(看字节码:javap -verbose xxxClass),比方是否继承了不被容许继承的类。3、字节码验证,通过数据流和控制流剖析来确保语义是非法的、合乎逻辑的。4、合乎援用验证,产生在虚拟机将符号援用转换为间接援用时,比方查看是否短少依赖的内部办法、类等资源。 筹备正式为类中的变量分配内存和设置初始值。举个例子: public static int value = 123;如上代码,在筹备阶段的时候会将value设置为0,而不是123,真正赋值123是在初始化阶段。当然如果是常量(用final润饰),则会间接初始化为123。 解析将常量池中符号援用替换为间接援用的过程,包含类/接口解析、办法解析、字段解析、接口办法解析等。符号援用:就是一个字面量,比方类的全限定名,只有能惟一辨认该类就行,和内存布局无关。间接援用:和内存布局相干,内存中要曾经存在该指标,间接援用相当于间接定位到该指标。 初始化类加载过程的最初一个步骤,虚拟机将主导权交给应用程序。筹备阶段在分配内存和设置初始值之后,初始化阶段就会真正设置咱们赋予的初始值,就是在执行类结构器<clinit>()办法的过程。动态语句块中只能拜访 到定义在动态语句块之前的变量,定义在它之后的变量,在后面的动态语句块能够赋值,然而不能拜访。 类加载器在下面的加载机制中提到了类加载器,它除了能够加载类以外,还能够确认一个类在虚拟机中的唯一性(对于任意一个类,都必须由加载它的类加载器和这个类自身一起独特确立其在Java虚拟机中的唯一性),同一个类被同一个虚拟机加载,只有加载器不同,那类必然也不同。 双亲委派模式java虚拟机经典的双亲委派模式。一幅图:启动类加载器(BootstrapClassLoader):负责加载寄存在 <JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的门路中寄存的类到虚拟机中。扩大类加载器(ExtClassLoader):负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs零碎变量所 指定的门路中所有的类库。应用程序类加载器(AppClassLoader):负责加载用户类门路 (ClassPath)上所有的类库。根本机制:每当加载一个类的时候,虚构机会先用启动类加载器进行加载,加载不到再用扩大类加载器,再加载不到就用应用程序类加载器进行加载。这样能够保障一个类在虚拟机里惟一。 总结本篇次要介绍了类文件构造以及类加载机制,整个类的加载过程还是比拟清晰且简略的,大家也能够本人试着定一个类加载器来加载类,跟进代码看一下双亲委派模式具体怎么走。

February 15, 2021 · 1 min · jiezi

关于jvm:JVM-调优流程

调优分类阐明jvm调优次要分为两个方面,代码调优和GC调优。无论哪种调优,应用top命令查看以后内存和CPU应用状况是否存在问题 [root@iZ8vb5a7qagk5piviztthoZ java]# top查看是否有异样的%CPU 和%MEM占用 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 904 root 20 0 425268 31168 16400 S 0.3 1.7 0:00.30 tuned 能够看到,我的零碎里只有一个mysql占用较高,但也仅为1.7%的内存占用 代码调优代码调优是依据服务器的运行状态判断代码是否存在可优化的局部。可优化的局部大略分为死锁、OOM等问题,可从以下几个方面动手进行诊断修改 jps命令能够查看所有正在执行的java过程端口号 [root@iZ8vb5a7qagk5piviztthoZ ~]# jps1736 Jps1627 jar服务器上只有一个jar包在执行,端口号是1627 应用jinfo能够查看jar包启动时对应的参数,蕴含手动设置的参数和默认参数 jinfo -flags 1627从输入中能够看到jvm的版本号以及一系列的启动参数(Non-default VM flags),jdk1.8在没有设置的状况下默认应用的GC是parallel+parallel old Attaching to process ID 1627, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.281-b09Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=29360128 -XX:MaxHeapSize=459276288 -XX:MaxNewSize=153092096 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=9764864 -XX:OldSize=19595264 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps Command line:1. CPU占用过高如果CPU有异常情况则能够进一步应用 top -p <端口号> 查看具体的内存和CPU占用 ...

February 13, 2021 · 8 min · jiezi

关于jvm:JAVA-GC机制概要

GC逻辑模型1. 分代模型serial和parallel、parNew以及CMS都是基于分代模型实现的GC组件。分代模型将内存大抵分为几个局部:年老代、幸存区、老年代。其中年老代和幸存区由一个回收器组件进行回收。老年区由另一个组件进行回收。 serial下的两个组件别离是:SerialGC 和 SerialOldGCparallel则是ParallelGC和ParallelOldGCCMS也是基于分代模型,不过因为其没有对应的年老代回收组件,大多数状况下应用parNew进行搭配,parNew和ParallelGC没有实质的区别,次要是为了配合CMS进行了优化。 分代模型图解:年老代进行GC后没有胜利清理的的对象将会进入幸存区。Eden与Survivor区默认8:1:1eden区加上Survivor区和老年代的比例个别是1:2 (这些比例都是能够调整的)如果新生代放不下新建的对象,则会发动Minor GC,Minor GC后仍旧放不下则会将对象提前放入老年代(如果老年代也放不下则会调用full GC,full GC后仍旧放不下则会OOM)2. 无分代模型G1和ZGC目前的实现都大幅度的弱化了分代模型的概念,ZGC更是齐全没有分代的概念,整套垃圾回收机制由一套简单的算法进行实现其中G1是从CMS演变而来,流程上略有些类似大抵流程如下图CMS:G1:ZGC: 垃圾回收器的算法几种垃圾回收器的介绍三色标记

February 12, 2021 · 1 min · jiezi

关于jvm:jstack处理Java中CPU100的思路流程

模仿问题代码结构一个死循环,造成CPU使用率100%。> vim InfiniteLoop.javapublic class InfiniteLoop { public static void main(String[] args) { Runnable target; Thread thread=new Thread(new Runnable() { @Override public void run() { long i=0; while (true){ i++; } } }); thread.setName("rumenz"); thread.start(); }}> javac InfiniteLoop.java运行问题代码> java InfiniteLoop发现零碎CPU 100%> top6076 root 20 0 7096732 18972 10648 S 100.0 0.1 7:42.51 java InfiniteLoop失去过程号是6076依据top命令,发现PID为6076的Java过程占用CPU高达100%,呈现故障。 找出具体的线程号> top -Hp 60766096 root 20 0 7096732 18972 10648 R 99.7 0.1 9:09.92 java 失去线程号是6096将线程号转换成16进制> printf "%x\n" 609617d0万事具备,开始应用jstack打印堆栈信息> jstack 6076 | grep 17d0 -A 30"rumenz" #10 prio=5 os_prio=0 tid=0x00007fe0580f9000 nid=0x17d0 runnable [0x00007fe04431d000] java.lang.Thread.State: RUNNABLE at InfiniteLoop$1.run(InfiniteLoop.java:11) at java.lang.Thread.run(Thread.java:748)"Service Thread" #9 daemon prio=9 os_prio=0 tid=0x00007fe0580e5800 nid=0x17ce runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE"C1 CompilerThread3" #8 daemon prio=9 os_prio=0 tid=0x00007fe0580c8000 nid=0x17cd waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE"C2 CompilerThread2" #7 daemon prio=9 os_prio=0 tid=0x00007fe0580c6000 nid=0x17cc waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE"C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007fe0580c4000 nid=0x17cb waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007fe0580c1000 nid=0x17ca waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007fe0580bf800 nid=0x17c9 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007fe05808e800 nid=0x17c8 in Object.wait() [0x00007fe044b25000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000076d408ed8> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144) - locked <0x000000076d408ed8> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)at InfiniteLoop$1.run(InfiniteLoop.java:11),提醒出代码11行, 查看源码发现有一个死循环。总结解决JAVA,CPU 100%的问题top 查找CPU 100%的过程号pidtop -Hp pid找出过程pid下最占CPU的线程号tidprintf "%x\n" tid 将tid转换成十六进制 16tidjstack pid | grep 16tid -A 30打印堆栈信息解决问题代码关注微信公众号:【入门小站】,解锁更多知识点。 ...

February 9, 2021 · 2 min · jiezi

关于jvm:JVM字节码文件结构说明

.class字节码文件构造 魔数: 文件的结尾的 四个字节 是固定 值位   0xCAFEBABE 次版本号( minor version ):二个字节00 00 示意jdk的次版本号 主版本号( major version ):二个字节 00 34  示意为jdk的主版本号,34对于10进制为52那么52代表的是1.8,51代表的是1.7 等等始终类推上来 所以通过主次版本号来确定咱们jdk的版本是1.8.0 常量池入口,占用二个字节,示意常量池中的个数=00 19 (25)-1=24个, 为啥须要-1,因为常量池中的第0个地位被咱们的jvm占用了示意为null  所以咱们通过编译进去的常量池索引是从1开始的. Constant pool:2  #1 = Methodref #4.#21 // java/lang/Object."<init>":()V3  #2 = Fieldref #3.#22 // com/tuling/smlz/jvm/classbyatecode/TulingByteCode.userName:Ljava/lang/String;4  #3 = Class #23 // com/tuling/smlz/jvm/classbyatecode/TulingByteCode5  #4 = Class #24 // java/lang/Object6  #5 = Utf8 userName7  #6 = Utf8 Ljava/lang/String;8  #7 = Utf8 <init>9  #8 = Utf8 ()V10  #9 = Utf8 Code11  #10 = Utf8 LineNumberTable12  #11 = Utf8 LocalVariableTable13  #12 = Utf8 this14  #13 = Utf8 Lcom/tuling/smlz/jvm/classbyatecode/TulingByteCode;15  #14 = Utf8 getUserName16  #15 = Utf8 ()Ljava/lang/String;17  #16 = Utf8 setUserName18  #17 = Utf8 (Ljava/lang/String;)V19  #18 = Utf8 MethodParameters20  #19 = Utf8 SourceFile21  #20 = Utf8 TulingByteCode.java22  #21 = NameAndType #7:#8 // "<init>":()V23  #22 = NameAndType #5:#6 // userName:Ljava/lang/String;24  #23 = Utf8 com/tuling/smlz/jvm/classbyatecode/TulingByteCode25  #24 = Utf8 java/lang/Object常量池构造u1,u2,u4,u8别离代表1个字节,2个字节,4个字节,8个字节的无符号数不同常量对应的字节码构造咱们的常量池能够看作咱们的java class类的一个资源仓库(比方Java类定的办法和变量信息),咱们前面的办法 类的信息的形容信息都是通过索引去常量池中获取。class文件构造类的拜访权限查问手册字段表构造办法表中的属性表attribute_info构造

February 6, 2021 · 1 min · jiezi

关于jvm:JVM学习之路3GC机制和GC收集器分析

继上一篇 JVM学习之路2-对象内存布局及逃逸剖析 介绍完jvm相干对象在内存中如何布局、如何进行拜访以及jvm进行逃逸剖析并做优化之后,本篇筹备聊一下jvm最要害的一个点(也是事实中遇到问题最多的点)垃圾回收。知识点1、垃圾回收机制2、常见的垃圾收集器 垃圾回收机制顾名思义,垃圾回收就是对垃圾进行回收,然而jvm中哪些属于"垃圾"呢?无用的对象。jvm判断对象是否能够被回收次要有两种算法,援用计数法和可达性剖析。上面别离来介绍一下。 援用计数法晚点补充。。。

February 4, 2021 · 1 min · jiezi

关于jvm:CMS垃圾收集器停顿案例

CMS垃圾收集器从jdk1.6中开始利用,是一个老年代垃圾收集器,在JVM的倒退过程中表演了重要的历史作用,jdk1.7,jdk1.8中都能够开启应用。在jdk9中曾经废除掉了。CMS垃圾收集器的重要毛病因为老年代碎片问题,在YGC的时候会产生降职失败(promotion failures),即便老年代有足够的空间,然而依然可能导致调配失败,因为没有足够间断的空间,从而触发Concurrent mode Failure,会产生SWT的FullGC。FullGC相比于CMS这种并发模式的GC须要更长的进展工夫能力实现垃圾回收工作。这会导致重大的进展服务不可用问题。concurrent mode failure,须要stop-the-wold 降级为GC-Serail Old)。CMS为什么会产生碎片CMS垃圾收集器在回收老年代时,采纳的是标记清理(Mark-Sweep)算法,它在垃圾回收时并不会压缩堆,工夫久了,导致老年代的碎片化问题越来越重大,直到产生单线程的Mark-Sweep Compact GC即FullGC,会齐全STW。如果堆比拟大并且老年代占的空间比拟大,STW的工夫会继续几秒,十几秒,几十秒。对于应用程序来说就是长时间的进展,这对于互联网利用的影响是很大的。剖析CMS日志启动jvm的时候,减少参数-XX:+PrintGCDetails 和 -XX:+PrintGCTimeStamps能够打印出CMS GC的具体日志。-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息,-Xloggc:../logs/gc.log 日志文件的输入门路。-XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps and -XX:+PrintGCApplicationStoppedTime -XX:PrintFLSStatistics=2{Heap before GC invocations=7430 (full 24):parnew generation total 134400K, used 121348K[0x53000000, 0x5c600000, 0x5c600000)eden space 115200K, 99% used [0x53000000, 0x5a07e738, 0x5a080000)from space 19200K, 32% used [0x5a080000, 0x5a682cc0, 0x5b340000)to space 19200K, 0% used [0x5b340000, 0x5b340000, 0x5c600000)concurrent mark-sweep generation total 2099200K, used 1694466K [0x5c600000, 0xdc800000, 0xdc800000)concurrent-mark-sweep perm gen total 409600K, used 186942K [0xdc800000, 0xf5800000, 0xfbc00000)10628.167: [GC Before GC:Statistics for BinaryTreeDictionary:------------------------------------Total Free Space: 103224160Max Chunk Size: 5486Number of Blocks: 57345Av. Block Size: 1800Tree Height: 36 <---- High fragmentationStatistics for IndexedFreeLists:--------------------------------Total Free Space: 371324Max Chunk Size: 254Number of Blocks: 8591 <---- High fragmentationAv. Block Size: 43free=103595484frag=1.0000 <---- High fragmentationBefore GC:Statistics for BinaryTreeDictionary:------------------------------------Total Free Space: 0Max Chunk Size: 0Number of Blocks: 0Tree Height: 0Statistics for IndexedFreeLists:--------------------------------Total Free Space: 0Max Chunk Size: 0Number of Blocks: 0free=0 frag=0.000010628.168: [ParNew (promotion failed) Desired survivor size 9830400 bytes, new threshold 1 (max 1)- age 1: 4770440 bytes, 4770440 total: 121348K->122157K(134400K), 0.4263254secs]10628,594: [CMS10630.887: [CMS-concurrent-mark: 7.286/8.682 secs] [Times: user=14.81, sys=0.34, real=8.68 secs](concurrent mode failure):1698044K->625427K(2099200K), 17.1365396 secs]1815815K->625427K(2233600K), [CMS Perm : 186942K->180711K(409600K)] After GC:Statistics for BinaryTreeDictionary:------------------------------------Total Free Space: 377269492Max Chunk Size:377269492Number of Blocks: 1 <---- No fragmentationAv. Block Size: 377269492Tree Height: 1 <---- No fragmentationStatistics for IndexedFreeLists:--------------------------------Total Free Space: 0Max Chunk Size: 0Number of Blocks: 0free=377269492frag=0.0000 <---- No fragmentationAfter GC:Statistics for BinaryTreeDictionary:------------------------------------Total Free Space: 0Max Chunk Size: 0Number of Blocks: 0Tree Height: 0Statistics for IndexedFreeLists:--------------------------------Total Free Space: 0Max Chunk Size: 0Number of Blocks: 0free=0 frag=0.0000, 17.5645589 secs] [Times: user=17.82 sys=0.06, real=17.57 secs]Heap after GC invocations=7431 (full 25):parnew generation total 134400K, used 0K [0x53000000, 0x5c600000, 0x5c600000)eden space 115200K, 0% used [0x53000000, 0x53000000, 0x5a080000)from space 19200K, 0% used [0x5b340000, 0x5b340000, 0x5c600000)to space 19200K, 0% used [0x5a080000, 0x5a080000, 0x5b340000)concurrent mark-sweep generation total 2099200K, used 625427K [0x5c600000, 0xdc800000, 0xdc800000)concurrent-mark-sweep perm gen total 409600K, used 180711K [0xdc800000, 0xf5800000, 0xfbc00000)}Total time for which application threads were stopped: 17.5730653 seconds因为碎片率十分高,从而导致promotion failure,而后产生concurrent mode failure,触发的FullGC总计花了17.1365396秒才实现。操作系统内存不够,应用了swap,导致CMS长时间进展操作系统应用了swap,可能导致GC进展工夫更长,这些进展可能是几秒,甚至几十秒级别。系统配置了容许应用swap空间,操作系统可能把JVM过程的非流动内存页移到swap空间,从而开释内存给以后流动过程(可能是操作系统上其余过程,取决于系统调度)。Swapping因为须要拜访磁盘,所以相比物理内存,它的速度慢的令人发指。所以,如果在GC的时候,零碎正好须要执行Swapping ...

February 2, 2021 · 3 min · jiezi

关于jvm:java类加载机制windows下

1、执行run办法后java.exe调用jvm.dll文件创建jvm虚构2、创立虚拟机的过程中创立出疏导类加载器实例(c++)3、c++代码调用java代码创立jvm启动器sum.misc.Launcher.getLauncher()4、launcher.getClassLoader()进行java类的加载加载-》验证-》筹备—》解析—》初始化1.验证:验证格局是否正确2.筹备:动态变量初值始赋值 Ex:int=0,boolean=false 3.解析:符号援用替换为间接援用(动态援用)4.初始化:初始化动态变量和动态代码5、执行类中的main办法6、执行完结,销毁JVM

February 2, 2021 · 1 min · jiezi

关于jvm:JVM学习之路2对象内存布局及逃逸分析

继上一篇介绍完JVM内存模型之后,这篇筹备聊聊对象的内存布局以及逃逸剖析。咱们晓得对象个别是调配在堆上的,然而你晓得对象在堆上是怎么寄存的吗?咱们平时程序中在应用的时候是怎么找到对象的?知识点1、内存对象布局2、逃逸剖析 内存对象布局先说一下咱们平时是怎么创建对象的A a = new A();如上所示,一个对象A就被创立进去了。看似简略的一行语句,其实虚拟机为咱们做了很多事件。首先虚拟机去常量池中查找是否有类A的符号援用,并查看该符号援用的类A是否曾经被虚拟机所加载过,如果没有则先进行加载(具体的类加载机制咱们会在前面的系列文章中介绍),如果曾经被加载过则能够确定为该类对象所调配的内存大小并进行内存调配,大略流程如下:这里波及到两种调配形式:指针碰撞和闲暇列表 指针碰撞简略来说,基于内存规整的前提下将内存分为两局部,用过的内存放到左侧,没用过的内存放到右侧,两头放一个指针来分界,当为对象分配内存时,向闲暇区挪动一块该对象大小的空间: 闲暇列表下面是基于内存规整的状况下进行指针碰撞,如果内存不规整的话,怎么解决?那就要说到闲暇列表了,简略来说就是已分配内存和未分配内存是交织的,虚拟机保护一份列表来晓得哪些内存是可用的,在进行对象调配的时候会从列表中取一块足够空间的内存,调配完结后更新列表:下面两种调配形式哪种更好呢?这个没有肯定哪个好或者哪个不好,取决于java的内存是否规整,而java内存是否规整又取决于所用的垃圾收集器是否带有压缩整顿法决定,所以在前面介绍垃圾收集器的时候再具体聊。没写完,工夫关系,今天持续。。

February 1, 2021 · 1 min · jiezi

关于jvm:jconsole和jstack定位死锁问题

什么是死锁死锁问题是多线程特有的问题,它能够被认为是线程间切换耗费零碎性能的一种极其状况。 在死锁时,线程间互相期待资源,而又不开释本身的资源,导致无穷无尽的期待,其后果是零碎工作永远无奈执行实现。 死锁问题是在多线程开发中应该坚定防止和杜绝的问题.死锁示例代码package com.rumenz.learn.deadLock;public class RumenzThread implements Runnable{ int a,b; public RumenzThread(int a, int b) { this.a = a; this.b = b; } @Override public void run() { //Integer.valueOf(a) 包装成对象 synchronized (Integer.valueOf(a)){ try{ //睡眠3秒,减少死锁的几率 Thread.sleep(3000); }catch (Exception e){ e.printStackTrace(); } synchronized (Integer.valueOf(b)){ System.out.println("a+b="+(a+b)); } } }}package com.rumenz.learn.deadLock;public class DeadLock { public static void main(String[] args) { new Thread(new RumenzThread(1, 2)).start(); new Thread(new RumenzThread(2, 1)).start(); }}运行程序应用jstack -l pid来定位死锁先找到死锁程序的过程id> jps56993 Jps56636 Launcher57066 DeadLock //这个就是死锁的过程应用jstack -l 57066来定位死锁> jstack -l 57066Found one Java-level deadlock:============================="Thread-1": waiting to lock monitor 0x00007fbe6d80de18 (object 0x000000076ab33988, a java.lang.Integer), which is held by "Thread-0""Thread-0": waiting to lock monitor 0x00007fbe6d8106a8 (object 0x000000076ab33998, a java.lang.Integer), which is held by "Thread-1"Java stack information for the threads listed above:==================================================="Thread-1": at com.rumenz.learn.deadLock.RumenzThread.run(RumenzThread.java:27) - waiting to lock <0x000000076ab33988> (a java.lang.Integer) - locked <0x000000076ab33998> (a java.lang.Integer) at java.lang.Thread.run(Thread.java:748)"Thread-0": at com.rumenz.learn.deadLock.RumenzThread.run(RumenzThread.java:27) - waiting to lock <0x000000076ab33998> (a java.lang.Integer) - locked <0x000000076ab33988> (a java.lang.Integer) at java.lang.Thread.run(Thread.java:748)Found 1 deadlock. //发现一个死锁RumenzThread.java:27 定位到大略的代码文件地位。jconsole定位死锁问题找到死锁过程 ...

January 31, 2021 · 1 min · jiezi

关于jvm:JVM笔记-Java跨平台和JVM跨语言

学习JVM的重要性从下层应用程序到底层操作系统,到底有哪些货色? 平时开发的应用程序次要基于各种框架,譬如Spring,SpringMVC,Mybatis,而各种框架又是基于Java API来实现的,Java API调用执行是在JVM上的,而JVM则是运行在操作系统上的,操作系统是在物理机器打交道的。 在框架上进行业务开发,或者学习框架如何应用,是大部分开发者的工作。然而实际上咱们不该执着于框架该如何应用,而是应该往下走,个别最初遇到的问题都会到JVM和操作系统的问题。即便当初的 JVM 曾经很欠缺,帮开发者做了很多事,然而咱们不该以此为理由不去理解 JVM 的原理。架构师把开发者变成温室里的花朵和温水里的青蛙,如果有一天呈现以下问题,该怎么解决: 运行零碎卡死,零碎无法访问,间接 OOM 。线上 GC (垃圾回收)有问题,须要 dump 内存,进行剖析。新我的项目上线,须要对系统进行评估,设置 JVM 的参数。面试时被问及理论我的项目中 JVM 参数调优。简直所有的高级语言在交给机器CPU执行之前,都会经验 高级语言 --> 汇编语言 --> 机器指令 的过程,因为计算机是不会间接辨认高级语言的。理解高级语言如何转换成能被机器辨认的语言,是开发者必须把握的技能。 Java比C++多了动态内存调配以及垃圾回收技术,Java的虚拟机帮开发者做了垃圾收集,编译优化等一系列工作,外面的垃圾收集算法有哪些?怎么执行的?JIT编译器是怎么工作的?这些往往也是面试常谈的话题。懂得JVM的外部机构和工作机制,有利于设计高拓展的利用和疾速诊断运行时的问题。 如何学习JVM去官网找虚拟机标准(英文版):https://www.oracle.com/cn/jav... 找到:Java Language and Virtual Machine Specifications: 关上之后是:https://docs.oracle.com/javas... ,也就是Java语言和虚拟机的标准。 能够抉择以下书籍,如果是初学,最好学周志明老师的深刻了解Java虚拟机,虚拟机标准会间接劝退: Java虚拟机标准(Java SE 8版)深刻了解Java虚拟机(JVM高级个性与最佳实际) Java标准只是规范,不同的版本标准不一样,同一个版本的标准在不同的虚拟机上有不一样的实现,初学者应该从最热门支流的HotSpot虚拟机开始,也就是Oracle本人推出的Java虚拟机。 通过cmd能够看出,我装的Java环境应用的就是64位的HotSpot虚拟机: 如何真正搞懂JVM?在理解JVM标准和原理的状况下,本人手动实现一个繁难的Java虚拟机。对于大部分人来说,挺难的,然而世间万物,为之则不难,不为,则难。平步青云则难,步步为营则不难。 语言排行版,目前Java是第二名:https://www.tiobe.com/tiobe-i... 世界上没有最好的编程语言,须要依照具体的应用场景来谈话。 Java跨平台怎么了解?Java是一门跨平台语言,所谓跨平台就是,Java源文件会被编译成为字节码文件,不论是Windows,Linux还是Mac,都有其适配的JVM,也就是字节码文件能够随便在这些JVM下来运行。 Write once,run anywhere.其余的语言,例如c语言,编译成为机器码之后,因为底层的机器语言反对不一样,编译后的机器语言文件是不能够跨操作系统运行的。而Java则是把兼容的工作,交给了JVM。不同的JVM负责去适配不同的操作系统。 所有的Java虚拟机都恪守java虚拟机的标准,语言编写者不须要思考兼容问题。 Java虚拟机是Java平台的基石。 它是技术的组成部分,负责硬件和操作系统的独立性,已编译代码的小尺寸以及爱护用户免受恶意程序攻打的能力。 Java虚拟机是形象的计算机。 像真正的计算机一样,它具备指令集并在运行时操作各种内存区域。 应用虚拟机实现编程语言是相当广泛的。 最出名的虚拟机可能是UCSD Pascal的P代码计算机。JVM 跨语言怎么了解?JVM是跨语言的平台,很多语言都能够编译成为恪守标准的字节码,这些字节码都能够在Java虚拟机上运行。Java虚拟机不关怀这个字节码是不是来自于Java程序,只须要各个语言提供本人的编译器,字节码遵循字节码标准,比方字节码的结尾是CAFEBABY。 将各种语言编译成为字节码文件的编译器,称之为前端编译器。而Java虚拟机中,也有编译器,比方即时编译器,此处称为后端编译器。 Java虚拟机要做到跨语言,目前来看应该是当下最弱小的虚拟机。然而并非一开始设计要跨语言。 跨语言的平台有利于什么?因为有了跨语言平台,多语言混合编程就更加不便了,通过特定畛域的语言去解决特定畛域的问题。 比方并行处理应用Clojure语言编写,展现层应用JRuby/Rails,中间层用Java编写,每一应用层都能够应用不同的语言编写,接口对于开发者是通明的。不同语言能够互相调用,就像是调用本人语言原生的API一样。它们都运行在同一个虚拟机上。 ...

January 30, 2021 · 1 min · jiezi

关于jvm:6-个JVM性能监控调优工具使用详解

事实企业级Java利用开发、保护中,有时候咱们会碰到上面这些问题: OutOfMemoryError,内存不足内存泄露线程死锁锁争用(Lock Contention)Java过程耗费CPU过高......这些问题在日常开发、保护中可能被很多人漠视(比方有的人遇到下面的问题只是重启服务器或者调大内存,而不会深究问题本源),但可能了解并解决这些问题是Java程序员进阶的必备要求。本文将对一些罕用的JVM性能调优监控工具进行介绍,心愿能起抛砖引玉之用。 而且这些监控、调优工具的应用,无论你是运维、开发、测试,都是必须把握的。 1、 jpsJava Virtual Machine Process Status Tool,jps次要用来输入JVM中运行的过程状态信息。语法格局如下: jps [options] [hostid]如果不指定hostid就默认为以后主机或服务器。 命令行参数选项阐明如下: -q #不输入类名、Jar名和传入main办法的参数-m #输入传入main办法的参数-l #输入main类或Jar的全限名-v #输入传入JVM的参数比方上面: root@ubuntu:/# jps -m -l2458 org.artifactory.standalone.main.Main /usr/local/artifactory-2.2.5/etc/jetty.xml29920 com.sun.tools.hat.Main -port 9998 /tmp/dump.dat3149 org.apache.catalina.startup.Bootstrap start30972 sun.tools.jps.Jps -m -l8247 org.apache.catalina.startup.Bootstrap start25687 com.sun.tools.hat.Main -port 9999 dump.dat21711 mrf-center.jar2、 jstackjstack次要用来查看某个Java过程内的线程堆栈信息。语法格局如下: jstack [option] pidjstack [option] executable corejstack [option] [server-id@]remote-hostname-or-ip命令行参数选项阐明如下: -l long listings #会打印出额定的锁信息,在产生死锁时能够用jstack -l pid来察看锁持有状况-m mixed mode,不仅会输入Java堆栈信息,还会输入C/C++堆栈信息(比方Native办法)jstack能够定位到线程堆栈,依据堆栈信息咱们能够定位到具体代码,所以它在JVM性能调优中应用得十分多。上面咱们来一个实例找出某个Java过程中最消耗CPU的Java线程并定位堆栈信息,用到的命令有ps、top、printf、jstack、grep。 第一步先找出Java过程ID,我部署在服务器上的Java利用名称为mrf-center: root@ubuntu:/# ps -ef | grep mrf-center | grep -v greproot     21711     1  1 14:47 pts/3    00:02:10 java -jar mrf-center.jar失去过程ID为21711,第二步找出该过程内最消耗CPU的线程,能够应用ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid,我这里用第三个,输入如下:TIME列就是各个Java线程消耗的CPU工夫,CPU工夫最长的是线程ID为21742的线程,用 printf "%xn" 21742失去21742的十六进制值为54ee,上面会用到。 OK,下一步终于轮到jstack上场了,它用来输入过程21711的堆栈信息,而后依据线程ID的十六进制值grep,如下: root@ubuntu:/# jstack 21711 | grep 54ee"PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait() [0x00007f94c6eda000]    能够看到CPU耗费在PollIntervalRetrySchedulerThread这个类的Object.wait(),我找了下我的代码,定位到上面的代码:// Idle waitgetLog().info("Thread [" + getName() + "] is idle waiting...");schedulerThreadState = PollTaskSchedulerThreadState.IdleWaiting;long now = System.currentTimeMillis();long waitTime = now + getIdleWaitTime();long timeUntilContinue = waitTime - now;synchronized(sigLock) { try {     if(!halted.get()) {      sigLock.wait(timeUntilContinue);     }    }  catch (InterruptedException ignore) {    }}它是轮询工作的闲暇期待代码,下面的sigLock.wait(timeUntilContinue)就对应了后面的Object.wait()。 3、 jmap和jhatjmap(Memory Map)用来查看堆内存应用情况,个别联合jhat(Java Heap Analysis Tool)应用。 jmap语法格局如下: jmap [option] pidjmap [option] executable corejmap [option] [server-id@]remote-hostname-or-ip如果运行在64位JVM上,可能须要指定-J-d64命令选项参数。 jmap -permstat pid打印过程的类加载器和类加载器加载的长久代对象信息,输入:类加载器名称、对象是否存活(不牢靠)、对象地址、父类加载器、已加载的类大小等信息,如下图: 应用jmap -heap pid查看过程堆内存应用状况,包含应用的GC算法、堆配置参数和各代中堆内存应用状况。比方上面的例子: root@ubuntu:/# jmap -heap 21711Attaching to process ID 21711, please wait...Debugger attached successfully.Server compiler detected.JVM version is 20.10-b01using thread-local object allocation.Parallel GC with 4 thread(s)Heap Configuration:MinHeapFreeRatio = 40   MaxHeapFreeRatio = 70   MaxHeapSize      = 2067791872 (1972.0MB)NewSize          = 1310720 (1.25MB)MaxNewSize       = 17592186044415 MBOldSize          = 5439488 (5.1875MB)NewRatio         = 2   SurvivorRatio    = 8   PermSize         = 21757952 (20.75MB)MaxPermSize      = 85983232 (82.0MB)Heap Usage:PS Young GenerationEden Space:   capacity = 6422528 (6.125MB)   used     = 5445552 (5.1932830810546875MB)   free     = 976976 (0.9317169189453125MB)   84.78829520089286% usedFrom Space:   capacity = 131072 (0.125MB)   used     = 98304 (0.09375MB)   free     = 32768 (0.03125MB)   75.0% usedTo Space:   capacity = 131072 (0.125MB)   used     = 0 (0.0MB)   free     = 131072 (0.125MB)   0.0% usedPS Old Generation   capacity = 35258368 (33.625MB)   used     = 4119544 (3.9287033081054688MB)   free     = 31138824 (29.69629669189453MB)   11.683876009235595% usedPS Perm Generation   capacity = 52428800 (50.0MB)   used     = 26075168 (24.867218017578125MB)   free     = 26353632 (25.132781982421875MB)   49.73443603515625% used   ....应用jmap -histo[:live] pid查看堆内存中的对象数目、大小统计直方图,如果带上live则只统计活对象,如下: root@ubuntu:/# jmap -histo:live 21711 | more num     #instances         #bytes  class name----------------------------------------------   1:         38445        5597736  <constMethodKlass>   2:         38445        5237288  <methodKlass>   3:          3500        3749504  <constantPoolKlass>   4:         60858        3242600  <symbolKlass>   5:          3500        2715264  <instanceKlassKlass>   6:          2796        2131424  <constantPoolCacheKlass>   7:          5543        1317400  [I   8:         13714        1010768  [C   9:          4752        1003344  [B  10:          1225         639656  <methodDataKlass>  11:         14194         454208  java.lang.String  12:          3809         396136  java.lang.Class  13:          4979         311952  [S  14:          5598         287064  [[I  15:          3028         266464  java.lang.reflect.Method  16:           280         163520  <objArrayKlassKlass>  17:          4355         139360  java.util.HashMap$Entry  18:          1869         138568  [Ljava.util.HashMap$Entry;  19:          2443          97720  java.util.LinkedHashMap$Entry  20:          2072          82880  java.lang.ref.SoftReference  21:          1807          71528  [Ljava.lang.Object;  22:          2206          70592  java.lang.ref.WeakReference  23:           934          52304  java.util.LinkedHashMap  24:           871          48776  java.beans.MethodDescriptor  25:          1442          46144  java.util.concurrent.ConcurrentHashMap$HashEntry  26:           804          38592  java.util.HashMap  27:           948          37920  java.util.concurrent.ConcurrentHashMap$Segment  28:          1621          35696  [Ljava.lang.Class;  29:          1313          34880  [Ljava.lang.String;  30:          1396          33504  java.util.LinkedList$Entry  31:           462          33264  java.lang.reflect.Field  32:          1024          32768  java.util.Hashtable$Entry  33:           948          31440  [Ljava.util.concurrent.ConcurrentHashMap$HashEntry;class name是对象类型,阐明如下: B  byteC  charD  doubleF  floatI  intJ  longZ  boolean[  数组,如[I示意int[][L+类名 其余对象还有一个很罕用的状况是:用jmap把过程内存应用状况dump到文件中,再用jhat剖析查看。jmap进行dump命令格局如下: jmap -dump:format=b,file=dumpFileName pid我一样地对下面过程ID为21711进行Dump: root@ubuntu:/# jmap -dump:format=b,file=/tmp/dump.dat 21711 Dumping heap to /tmp/dump.dat ...Heap dump file createddump进去的文件能够用MAT、VisualVM等工具查看,这里用jhat查看: root@ubuntu:/# jhat -port 9998 /tmp/dump.datReading from /tmp/dump.dat...Dump file created Tue Jan 28 17:46:14 CST 2014Snapshot read, resolving...Resolving 132207 objects...Chasing references, expect 26 dots..........................Eliminating duplicate references..........................Snapshot resolved.Started HTTP server on port 9998Server is ready.留神如果Dump文件太大,可能须要加上-J-Xmx512m这种参数指定最大堆内存,即jhat -J-Xmx512m -port 9998 /tmp/dump.dat。而后就能够在浏览器中输出主机地址:9998查看了: 下面红线框出来的局部大家能够本人去摸索下,最初一项反对OQL(对象查询语言)。 4、jstat(JVM统计监测工具)语法格局如下: jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]vmid是Java虚拟机ID,在Linux/Unix零碎上个别就是过程ID。interval是采样工夫距离。count是采样数目。比方上面输入的是GC信息,采样工夫距离为250ms,采样数为4: root@ubuntu:/# jstat -gc 21711 250 4 S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT   192.0  192.0   64.0   0.0    6144.0   1854.9   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649192.0  192.0   64.0   0.0    6144.0   1972.2   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649192.0  192.0   64.0   0.0    6144.0   1972.2   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649192.0  192.0   64.0   0.0    6144.0   2109.7   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649要明确下面各列的意义,先看JVM堆内存布局: 能够看出: 堆内存 = 年老代 + 年轻代 + 永恒代年老代 = Eden区 + 两个Survivor区(From和To)当初来解释各列含意: ...

January 26, 2021 · 1 min · jiezi

关于jvm:JVM-参数

JVM参数分类=========== jvm 参数可分为三类: 规范参数:以 "-" 结尾的参数非标准参数:以 "-X" 结尾的参数不稳固参数:以"-XX" 结尾的参数规范参数(-)=========== 规范参数是指在各个JVM版本中根本放弃不变,绝对比较稳定。 规范参数对立都是以 "-" 结尾,如: java -classpath E:/code -Dprofile=dev HelloWorld tom jack留神:其中HelloWorld 是被运行的 HelloWorld.class。HelloWorld 之前就是设置的JVM规范参数(-classpath、-D),HelloWorld 之后的参数(tom、jack)是用来传给 main(String[] args) 办法的args数组变量的,两者地位不要放错查看所有规范参数: 关上一个命令终端,执行 "java -help",就能够展现所有的JVM规范参数 C:Userstaichangwei>java -helpPicked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8用法: java [-options] class [args...] (执行类) 或 java [-options] -jar jarfile [args...] (执行 jar 文件)其中选项包含: -d32 应用 32 位数据模型 (如果可用) -d64 应用 64 位数据模型 (如果可用) -server 抉择 "server" VM 默认 VM 是 server. -cp <目录和 zip/jar 文件的类搜寻门路> -classpath <目录和 zip/jar 文件的类搜寻门路> 用 ; 分隔的目录, JAR 档案 和 ZIP 档案列表, 用于搜寻类文件。 -D<名称>=<值> 设置零碎属性 -verbose:[class|gc|jni] 启用具体输入 -version 输入产品版本并退出 -version:<值> 正告: 此性能已过期, 将在 将来发行版中删除。 须要指定的版本能力运行 -showversion 输入产品版本并持续 -jre-restrict-search | -no-jre-restrict-search 正告: 此性能已过期, 将在 将来发行版中删除。 在版本搜寻中包含/排除用户专用 JRE -? -help 输入此帮忙音讯 -X 输入非标准选项的帮忙 -ea[:<packagename>...|:<classname>] -enableassertions[:<packagename>...|:<classname>] 按指定的粒度启用断言 -da[:<packagename>...|:<classname>] -disableassertions[:<packagename>...|:<classname>] 禁用具备指定粒度的断言 -esa | -enablesystemassertions 启用零碎断言 -dsa | -disablesystemassertions 禁用零碎断言 -agentlib:<libname>[=<选项>] 加载本机代理库 <libname>, 例如 -agentlib:hprof 另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help -agentpath:<pathname>[=<选项>] 按残缺路径名加载本机代理库 -javaagent:<jarpath>[=<选项>] 加载 Java 编程语言代理, 请参阅 java.lang.instrument -splash:<imagepath> 应用指定的图像显示启动屏幕无关详细信息, 请参阅 http://www.oracle.com/technetwork/java/javase/documentation/index.html。非标准参数(-X)============= ...

January 11, 2021 · 3 min · jiezi

关于jvm:Java教程JVM内存空间三区方法区堆区栈区方法实例

后面总结:JVM内存空间分为三块,别离为办法区、堆区、栈区 办法区蕴含:class文件、动态变量 堆区蕴含:对象、对象中的实例变量 栈区蕴含:办法、办法中的局部变量 办法区最先有数据,垃圾回收器针对堆区(没有援用指向它时),栈区最沉闷,如何设置内存空间及其利用,以及办法去、栈区、堆区的一些技术点,把握这些技巧,以及增强各种java技术的常识,拉勾IT课小编为大家剖析。 封装:最次要的目标是把简单的问题简单化,把简单的构造封装,只留有简略的操作入口 其次,平安,可移植性好 实现:属性私有化,并提供set、get办法,set中能够进行安全控制。 ....... =================================================================================== day10-课堂笔记 1、this关键字 【Customer】 【了解:因为对象是不惟一的,是动静的,因此实例办法不须要static(动态)关键字】 【下面的this能够省略(少数状况都能够)】 【CustomerTest】 【this不能应用在带有static的办法当中,联合下面4条共6条规定】 【怎么在Static中拜访实例变量?创建对象!】 【在类体中间接定义的是实例变量,在主办法和办法体之间的是成员变量,在办法体中定义的是局部变量,待定】 【ThisTest】 【this最终论断】 【this不能省略的状况】 【生成有参无参构造方法】 【在弹出窗口抉择参数】 【this用在无参构造方法内调用有参构造方法】 【this能够用在哪里】 【作业】 【把下面最初的局部放到最后面,如下】 我是Java程序员,从事Java开发工作十年,目前全职Java线上一对一辅导学习,依据你的根底,学习工夫,学习进度,给你制订学习打算,做到因材施教,如果须要,能够私信理解,零根底学习Java,能够退出我的十年Java学习园地。

January 6, 2021 · 1 min · jiezi

关于jvm:JVM快速入门上

一、JVM体系结构    .java经由javac变为class字节码文件,再通过类加载器加载.对于类加载器我依据南淮北安博主的文章进行了总结. 1.类加载器    咱们都晓得java程序写好当前是以.java(文本文件)的文件存在磁盘上,而后,咱们通过(bin/javac.exe)编译命令把.java文件编译成.class文件(字节码文件),并存在磁盘上。然而程序要运行,首先肯定要把.class文件加载到JVM内存中能力应用的,咱们所讲的classLoader,就是负责把磁盘上的.class文件加载到JVM内存中. 2.ClassLoader 层次结构     类加载器有三种:(1)根类加载器(null)它是由本地代码(c/c++)实现的,你基本拿不到他的援用,然而他理论存在,并且加载一些重要的类,它加载(%JAVA_HOME%jrelib),如rt.jar(runtime)、i18n.jar等,这些是Java的外围类。(2)平台类加载器(PlatformClassLoader)(jdk1.8之后的版本,之前的称为扩大类加载器 ExtClassLoader)虽说能拿到,然而咱们在实践中很少用到它,它次要加载扩大目录下的jar包, %JAVA_HOME%libext(3)利用类加载器(appClassLoader)它次要加载咱们应用程序中的类,如Test,或者用到的第三方包,如jdbc驱动包等。这里的父类加载器与类中继承概念要辨别,它们在class定义上是没有父子关系的。 3.Class 加载时调用类加载器的程序     当一个类要被加载时,有一个启动类加载器和理论类加载器的概念,这个概念请看如下剖析:如下面的Test.class要进行加载时,它将会启动利用类加载器进行加载Test类,然而这个利用类加载器不会真正去加载它,而是会调用看是否有父加载器,后果有,是扩大类加载器,扩大类加载器也不会间接去加载,它看本人是否有父加载器没,后果它还是有的,是根类加载器。

January 5, 2021 · 1 min · jiezi

关于jvm:Java并发编程3并发之synchronized

摘要之前咱们解说了并发方面的根底跟基本原理;基本原理外面次要是计算机cpu多级缓存模型以及多级缓存模型下缓存一致性的计划:总线加锁跟MESI缓存一致性原理跟jvm为了屏蔽计算机硬件和操作系统,让Java程序在各种计算机硬件跟操作系统下能保证数据拜访形式一致性引出了Java内存模型;以及因为Java的内存模型导致的主内存跟工作内存数据不统一问题;进而引发并发编程须要思考的几大个性:可见性(某个线程批改主内存外面共享数据时候,其余线程在读取的时候可能立马感知到),原子性(多线程并发去写批改主内存外面的共享变量的时候,须要保障每一次操作都是不可再分的,主内存数据在某一时候只能由一个线程批改),有序性(程序代码须要满足happends-bfore准则,保障代码执行程序有序)。而后为了解决简略数据比方:标识位数据并发读写问题保障可见性跟有序性的volatile关键字;以及底层外围原理。然而针对于多线程并发批改主内存外面共享变量的时候,如何保证数据一致性问题未能解决,这一节咱们次要解说。多线程并发批改主内存外面共享变量的时候保证数据一致性问题。 思维导图内容1.案例引入多线程原子行问题定义一个变量,此变量是寄存到堆内存的共享变量也就是主内存外面的数据。而后咱们开启多个线程并发去写批改此数据的值,而后再读取此数据的值,而后打印出,最初看一下各个线程数据更改的状况和最终数据的变更状况。

January 5, 2021 · 1 min · jiezi

关于jvm:Java并发编程2并发原理

摘要咱们这一讲次要解说基于volatile实现并发:可见性跟有序性问题,解说volatile的时候,须要解说:cpu缓存模型 -> java内存模型 -> 并发编程3大个性:原子性、可见性、有序性 -> volatile的作用 -> volatile的底层原理 -> volatile实战。 思维导图内容cpu多级缓存模型1.volatile引入咱们先看下以下例子: public class VolatileTest { static int flag = 0; public static void main(String[] args) { /** * 1、开启一个读线程,读取flag的值 */ new Thread(){ @Override public void run() { int localFlag = flag; while (true){ if(localFlag != flag){ System.out.println("读取到的标识位值:"+flag); localFlag = flag; } try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } } } }.start(); /** * 2、开启一个读写线程,批改flag值 */ new Thread(){ @Override public void run() { int localFlag = flag; while (true){ System.out.println("标识位被批改了:"+ ++localFlag); flag = localFlag; try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } } } }.start(); }}后果输入为: ...

January 3, 2021 · 1 min · jiezi

关于jvm:JVM11常见面试实战

摘要程序编译与代码优化

January 3, 2021 · 1 min · jiezi

关于jvm:JVM10程序编译与代码优化

摘要本节次要程序编译与代码优化

January 3, 2021 · 1 min · jiezi

关于jvm:JVM9虚拟机字节码执行引擎

摘要本节次要剖析虚拟机字节码执行引擎

January 3, 2021 · 1 min · jiezi

关于jvm:JVM8虚拟机类加载机制

摘要本章节次要解说虚拟机类加载机制

January 3, 2021 · 1 min · jiezi

关于jvm:JVM7线上图形化工具

摘要后面一节解说了线上虚拟机调试工具,这一讲次要解说线程图形化工具。

January 3, 2021 · 1 min · jiezi

关于jvm:JVM6线上虚拟机工具

摘要

January 2, 2021 · 1 min · jiezi

关于jvm:JVM5内存分配回收

摘要之前咱们解说了垃圾回收器,当初咱们须要解说内存调配与回收

January 2, 2021 · 1 min · jiezi

关于jvm:JVM4垃圾回收器

摘要前一节,咱们解说了内存回收的方法论,从根本的回收对象是否存活引入了:间接援用计数法(对象增加援用计数器)、可达性分析法(援用对象从GC Root登程。通过援用链查找),正因为间接援用计数法无奈解决循环援用问题,引入可达性分析法。而后引入垃圾对象回收的算法方法论:最简略的是“标记-革除法”:应用可达性分析法将内存对象标记为垃圾对象,而后革除垃圾对象,标记-革除法在对象存活工夫比拟长的内存区域效率低下:会耗费大量工夫标记革除大量内存对象,标记革除法革除垃圾对象的时候会导致不间断的内存空间,引出空间碎片化问题。所以为了解决下面问题引出了标记-复制算法:将空间分为两局部:正在应用的其中一块空间,当对象标记为垃圾对象,须要登程GC时候,将存对象一次性移到另一块内存,并且应用挪动内存指针形式让另一块内存空间间断话,而后gc掉第一块所有内存,解决了内存碎片化问题,在内存对象存活周期短的区域,只须要挪动大量的存活对象到另一块区域,效率高,所以适宜在堆内存外面的新生代;对于老年代;咱们须要引出:标记-整顿算法:将垃圾对象进行标记,而后gc时候通过指针将内存对象间断化。这一讲咱们来钻研具体的内存回收实现:垃圾回收器。 咱们次要剖析罕用的虚拟机(HotSpot)的垃圾回收器。展现了七种作用于不同分代的收集器,如果两个收集器之间存在连线,就阐明它们能够搭配应用,图中收集器所处的区域,则示意它是属于新生代收集器抑或是老年代收集器。 思维导图 内容咱们从简略到简单次要剖析5种垃圾回收器:Serial收集器、ParNew收集器、Parallel Scavenge收集器、CMS收集器、G1收集器. Serial收集器原理/是什么:单线程垃圾回收器,进行垃圾回收时候,用户线程全副进行,直到垃圾回收完结。 图解:单线程工作的收集器,但它的“单线程”的意义并不仅仅是阐明它只会应用一个处理器或一条收集线程去实现垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其余所有工作线程,直到它收集完结。“Stop The World”这个词语也 许听起来很酷,但这项工作是由虚拟机在后盾主动发动和主动实现的,在用户不可知、不可控的状况 下把用户的失常工作的线程全副停掉,这对很多利用来说都是不能承受的。特点: 毛病:“Stop The World”:它进⾏垃圾收集时,必须暂停其余所有的⼯作线程,直到它收集完结。在⽤户不可⻅的状况下把⽤户失常⼯作的线程全副停掉。(从Serial收集器到Parallel收集器,再到Concurrent Mark Sweep(CMS)和Garbage First(G1)收集器,最终至当初垃圾收集器的最前沿成绩Shenandoah和ZGC 等,咱们看到了一个个越来越构思精美,越来越优良,也越来越简单的垃圾收集器不断涌现,用户线 程的进展工夫在继续缩短,然而依然没有方法彻底消除)长处:多⽤于桌⾯应⽤,是客户端Client模式下的虚拟机。桌⾯应⽤暂用内存⼩,进⾏垃圾回收的工夫⽐较短,只有不频繁发⽣进展就能够承受。(因为是单线程的,所以耗费cpu跟内存绝对比拟小) ParNew收集器回顾:上一节咱们解说了垃圾回收器外面第一种垃圾回收器Serial。Serial垃圾回收器采纳单线程垃圾回收,他的性能是比拟差的。为了解决Serial的性能问题,咱们引入了另一种垃圾回收器:ParNew垃圾回收器。 原理:ParNew收集器本质上是Serial收集器的多线程并行版本,除了同时应用多条线程进行垃圾收集之 外,其余的行为包含Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX: PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象调配规 则、回收策略等都与Serial收集器完全一致 图解:1、ParNew收集器除了反对多线程并行收集之外,其余与Serial收集器相比并没有太多翻新之处,但它 却是不少运行在服务端模式下的HotSpot虚拟机,尤其是JDK 7之前的遗留零碎中首选的新生代收集 器,其中有一个与性能、性能无关但其实很重要的起因是:除了Serial收集器外,目前只有它能与CMS 收集器配合工作。2、CMS作为老年代的收集器,却无奈与JDK 1.4.0中曾经存在的新生代收集器Parallel Scavenge配合工作[1],所以在JDK 5中应用CMS来收集老年代的时候,新生代只能抉择ParNew或者 Serial收集器中的一个。ParNew收集器是激活CMS后(应用-XX:+UseConcMarkSweepGC选项)的默 认新生代收集器,也能够应用-XX:+/-UseParNewGC选项来强制指定或者禁用它。3、G1是一个面向全堆的收集器,不 再须要其余新生代收集器的配合工作。所以自JDK 9开始,ParNew加CMS收集器的组合就不再是官网 举荐的服务端模式下的收集器解决方案了。官网心愿它能齐全被G1所取代,甚至还勾销了ParNew加 Serial Old以及Serial加CMS这两组收集器组合的反对(其实本来也很少人这样应用) 特点:---1、ParNew 收集器除了多线程收集之外,其余与 Serial 收集器相⽐并没有太多翻新之处,但它却是许多运⾏在 Server 模式下(之前咱们的Serial是运行在client端的)的虚拟机中⾸选的新⽣代收集器,其中有⼀个与性能⽆关但很重要的起因是,除了 Serial 收集器外,⽬前只有它能与CMS收集器配合⼯作(ParNew个别跟CMS一起来应用;咱们的ParNew个别用来回收新生代,而CMS个别用来回收老年代)。---2、使⽤-XX: ParallelGCThreads 参数来限度垃圾收集的线程数。(这个设置有一个参照点:大家晓得咱们的cpu有一个核数,这个核数代表了咱们cpu同时能解决多少个线程的数量,如果cpu核数是8的话,倡议将咱们的ParallelGCThreads参数值设置为8)---3、多线程操作存在高低⽂切换的问题,所以倡议将-XX: ParallelGCThreads设置成和CPU核数雷同,如果设置太多的话就会产⽣高低⽂切换耗费。(所以并不是咱们这个ParallelGCThreads参数越大越好、他会有上下文切换的生效) 收集器的上下文语境中:并发与并行并发:形容GC线程跟用户线程之间关系:GC线程跟用户线程同时运行。并行:形容GC线程间关系:多个GC线程同时运行,用户线程暂停。 Parallel Scavenge收集器回顾:上一节咱们解说了ParNew垃圾回收器,ParNew垃圾回收器是在Serial根底上实现的一个多线程的裁减。多线程的垃圾回收器除了ParNew之外,还有Parallel Scavenge垃圾回收器。 是什么:管制的吞吐量的ParNew收集器(也能够叫做:基于标记-复制算法实现的多线程吞吐量优先的垃圾回收器) 特点: 1、多线程垃圾回收 2、关注吞吐量 3、参数可调。 与其余垃圾回收器区别: 关注点:其余垃圾回收线程关注缩短垃圾收集时用户线程的进展工夫。Parallel Scavenge收集器的指标则是达到一个可管制的吞吐 量(Throughput) ...

January 2, 2021 · 1 min · jiezi

关于jvm:JVM-OOM

既然抉择了远方,即便天寒地冻,路遥马亡,我本就赤贫如洗,又有何惧。OOM(内存溢出)是一个让人很头疼的问题,呈现 OOM 的问题有很多,上面就 OOM 可能呈现的起因进行介绍。 1、堆空间太小用以下参数启动 jvm -Xms20m -Xmx20m public class OOMTest { public static void main(String[] args) { Byte[] bs = new Byte[1024 * 1024 * 30]; }}最大堆、初始化堆均为 20m,程序创立了 30m 的数组,间接 OOM。 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space1.1、解决形式调大-Xmx 的值通过内存剖析工具,找出占用内存较多的对象2、间接内存溢出间接内存属于堆外内存,是间接向操作系统申请内存空间。 间接内存申请速度个别小于堆内存的申请速度,然而访问速度会比堆内存的访问速度快。 间接内存并没有齐全的归 GC 治理,使用不当,也会造成 OOM。 2.1、间接内存不足,溢出用以下参数启动 jvm-Xms20m -Xmx20m public class OOMTest { public static void main(String[] args) { while (true) { ByteBuffer.allocateDirect(1024 * 1024 * 30); } }}间接内存最大值为 20m, 所以间接溢出 ...

January 1, 2021 · 1 min · jiezi

关于jvm:JVM你真的了解对象吗

摘要本节次要解说java中对象是如何创立?如何布局?如何拜访? 内容Java是一门面向对象的程序语言,Java程序运行过程中无时无刻都有对象被创立,语言层面上,常见对象,创立一个对象是一个new关键字而已,在虚拟机中,对象又是怎么创立的呢? 1、对象创立举例来说;咱们通过以下代码创立一个对象A; public class A { private int a; public static void main(String[] args) { A a = new A(); }}他对应的底层jvm虚拟机中又是一个怎么的过程呢? 对象的创立次要包含六个步骤: 类的初始化对于援用变量初始化为null,对于根本类型变量就具体赋值;init办法执行:次要包含静态方法、动态代码块等。 对象查看:虚拟机遇到new指令时,首先去查看这个指令参数是否在常量池中定位到一个类的符号援用;并查看这个符号援用代表的类是否曾经被加载、解析、初始化。类加载:虚拟机进行符号援用的类加载、解析、初始化。分配内存:类查看通过后,虚拟机为新对象分配内存;对象调配的内存在虚拟机类加载实现之后就是能够确定的;为对象调配空间的工作等同于把一块确定大小的内存从java堆中部划分进去。初始化:

December 30, 2020 · 1 min · jiezi

关于jvm:JVM运行时数据区

摘要自从java面世以来,声势浩大,提出“Write Once,Run Anywhere";Java相比于其余C/C++语言的劣势:在JVM内存治理之下,不再须要为每一个new操作去手动分配内存和free/delete的内存开释;不容易呈现内存透露和内存溢出等问题。本节次要解说Java运行时数据区:线程共享数据区:办法区、堆 线程隔离数据去:虚拟机栈、本地办法栈、程序计数器思维导图 内容1、运行时数据区蕴含哪几局部?java虚拟机运行时数据区次要蕴含以下几个模块? 线程共享数据区:办法区、堆线程隔离数据区:虚拟机栈、本地⽅法栈、堆、程序计数器程序计数器:用来记录字节指令的行号;咱们将.java文件编译成.class文件后,交由JVM去执行的时候,程序一行一行执行就是交给程序计数器去做的Java虚拟机栈:比方咱们写一个办法,JVM执行这个办法的时候,相似于创立了一个栈针;入栈到出栈就是这个办法调用的整个过程;对应的就是一个办法一个栈。本地办法栈:就是JVM虚拟机执行一些本地办法库;咱们在进行一个CAS操作的时候:通过unsafe的compareAndSwapInt调到本地办法库外面的native办法。那么这些native办法就是在本地办法栈外面运行的。办法区:寄存类信息,类变量,动态变量。堆:简直所有数组跟对象的创立都是在堆外面。 2、程序计数器是什么?程序计数器(Program Counter Register)是一块较小的内存空间,是以后线程所执行的字节码的行号指示器。 为什么?字节码解释器工作时候通过改变程序计数器的值来选取下一条要执行的字节码指令;线程的各个根底性能都须要依赖这个计数器来实现。java虚拟机多线程是通过线程轮流切换并调配处理器执行的工夫实现,在任意一个确定的时刻,一个处理器都只会执行一条线程指令,因而为了线程切换后能复原到正确的执行地位。每个线程都须要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储。咱们称这类内存区域为“线程公有”的内存。特点?内存区域中唯⼀⼀个在Java虚拟机标准中没有规定任OutOfMemoryError 状况的区域。因为程序计数器自身不须要咱们程序员去操作,所以不会呈现OOM。 实战演练咱们创立一个Person类;领有属性age,提供getter/setter办法。 public class Person { public int getAge() { return age; } public void setAge(int age) { this.age = age; } private int age;}咱们应用javac进行编译源代码为字节码,而后应用javap查看字节码,如下: 咱们通过javap -l能够看到字节码的内容;咱们看到外面有两个办法:setAge;getAge;而后咱们看到getAge办法在第5行。setAge在8,9行。当初如果咱们须要执行Person外面的getAge办法;咱们说到,这个时候咱们可能多线程来执行这个办法;所以这块运行时数据库是一块独立的内存;咱们晓得程序计数器是一块线程隔离的数据库,所以每块线程有本人独立的程序计数器。这块内存是在咱们的线程外面独自隔离开来的,不同的线程保护了本人不同的程序计数器。 3、java虚拟机栈上一节咱们解说了程序计数器;程序计数器是线程公有的一块小内存,线程公有的内存除了程序计数器之外还有两块:java虚拟机栈,本地办法栈。办法区跟堆绝对的就是线程共享的内存。 是什么?Java办法执行的一块内存区域;随着线程办法执行时候压栈出栈,此内存区域也会销毁,他是跟线程的生命周期雷同的。 为什么?办法的执行是依照栈数据结构;每个办法在执行的同时都会创立一个栈帧(Stack Frame)用于寄存局部变量表、操作数栈、动静链接、⽅法出⼝等信息。每⼀个⽅法从调⽤直⾄执⾏实现的过程,就对应着⼀个栈帧在虚拟机栈中⼊栈到出栈的过程。 特点?局部变量表寄存了编译期可知(指代咱们这些根底数据类型他所对应的数据的大小,大小是可晓得的)的各种根本数据类型(boolean、byte、char、short、int、 float、long、double)以及对象引⽤(reference 类型)如果线程申请的栈深度⼤于虚拟机所容许的深度,将抛出 StackOverflowError异样。实战public class StackDemo { public static void a(){ System.out.println("method a executed"); } public static void b(){ //a(); b(); System.out.println("method b executed"); } public static void main(String[] args){ b(); System.out.println("method main executed"); }}咱们批改b()的调用为本人。下面进行了递归调用:b()办法执行一直入栈操作,而没有出栈,导致所调配的栈内存不够,从而呈现栈内存溢出。栈长度超过制订长度大小。后果如下: ...

December 29, 2020 · 1 min · jiezi

关于jvm:JVM笔记1-运行时数据区

[TOC] (一)java内存区域治理C/C++每一个new操作都须要本人去delete/free,而java外面有虚拟机主动治理内存,不容易呈现内存透露或者溢出的问题,然而不容易呈现不代表不呈现,理解虚拟机怎么应用和治理内存是非常重要的是,对程序优化或者问题排查有帮忙。 运行时区域次要分为: 线程公有: 程序计数器:Program Count Register,线程公有,没有垃圾回收虚拟机栈:VM Stack,线程公有,没有垃圾回收本地办法栈:Native Method Stack,线程公有,没有垃圾回收线程共享: 办法区:Method Area,以HotSpot为例,JDK1.8后元空间取代办法区,有垃圾回收。堆:Heap,垃圾回收最重要的中央。 1.1 程序计数器空间很小,以后线程执行的字节码的行号指示器(线程独有,批示以后执行到哪,下一步须要执行哪一个字节码),分支,循环,跳转,异样解决,线程复原都须要依赖它。线程公有:java多线程其实是线程轮流切换并调配处理器执行工夫的形式实现,一个核一个具体的工夫点,只会执行一个线程的指令。线程切换须要保留和复原正确的执行地位(爱护和复原现场),所以不同的线程须要不同的程序计数器。 执行java办法时,程序计数器记录的是正在执行的字节码指令地址执行Native办法,程序计数器为空惟一一个没有规定任何OutOfMemory的区域,也没有GC(垃圾回收)。 1.2 虚拟机栈线程公有,生命周期和线程一样,次要是记录该线程Java办法执行的内存模型。虚拟机栈外面放着好多栈帧。留神虚拟机栈,对应是Java办法,不包含本地办法。一个Java办法执行会创立一个栈帧,一个栈帧次要存储: 局部变量表操作数栈动静链接办法进口每一个办法调用的时候,就相当于将一个栈帧放到虚拟机栈中(入栈),办法执行实现的时候,就是对应着将该栈帧从虚拟机栈中弹出(出栈)。 每一个线程有一个本人的虚拟机栈,这样就不会混起来,如果不是线程独立的话,会造成调用凌乱。 大家平时说的java内存分为堆和栈,其实就是为了简便的不太谨严的说法,他们说的栈个别是指虚拟机栈,或者虚拟机栈外面的局部变量表。 局部变量表个别寄存着以下数据: 根本数据类型(boolean,byte,char,short,int,float,long,double)对象援用(reference类型,不肯定是对象自身,可能是一个对象起始地址的援用指针,或者一个代表对象的句柄,或者与对象相干的地位)returAddress(指向了一条字节码指令的地址)局部变量表内存大小编译期间确定,运行期间不会变动。空间掂量咱们叫Slot(局部变量空间)。64位的long和double会占用2个Slot,其余的数据类型占用1个Slot。 异样: StackOverflowError:线程申请的栈深度大于虚拟机容许的深度OutOfMemoryError:内存不足1.3 本地办法栈和虚拟机栈相似,对应本地办法,Native,虚拟机标准容许语言,应用形式和数据结构不同,有些可能将虚拟机栈和本地办法栈合并。异样与虚拟机栈统一: StackOverflowError:线程申请的栈深度大于虚拟机容许的深度OutOfMemoryError:内存不足1.4 java堆堆是内存治理最大的一块,线程共享。 虚拟机标准中说,所有的对象实例和数组都要在堆上调配。然而实际上不是所有的对象都在堆上调配,这个和JIT编译器的倒退和逃逸剖析技术相干。Why?// TODO堆的细分:新生代,老年代,再细分有Eden,From survivor,To survivor等。 堆中也有可能有线程公有的区域,调配缓冲区。 物理上能够不间断,然而逻辑上是间断的。 异样: OutOfMemoryError:内存不足1.5 办法区名为非堆,然而理论和堆一样,是线程共享的区域,次要存贮以下信息: 已被虚拟机加载的类信息常量动态变量即时编译器编译后的代码办法区不等于永恒代,批示Hotspot虚拟机将GC分代收集拓展到办法区,也就是用永恒代实现了办法区,而其余的虚拟机不肯定,不是固定的。JDK1.7将永恒代的字符串常量移出了。 办法区回收垃圾的成果不是很好,能够抉择不回收,虚拟机能够决定,当然也可能产生内存透露。异样: OutOfMemoryError:内存调配异样1.5.1 运行时常量池运行时常量池时办法区的一部分,然而不是全副,Class文件次要包含: 类的版本字段办法接口常量池,寄存编译产生的字面量和符号援用,个别除了形容Class文件的符号援用,还有间接援用也在外面。是动静的,运行时能够产生,比方String.intern()办法。异样: OutOfMemoryError:内存调配异样(二)间接内存不是虚拟机运行时数据区,也不是标准规定的区域,然而应用频繁且可能会有OutOfMemoryError:内存调配异样呈现。比方,NIO(1.4)基于Channel与Buffer的I/O,能够用Native函数间接调配堆外内存,通过存储在Java堆中的DirectByteBuffer对象作为援用来操作,进步性能,不须要Java堆和Native堆都来回复制数据。 间接内存受物理的内存,或者处理器寻址空间之类的限度。 本文系JVM学习相干笔记,整顿来自周志明老师的《深刻了解Java虚拟机》,无比钦佩,强烈推荐! 【作者简介】: 秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使迟缓,驰而不息。这个世界心愿所有都很快,更快,然而我心愿本人能走好每一步,写好每一篇文章,期待和你们一起交换。 此文章仅代表本人(本菜鸟)学习积攒记录,或者学习笔记,如有侵权,请分割作者核实删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有谬误之处,还望指出,感激不尽~

December 26, 2020 · 1 min · jiezi

关于jvm:JVM-垃圾回收算法概述

[toc] 既然抉择了远方,即便天寒地冻,路遥马亡,我本就赤贫如洗,又有何惧。标记革除法标记革除算法工作分为 2 个阶段。第一个阶段,先依据 GC Root 标记 可达对象第二阶段,将不可达对象,间接革除。 算法毛病:会产生大量的内存碎片 工作原理如下图所示: 复制算法复制算法思维: 将内存一分为二,每次只应用其中一块。产生垃圾回收时,将存活的对象复制到另一块未应用的内存清空应用的内存块中的对象,两者角色调换,实现垃圾回收。复制算法,用于新生代。java 借鉴复制算法,将新生代内存划分为 eden、from、to。默认比例为 8:1:1。eden, from 存活的对象会被放入 to 中,eden, from 不可达对象会被清空。清空后, from、to 角色调换 算法前提:新生代对象朝生夕死 工作原理如下图所示: 标记压缩法标记压缩法思维: GC Root 可达性剖析 标记可达对象将可达对象对立放至内存一端清理边界外的空间标记压缩不会产生内存碎片 工作原理如下图所示: 分代算法JVM 将对象分为新生代和老年代。新生代应用复制算法回收垃圾;老年代应用标记压缩或标记革除回收垃圾。 新生代对象大多朝生夕死,失常来说 ygc 频率高,速度快。如果 老年代对象援用了新生代对象,那么就须要扫描老年代对象。因而会造成 ygc 效率低下,须要全堆扫描。JVM 引入 卡表(card table)数据结构 来解决 老年代对象援用新生代对象,造成 ygc 效率低下 问题。 卡表JVM 通过卡表,记录老年代指向新生代的援用。卡表为比特位数组,每个比特位能够用于示意老年代某一区域中所有对象是否持有新生代援用。0 示意没有持有,1示意持有。在 GC Root 扫描时,只需扫描卡表位为 1 的老年代空间即可,防止全堆扫描,晋升了 ygc 效率。 分区算法分区算法将整个堆空间分成间断的不同小区间,每一个小区间独立应用,独立回收。分区算法的益处是,能够管制一次 GC 回收的区间,即管制 GC 回收工夫

December 21, 2020 · 1 min · jiezi

关于jvm:JVM-垃圾回收概述

我是清都山水郎,天教懒慢带疏狂。曾批给露支风券,累奏流云借月章。 诗万首,酒千觞,几曾着眼看侯王。玉楼金阙慵归去,且插梅花醉洛阳。在进行垃圾回收的时候,对于 JVM 而言,什么对象才算是垃圾呢?如何判断某些对象是垃圾呢?很显著的,曾经没有被应用的对象,就是垃圾。 援用计数法援用计数法是用于判断对象是垃圾的一种形式。如果被其余对象援用,那么对象的援用计数就会+1。当援用生效时,援用计数就-1。当援用等于0时,即示意对象已无用。援用计数尽管实现简略,然而无奈解决循环援用的问题,也因而没有被采纳。 循环援用对象A援用对象B,对象B的援用+1;对象B援用对象A,对象A的援用+1。然而对象A,对象B没有被应用。从上帝视角来看,A、B曾经是无用对象了。然而因为援用计数不等于0,因而不会被认为是垃圾。 GC Root 可达性剖析JVM 应用 GC Root 可达性分析判断对象是否为垃圾。对象是否应该被回收,判断条件如下 从 Root 对象登程,如果对象可被拜访到,那么此对象就不应该被回收。对象被回收前,可执行一次 finalize() 办法,如果在 finalize() 办法中复活,则不会被回收。须要留神的是:finalize() 办法只会被一个对象执行一次。根对象定义想必,你肯定很纳闷,what is root object?。官网文档定义: Garbage Collection RootsA garbage collection root is an object that is accessible from outside the heap. The following reasons make an object a GC root: System ClassClass loaded by bootstrap/system class loader. For example, everything from the rt.jar like java.util.* . JNI LocalLocal variable in native code, such as user defined JNI code or JVM internal code. ...

December 16, 2020 · 4 min · jiezi

关于jvm:JVM类加载子系统

JVM-类加载子系统JVM架构图 1.类加载子系统作用类加载子系统负责从文件系统或者网络中加载Class文件,class文件在文件结尾有特定的文件标识;ClassLoader只负责class文件的加载,至于它是否能够运行,则由Execution Engine决定加载的类信息寄存于一块成为办法区的内存空间。除了类信息之外,办法区还会寄存运行时常量池信息,可能还包含字符串字面量和数字常量(这部分常量信息是Class文件中常量池局部的内存映射)1.1类加载器ClassLoader角色 1.2加载通过一个类的全限定获取定名义此类的二进制字节流;将这个字节流所代表的的动态存储构造转化为办法区的运行时数据;在内存中生成一个代表这个类的java.lang.Class对象,作为办法区这个类的各种数据的拜访入口 1.3 链接1.3.1 验证:目标在于确保Class文件的字节流中蕴含信息合乎以后虚拟机要求,保障被加载类的正确性,不会危害虚拟机本身平安。次要包含四种验证,文件格式验证,源数据验证,字节码验证,符号援用验证。1.3.2 筹备为类变量分配内存并且设置该类变量的默认初始值,即零值;这里不蕴含用final润饰的static,因为final在编译的时候就会调配了,筹备阶段会显式初始化;这里不会为实例变量调配初始化,类变量会调配在办法去中,而实例变量是会随着对象一起调配到java堆中。1.3.3 解析将常量池内的符号援用转换为间接援用的过程。事实上,解析操作往往会随同着jvm在执行完初始化之后再执行符号援用就是一组符号来形容所援用的指标。符号利用的字面量模式明确定义在《java虚拟机标准》的class文件格式中。间接援用就是间接指向指标的指针、绝对偏移量或一个间接定位到指标的句柄解析动作次要针对类或接口、字段、类办法、接口办法、办法类型等。对应常量池中的CONSTANT_Class_info/CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。1.4初始化初始化阶段就是执行类结构器办法clinit()的过程。此办法不须要定义,是javac编译器主动收集类中的所有类变量的赋值动作和动态代码块中的语句合并而来。 咱们留神到如果没有动态变量c,那么字节码文件中就不会有clinit办法 结构器办法中指令按语句在源文件中呈现的程序执行 clinit()不同于类的结构器。(关联:结构器是虚拟机视角下的init())若该类具备父类,jvm会保障子类的clinit()执行前,父类的clinit()曾经执行结束 虚拟机必须保障一个类的clinit()办法在多线程下被同步加锁。 2.类加载器分类JVM反对两种类型的加载器,别离为疏导类加载器(BootStrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)从概念上来讲,自定义类加载器个别指的是程序中由开发人员自定义的一类类加载器,然而java虚拟机标准却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。无论类加载器的类型如何划分,在程序中咱们最常见的类加载器始终只有三个,如下所示: 2.1 自定义类与外围类库的加载器对于用户自定义类来说:应用零碎类加载器AppClassLoader进行加载java外围类库都是应用疏导类加载器BootStrapClassLoader加载的/** * ClassLoader加载 */public class ClassLoaderTest { public static void main(String[] args) { //获取零碎类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //获取其下层 扩大类加载器 ClassLoader extClassLoader = systemClassLoader.getParent(); System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@610455d6 //获取其下层 获取不到疏导类加载器 ClassLoader bootStrapClassLoader = extClassLoader.getParent(); System.out.println(bootStrapClassLoader);//null //对于用户自定义类来说:应用零碎类加载器进行加载 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //String 类应用疏导类加载器进行加载的 -->java外围类库都是应用疏导类加载器加载的 ClassLoader classLoader1 = String.class.getClassLoader(); System.out.println(classLoader1);//null }}2.2 虚拟机自带的加载器①启动类加载器(疏导类加载器,BootStrap ClassLoader) ...

December 11, 2020 · 1 min · jiezi

关于jvm:JVM中8种垃圾收集器小结

JDK 倒退历史JAVA 1.0,代号Oak橡树)于 1996-01-23 发行 JAVA 1.11997-02-19 发行, 次要更新内容: 引入 JDBC增加外部类反对引入 JAVA BEAN引入 RMI引入反射JAVA 1.2, 代号 Playground(操场)1998-12-8 发行,次要更新内容: 引入汇合框架对字符串常量做内存映射引入 JIT(Just In Time)编译器引入打包文件数字签名引入管制受权拜访系统资源策略工具引入 JFC(Java Foundation Classes),包含 Swing1.0,拖放和 Java2D 类库引入 Java 插件JDBC 中引入可滚动后果集,BLOB,CLOB, 批量更新和用户自定义类型Applet 中增加声音反对JAVA1.3,代号 Kestrel(红隼)2000-5-8 公布,次要更新内容: 引入 Java Sound API引入 jar 文件索引对 Java 各方面多了大量优化和加强Java Platform Debugger Architecture 用于 Java 调式的平台。JAVA 1.4,代号 Merlin(隼)2004-2-6 公布(首次在 JCP 下发行),次要更新内容: 增加 XML 解决增加 Java 打印服务(Java Print Service API)引入 Logging API引入 Java Web Start引入 JDBC 3.0 API引入断言引入 Preferences API引入链式异样解决反对 IPV6反对正则表达式引入 Image I/O APINIO,非阻塞的 IO,优化 Java 的 IO 读取。JAVA 5.0,代号 Tiger(老虎),有重大改变2004-9-30 公布,次要更新内容: ...

December 10, 2020 · 2 min · jiezi

关于jvm:总结G1垃圾收集器面试题

介绍一下G1垃圾收集器G1收集器随同JAVA9于2017-9-21公布,G1收集器兼顾低提早和高吞吐在服务端运行,HotSpot团队冀望取代CMS收集器。也就是在满足进展工夫的状况下获取最大的吞度量。有两种收集模式Young GC和Mixed GC。G1收集器将堆内存划分成大小相等的Region,新生代,老年代也就成了逻辑概念。整体上采纳的是标记-整顿算法,部分采纳了复制算法。G1是jdk1.9的默认垃圾收集器,-XX:+UseG1GC开启 G1收集器Region的类型新生代老年代未应用大对象区 新生代,老年代的Region不再是一块间断的空间。当然寄存大对象的Region必须要间断。G1收集器中大对象怎么调配对象的大小<0.5个RegionSize间接存在新生代Eden Region区对象的大小>=0.5个RegionSize且对象的大小<1个RegionSize,存到大对象区Humongous Region对象的大小>=1个RegionSize存到间断的大对象区Humongous Region怎么指定Region的大小应用-XX:G1HeapRegionSize来指定Region的大小,Region的大小必须是2的幂次方,最大32M。G1垃圾收集器的运行过程?初始标记(stop the world)比拟GC Roots间接援用的对象同时标记,同时标记GC Roots对象所在的Region称为Root Region根分区扫描扫描整个老年代Region的Rset,标记蕴含初始标记阶段的Root Region的Region并发标记遍历根分区扫描步骤标记好Region,标记所有可达对象,和利用线程并发执行从新标记(stop the world)因为并发标记是和利用线程并发执行的,所以不可避免的有些对象会发生变化,G1 GC清空 SATB缓冲区,跟踪未被拜访的存活对象,并执行援用解决。抉择革除(stop the world)评估每个Region的垃圾量,选取回收成果最好的若干Region收集(取决于-XX:MaxGCPauseMillis设置值,默认是200ms)。在规定进展工夫内,获取最大的吞吐量。什么是Rset每个Region初始化时,都会初始化一个Rset(remembered set),每个Region都蕴含一个Rset,Rset的作用是记录了哪些内存区域(Region)堆以后Region的援用。Rset是一个空间换工夫的数据结构。有了Rset能够防止对整个堆进行扫描。G1和CMS的区别G1采纳标记-整顿算法,CMS采纳标记-革除算法,所以G1不会产生很多垃圾碎片.G1的STW(stop the world)可控,能够应用-XX:MaxGCPauseMillis设置默认200msG1的Young GC模式能够工作在年老代,而独自的CMS只能工作在老年代.G1的利用场景服务端多核CPU,JVM占用较大的利用(至多大于4G)。利用在运行过程中产生大量的碎片,须要常常压缩。想要更可控,可预期的进展工夫;避免高并发下利用雪崩景象。

December 9, 2020 · 1 min · jiezi

关于jvm:总结CMS常见面试题

谈谈对CMS的认知?CMS(concurrent mark sweep)在jdk1.5中曾经开始应用了,2004年9月30日,JDK1.5公布。CMS设计的指标就是获取最低进展工夫(stop the world进展工夫),它是基于标记-革除算法实现的。罕用的场景是互联网网站(对服务响应要求较高),它是一个老年代垃圾收集器,能够和Serial收集器,Parallel New收集器配合应用。当并行模式(concurrent mode failure)失败时CMS会进化成Serial Old.CMS收集器的工作流程(步骤)是什么样的?次要四个阶段初始标记:只标记和GC Roots能直连的对象,速度快,会产生(stop the world)并发标记:和利用线程并发执行,遍历初始标记阶段标记过的对象,标记这些对象的可达对象。从新标记:因为并发标记是和利用线程是并发执行的,所以有些标记过的对象产生了变动。这个过程比初始标记用时长,然而比并发标记阶段用时短。会产生(stop the world)并发革除:和利用线程一起运行。基于标记对象,间接清理对象。CMS的毛病?垃圾碎片问题起因:因为CMS采纳的是标记-革除算法,所以不可避免会有内存碎片问题。解决:应用-XX:+CMSFullGCsBeforeCompaction=n,意思是在上次CMS并发GC执行过后,到底还要做多少Full GC才做压缩。默认是0,也就是说每次CMS GC顶不住了转入Full GC时都要压缩。并发模式失败(concurrent mode failure)起因:CMS垃圾清理线程和利用线程是并发执行的,如果在清理过程中老年代空间有余不能包容新对象。解决:应用-XX:+UseCMSInitiatingOccupancyOnly和-XX:CMSInitiatingOccupancyFraction=60,指定CMS对内存的占用率到60%时开始GC。从新标记阶段工夫过长解决:应用-XX:+CMSScavengeBeforeRemark,在执行从新标记之前,先做一次Young GC,目标在于较少年老代对老年代的有效援用,升高从新标记的开销。为什么配置了CMS GC,却触发了Full GC?大对象调配时,年老代放不下,间接去老年代,后果老年代也放不下。内存碎片问题(应用标记-革除算法的毛病)CMS GC失败(concurrent mode failure导致)jmap -histo 人为执行了命令

December 8, 2020 · 1 min · jiezi

关于jvm:什么是GC-Roots

可达性算法中以GC Root对象为终点开始搜寻。 什么是GC Root对象虚拟机栈中援用的对象public class Rumenz{ public static void main(String[] args) { Rumenz a = new Rumenz(); a = null; }}a是栈帧中的本地变量,a就是GC Root,因为a=null,a与new Rumenz()对象断开了链接,所以对象会被回收。办法区类的动态成员援用的对象public class Rumenz{ public static Rumenz=r; public static void main(String[] args){ Rumenz a=new Rumenz(); a.r=new Rumenz(); a=null; }}栈帧中的本地变量a=null,因为a断开了与GC Root对象(a对象)的分割,所以a对象会被回收。因为给Rumenz的成员变量r赋值了变量的援用,并且r成员变量是动态的,所以r就是一个GC Root对象,所以r指向的对象不会被回收。办法区常量援用的对象public class Rumenz{ public static final Rumenz r=new Rumenz(); public static void main(String[] args){ Rumenz a=new Rumenz(); a=null; } }常量r援用的对象不会因为a援用的对象的回收而被回收。本地办法栈中JNI援用的对象JNIEXPORT void JNICALL Java_com_pecuyu_jnirefdemo_MainActivity_newStringNative(JNIEnv *env, jobject instance,jstring jmsg) {... // 缓存String的class jclass jc = (*env)->FindClass(env, STRING_PATH);} ...

December 7, 2020 · 1 min · jiezi

关于jvm:CMS收集器中两个致命的问题

CMS是一个很好的并发垃圾收集器,然而应用过程中会产生两个重要的问题。promotion failed 降职失败concurrent mode failure 收集器无奈解决浮动垃圾promotion failed 降职失败起因该问题产生在Minor GC过程中,Survivor Space放不下转移的对象,老年代也放不下(promotion failed产生的时候老年代CMS还没有机会进行回收,又放不下转移到老年代的对象,下一步就会产生concurrent mode fialure,产生STW降级为Serial Old)上面是一条promotion failed失败的日志 106.641: [GC 106.641: [ParNew (promotion failed): 14784K->14784K(14784K), 0.0370328 secs]106.678: [CMS106.715: [CMS-concurrent-mark: 0.065/0.103 secs] [Times: user=0.17 sys=0.00, real=0.11 secs](concurrent mode failure): 41568K->27787K(49152K), 0.2128504 secs] 52402K->27787K(63936K), [CMS Perm : 2086K->2086K(12288K)], 0.2499776 secs] [Times: user=0.28 sys=0.00, real=0.25 secs]concurrent mode failure产生的起因concurrent mode failure是CMS特有的谬误,CMS的垃圾清理线程和用户线程是并行进行的. 老年代正在清理,从年老代降职了新对象,或者调配的大对象在新生代放不下,间接在老年代分配内存,这时老年代也放不下,则会抛出concurrent mode failureconcurrent mode failure的影响老年代的垃圾收集器从CMS进化成Serial Old,所有用户线程被暂停,进展工夫变长。解决方案CMS触发太晚-XX:CMSInitiatingOccupancyFraction=N 是指设定CMS在对内存占用率达到N%的时候开始GC(因为CMS会有浮动垃圾,所以个别都较早启动GC);将:-XX:CMSInitiatingOccupancyFraction=N调小空间碎片太多开启空间碎片整顿,并将空间碎片整顿周期设置在正当范畴,-XX:CMSFullGCsBeforeCompaction作用:设置在执行多少次Full GC后对内存空间进行压缩整顿。-XX:+UseCMSCompactAtFullCollection (空间碎片整顿)-XX:CMSFullGCsBeforeCompaction=n垃圾产生太快降职阈值太小Survivor空间过小Eden区过小,导致降职速率过快存在大对象

December 6, 2020 · 1 min · jiezi

关于jvm:JavaParallel-GC介绍

JVM 垃圾收集器倒退历史 JDK1.8中应用jmap -heap pid下面会呈现Parallel GCjmap -heap 18378Attaching to process ID 18378, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.261-b12using thread-local object allocation.Parallel GC with 4 thread(s) ###JVM垃圾收集器的倒退历史中,咱们并没有找到Parallel GC,那么它到底代表什么?Parallel GC有两种组合应用-XX:+UseParallelGC参数来启用Parallel Scavenge和PSMarkSweep(Serial Old)收集器组合进行垃圾收集。(图上能够找到)应用-XX:+UserParallelOldGC参数来启用Parallel scavenge和Parallel Old收集器组合收集。(图上能够找到)Parallel GC起源Young GC / Parallel ScavengeParallel Scavenge收集器(下称PS收集器)也是一个多线程收集器,也是应用复制算法,但它的对象调配规定与回收策略都与ParNew收集器有所不同,它是以吞吐量最大化(即GC工夫占总运行工夫最小)为指标的收集器实现,它容许较长时间的STW换取总吞吐量最大化。Full GC / PSMarkSweep(Serial Old)在Parallel Scavenge收集器架构中自身有PS MarkSweep收集器来进行老年代收集,但因为PS MarkSweep与Serial Old实现十分靠近,因而官网的许多材料都间接以Serial Old代替PS MarkSweep进行解说。应用-XX:+UseParallelGC参数便是开启PSScavenge和PSMarkSweep的组合,即是只有Young GC是并行的,Full GC依然是串行,应用标记-整顿算法。 Full GC / PSCompact(ParallelOld GC)起初开发者开发了基于LISP2算法的并行版的Full GC收集器来收集整个GC堆,名为PSCompact。应用-XX:+UseParallelOldGC参数来便是开启PSScavenge和PSCompact的组合,将Young GC和Full GC都并行化了。收集器Parallel Scavenge新生代并行回收器,内存散布应用的复制算法。Parallel Scavenge次要关注的是利用的吞吐量,而其余收集器关注的次要是尽可能的缩短STW(stop the word)的工夫。吞度量=t1/(t1+t2)t1运行用户代码的总工夫t2运行垃圾收集的总工夫比方,虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。Parallel Scavenge收集器提供了两个参数来用于准确管制吞吐量,一是管制最大垃圾收集进展工夫的-XX:MaxGCPauseMillis参数,二是管制吞吐量大小的-XX:GCTimeRatio参数-XX:MaxGCPauseMillis参数的值是一个大于0的毫秒数,收集器将尽可能的保障回收消耗的工夫不超过设定的值,然而,并不是越小越好,GC进展工夫缩短是以就义吞吐量和新生代空间来换取的,如果设置的值太小,将会导致频繁GC,这样尽管GC进展工夫下来了,然而吞吐量也下来了。比方收集500MB时候,须要每10秒收集一次,每次回收耗时100ms;如果收集300MB的时候,须要每5秒收集一次,每次回收耗时70ms,尽管每次回收耗时更少,然而工作频次进步,导致吞吐量反而升高了。-XX:GCTimeRatio参数的值是一个大于0且小于100的整数,也就是垃圾收集工夫占总工夫的比率,默认值是99,就是容许最大1%(即1/(1+99))的垃圾收集工夫。Parallel Scavenge有个重要的个性,是反对GC自适应的调节策略,应用-XX:UseAdaptiveSizePolicy参数开启,开启之后,虚构机会依据以后零碎运行状况收集监控信息,动静调整新生代的比例、老年大大小等细节参数,以提供最合适的进展工夫或最大的吞吐量。开启这个参数之后,就不须要再设置新生代大小,Eden与S0/S1的比例等等参数。Parallel Old ...

December 5, 2020 · 6 min · jiezi

关于jvm:jstack查看某个进程堆栈信息

jstack次要用来查看某个过程内线程的堆栈信息一个死锁的模仿代码package test;import java.util.concurrent.Executor;import java.util.concurrent.Executors;/** * @className: test * @description: TODO 类形容 * @author: mac * @date: 2020/12/3 **/public class test { public static Executor executor=Executors.newFixedThreadPool(5); public static Object lock=new Object(); public static void main(String[] args) { Task task1=new Task(); Task task2=new Task(); executor.execute(task1); executor.execute(task2); } static class Task implements Runnable{ @Override public void run() { synchronized (lock){ calc(); } } private void calc() { int n=0; for(;;){ n++; } } }}top 查看零碎中最耗资源的java过程pid> top3913 root 20 0 6751200 18888 10452 S 100.3 0.1 0:20.71 java 3494 root 20 0 750324 46036 16564 S 0.7 0.3 48:31.49 containerd 4773 root 20 0 577428 33312 10540 S 0.7 0.2 77:03.92 jdog-kunlunmirr 23139 prometh+ 20 0 1559032 117636 28908 S 0.7 0.7 32:40.89 prometheus 1 root 20 0 191144 4132 2648 S 0.3 0.0 10:33.95 systemd 找出上一步pid内最耗cpu的线程pid> top -Hp 3913 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3929 root 20 0 6751200 18888 10452 R 99.7 0.1 0:59.54 java 3913 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.00 java 3914 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.03 java 3915 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.00 java 3916 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.00 java 3917 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.00 java 3918 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.00 java 3919 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.00 java 3920 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.00 java 3922 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.00 java 3923 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.00 java 3924 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.00 java 3925 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.00 java 3926 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.00 java 3927 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.00 java 3928 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.01 java 3930 root 20 0 6751200 18888 10452 S 0.0 0.1 0:00.01 java 计算该线程的pid的十六进制> printf "%x\n" 3929f59定位代码> jstack 3913 | grep f59 -A 50"pool-1-thread-1" #9 prio=5 os_prio=0 tid=0x00007f88bc100000 nid=0xf59 runnable [0x00007f88a8a42000] java.lang.Thread.State: RUNNABLE at test.test$Task.calc(test.java:35) at test.test$Task.run(test.java:28) - locked <0x000000076d45d8d8> (a java.lang.Object) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)"Service Thread" #8 daemon prio=9 os_prio=0 tid=0x00007f88bc0da800 nid=0xf57 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE"C1 CompilerThread2" #7 daemon prio=9 os_prio=0 tid=0x00007f88bc0bd800 nid=0xf56 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE"C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f88bc0bb800 nid=0xf55 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f88bc0b8800 nid=0xf54 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f88bc0b7000 nid=0xf53 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f88bc086000 nid=0xf52 in Object.wait() [0x00007f88a9149000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000076d408ee0> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144) - locked <0x000000076d408ee0> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f88bc081800 nid=0xf50 in Object.wait() [0x00007f88a924a000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000076d406c00> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:502) at java.lang.ref.Reference.tryHandlePending(Reference.java:191) - locked <0x000000076d406c00> (a java.lang.ref.Reference$Lock) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)"VM Thread" os_prio=0 tid=0x00007f88bc077800 nid=0xf4f runnable "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f88bc01e800 nid=0xf4b runnable "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f88bc020800 nid=0xf4c runnable "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f88bc022000 nid=0xf4d runnable "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f88bc024000 nid=0xf4e runnable 在dump文件中,线程个别会存在上面几种状态RUNNABLE 线程处于执行中BLOCKED 线程被阻塞WAITING 线程正在期待 ...

December 3, 2020 · 3 min · jiezi

关于jvm:Mac-18-如何使用jhsdb-调试

1、运行命令,输出明码 即可弹出sudo java -cp $JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB2、创立类,代码如下 /** * @author * JVM参数 * -Xms30m -Xmx30m -XX:MaxMetaspaceSize=30m -XX:+UseConcMarkSweepGC -XX:-UseCompressedOops * * */public class JVMObject { public final static String MAN_TYPE = "man"; // 常量 public static String WOMAN_TYPE = "woman"; // 动态变量 public static void main(String[] args)throws Exception { Teacher T1 = new Teacher(); T1.setName("李四"); T1.setSexType(MAN_TYPE); T1.setAge(36); for(int i =0 ;i<15 ;i++){ System.gc();//被动触发GC 垃圾回收 15次--- T1存活 } Teacher T2 = new Teacher(); T2.setName("张三"); T2.setSexType(MAN_TYPE); T2.setAge(18); Thread.sleep(Integer.MAX_VALUE);//线程休眠 }}class Teacher{ String name; String sexType; int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSexType() { return sexType; } public void setSexType(String sexType) { this.sexType = sexType; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}运行代码,运行之前请先设置虚拟机参数(垃圾回收器CMS) -Xms30m -Xmx30m -XX:MaxMetaspaceSize=30m -XX:+UseConcMarkSweepGC -XX:-UseCompressedOops ...

December 3, 2020 · 1 min · jiezi

关于jvm:jmap查询JVM堆内存

jmap命令能够获取运行中的jvm的快照,从而离线剖析,查看内存透露,查看一些重大影响性能的大对象的创立,查看零碎中最多的对象,各种对象所占用的内存大小.能够应用jmap生成Heap Dump.什么是堆Dump堆Dump是反馈Java堆应用状况的内存镜像,其中次要蕴含零碎信息,虚拟机属性,残缺的线程Dump,所有类和对象的状态等。个别,在内存不足,GC异样等状况下,咱们就会狐疑内存透露,这个时候就能够制作堆(Dump)来查问具体情况。常见的内存谬误> outOfMemoryError 年轻代内存不足。> outOfMemoryError:PermGen Space 永恒代内存不足。> outOfMemoryError:GC overhead limit exceed 垃圾回收工夫占用零碎运行工夫的98%或以上。jmap -heap pid查看java堆信息Attaching to process ID 18378, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.261-b12using thread-local object allocation.Parallel GC with 4 thread(s)Heap Configuration: MinHeapFreeRatio = 0 # JVM最小闲暇比率 MaxHeapFreeRatio = 100 MaxHeapSize = 4164943872 (3972.0MB) NewSize = 87031808 (83.0MB) MaxNewSize = 1388314624 (1324.0MB) OldSize = 175112192 (167.0MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB)Heap Usage:PS Young GenerationEden Space: capacity = 1314914304 (1254.0MB) used = 794405392 (757.6040191650391MB) free = 520508912 (496.39598083496094MB) 60.41499355383087% usedFrom Space: capacity = 36175872 (34.5MB) used = 23758320 (22.657699584960938MB) free = 12417552 (11.842300415039062MB) 65.6744915506114% usedTo Space: capacity = 37224448 (35.5MB) used = 0 (0.0MB) free = 37224448 (35.5MB) 0.0% usedPS Old Generation capacity = 352321536 (336.0MB) used = 206117376 (196.56884765625MB) free = 146204160 (139.43115234375MB) 58.502633231026785% used应用了Parallel GC垃圾收集器MinHeapFreeRatio 参数用来设置堆空间最小闲暇比例,默认值是 0。当堆空间的闲暇内存小于这个数值时,JVM 便会扩大堆空间。MaxHeapFreeRatio 参数用来设置堆空间最大闲暇比例,默认值是 100。当堆空间的闲暇内存大于这个数值时,便会压缩堆空间,失去一个较小的堆。当-Xmx 和-Xms 相等时MinHeapFreeRatio和MaxHeapFreeRatio 两个参数有效。MaxHeapSize 最大堆内存3972MBNewSize 新生代默认大小 83MBMaxNewSize 新生代最大大小为 1324MBOldSize 老年代大小 167MBNewRatio 新生代和老年代的大小比率 2SurvivorRatio 年老代中Eden和Survivor的比率 8MetaspaceSize 元空间大小 20.796875MBCompressedClassSpaceSize 如果开启了-XX:+UseCompressedOops及-XX:+UseCompressedClassesPointers(默认是开启),则UseCompressedOops会应用32-bit的offset来代表java object的援用,而UseCompressedClassPointers则应用32-bit的offset来代表64-bit过程中的class pointer;能够应用CompressedClassSpaceSize来设置这块的空间大小MaxMetaspaceSize 最大元空间大小 1073741824G1HeapRegionSize G1收集器启用,一个Region的大小能够通过参数-XX:G1HeapRegionSize设定,取值范畴从1M到32M,且是2的指数。jmap pid查看过程的内存映像信息,相似 Solaris pmap 命令应用不带选项参数的jmap打印共享对象映射,将会打印指标虚拟机中加载的每个共享对象的起始地址、映射大小以及共享对象文件的门路全称。这与Solaris的pmap工具比拟类似。 ...

December 2, 2020 · 3 min · jiezi

关于jvm:JVM-进阶-Java字节码

点击 blog即可查看原文和更多的文章,欢送star。什么是Java字节码 Java字节码是由(.Java)文件编译成(.class)的文件。之所以叫字节码是因为(.class)文件是由十六进制组成的。而JVM以两个十六进制值为一组,即以字节为单位进行读取。java之所以可能做到一次编译、到处运行,就是因为不同的平台都会编译成雷同的(.class)文件,所以能力在不同的平台执行。这种跨平台执行的实现,极大的进步了开发和保护的老本。 怎么查看字节码查看字节码有很多种办法,网上也有一些插件能够查看。咱们这里直说一种就是通过javap命令来查看。 先通过javap -help来查看下这个命令怎么应用: 用法: javap <options> <classes>其中, 可能的选项包含: -? -h --help -help 输入此帮忙音讯 -version 版本信息 -v -verbose 输入附加信息 -l 输入行号和本地变量表 -public 仅显示公共类和成员 -protected 显示受爱护的/公共类和成员 -package 显示程序包/受爱护的/公共类 和成员 (默认) -p -private 显示所有类和成员 -c 对代码进行反汇编 -s 输入外部类型签名 -sysinfo 显示正在解决的类的 零碎信息 (门路, 大小, 日期, MD5 散列) -constants 显示最终常量 --module <模块>, -m <模块> 指定蕴含要反汇编的类的模块 --module-path <门路> 指定查找利用程序模块的地位 --system <jdk> 指定查找零碎模块的地位 --class-path <门路> 指定查找用户类文件的地位 -classpath <门路> 指定查找用户类文件的地位 -cp <门路> 指定查找用户类文件的地位 -bootclasspath <门路> 笼罩疏导类文件的地位接下来咱们定义一个简略的类 ...

December 1, 2020 · 1 min · jiezi

关于jvm:jstat查看JVM的GC情况

jstat能够查看堆各局部的使用量,以及类加载的数量jstat所有的参数> jstat -optionsjstat -class pid # 显示ClassLoad相干信息jstat -compiler pid # 显示JIT编译的相干信息jstat -gc pid # 显示和gc相干的堆信息jstat -gccapacity pid # 显示各个代的容量以及应用状况jstat -gccause pid # 显示垃圾回收相干信息,同时显示最初一次或正在产生GC的起因jstat -gcmetacapacity pid # 显示`metaspace`的大小jstat -gcnew pid # 显示新生代信息jstat -gcnewcapacity pid # 显示新生代大小和应用状况jstat -gcold pid # 显示老年代和永恒代的信息jstat -gcoldcapacity pid # 显示老年代大小jstat -gcutil pid # 显示垃圾收集信息jstat -printcompilation pid # 输入JIT编译的办法信息jstat -class pid显示已加载class的数量,和空间占用状况> jstat -class 18378Loaded Bytes Unloaded Bytes Time 23192 42406.6 4121 5982.8 12.65Loaded 已装载类的数量Bytes 已装载类占用的大小Unloaded 曾经卸载类的数量Bytes 已卸载类占用的大小Time 装载类和卸载类的工夫jstat -compiler pid显示JVM实时编译(JIT)信息> jstat -compiler 18378Compiled Failed Invalid Time FailedType FailedMethod 29393 2 0 133.63 1 sun/security/util/math/intpoly/IntegerPolynomialP521 carryReduceCompiled:编译数量。Failed:失败数量Invalid:不可用数量Time:工夫FailedType:失败类型FailedMethod:失败的办法jstat -gc pid显示GC相干信息jstat -gc 18378 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 19968.0 14336.0 0.0 14268.4 1319936.0 672172.2 344064.0 158500.9 145024.0 110813.5 20608.0 12871.0 247 3.224 5 0.794 4.018S0C:年老代中第一个survivor(幸存区)的容量 (字节)S1C:年老代中第二个survivor(幸存区)的容量 (字节)S0U :年老代中第一个survivor(幸存区)目前已应用空间 (字节)S1U :年老代中第二个survivor(幸存区)目前已应用空间 (字节)EC :年老代中Eden(伊甸园)的容量 (字节)EU :年老代中Eden(伊甸园)目前已应用空间 (字节)OC :Old代的容量 (字节)OU :Old代目前已应用空间 (字节)MC:metaspace(元空间)的容量 (字节)MU:metaspace(元空间)目前已应用空间 (字节)YGC :从应用程序启动到采样时年老代中gc次数YGCT :从应用程序启动到采样时年老代中gc所用工夫(s)FGC :从应用程序启动到采样时old代(全gc)gc次数FGCT :从应用程序启动到采样时old代(全gc)gc所用工夫(s)GCT:从应用程序启动到采样时gc用的总工夫(s)jstat -gccapacity pid展现JVM三代空间大小> jstat -gccapacity 18378 NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC 84992.0 1355776.0 1355776.0 19968.0 14336.0 1319936.0 171008.0 2711552.0 344064.0 344064.0 0.0 1173504.0 145024.0 0.0 1048576.0 20608.0 247 5NGCMN :年老代(young)中初始化(最小)的大小(字节)NGCMX :年老代(young)的最大容量 (字节)NGC :年老代(young)中以后的容量 (字节)S0C :年老代中第一个survivor(幸存区)的容量 (字节)S1C : 年老代中第二个survivor(幸存区)的容量 (字节)EC :年老代中Eden(伊甸园)的容量 (字节)OGCMN :old代中初始化(最小)的大小 (字节)OGCMX :old代的最大容量(字节)OGC:old代以后新生成的容量 (字节)OC :Old代的容量 (字节)MCMN:metaspace(元空间)中初始化(最小)的大小 (字节)MCMX :metaspace(元空间)的最大容量 (字节)MC :metaspace(元空间)以后新生成的容量 (字节)CCSMN:最小压缩类空间大小CCSMX:最大压缩类空间大小CCSC:以后压缩类空间大小YGC :从应用程序启动到采样时年老代中gc次数FGC:从应用程序启动到采样时old代(全gc)gc次数jstat -gcmetacapacity pid> jstat -gcmetacapacity 18378 MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC FGCT GCT 0.0 1173504.0 145024.0 0.0 1048576.0 20608.0 248 5 0.794 4.036MCMN:最小元数据容量MCMX:最大元数据容量MC:以后元数据空间大小CCSMN:最小压缩类空间大小CCSMX:最大压缩类空间大小CCSC:以后压缩类空间大小YGC :从应用程序启动到采样时年老代中gc次数FGC :从应用程序启动到采样时old代(全gc)gc次数FGCT :从应用程序启动到采样时old代(全gc)gc所用工夫(s)GCT:从应用程序启动到采样时gc用的总工夫(s)jstat -gcnew pid年老代对象信息> jstat -gcnew 18378 S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT 19968.0 24576.0 19940.4 0.0 8 15 24576.0 1306624.0 155059.4 248 3.242S0C :年老代中第一个survivor(幸存区)的容量 (字节)S1C :年老代中第二个survivor(幸存区)的容量 (字节)S0U :年老代中第一个survivor(幸存区)目前已应用空间 (字节)S1U :年老代中第二个survivor(幸存区)目前已应用空间 (字节)TT:持有次数限度MTT:最大持有次数限度DSS:冀望的幸存区大小EC:年老代中Eden(伊甸园)的容量 (字节)EU :年老代中Eden(伊甸园)目前已应用空间 (字节)YGC :从应用程序启动到采样时年老代中gc次数YGCT:从应用程序启动到采样时年老代中gc所用工夫(s)jstat -gcnewcapacity pid年老代对象的信息和占用量> jstat -gcnewcapacity 18378 NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC YGC FGC 84992.0 1355776.0 1355776.0 451584.0 19968.0 451584.0 24576.0 1354752.0 1306624.0 248 5NGCMN :年老代(young)中初始化(最小)的大小(字节)NGCMX :年老代(young)的最大容量 (字节)NGC :年老代(young)中以后的容量 (字节)S0CMX :年老代中第一个survivor(幸存区)的最大容量 (字节)S0C :年老代中第一个survivor(幸存区)的容量 (字节)S1CMX :年老代中第二个survivor(幸存区)的最大容量 (字节)S1C:年老代中第二个survivor(幸存区)的容量 (字节)ECMX:年老代中Eden(伊甸园)的最大容量 (字节)EC:年老代中Eden(伊甸园)的容量 (字节)YGC:从应用程序启动到采样时年老代中gc次数FGC:从应用程序启动到采样时old代(全gc)gc次数jstat -gcold pidold代对象信息> jstat -gcold 18378 MC MU CCSC CCSU OC OU YGC FGC FGCT GCT 145024.0 111038.1 20608.0 12953.0 344064.0 162323.8 248 5 0.794 4.036MC :metaspace(元空间)的容量 (字节)MU:metaspace(元空间)目前已应用空间 (字节)CCSC:压缩类空间大小CCSU:压缩类空间应用大小OC:Old代的容量 (字节)OU:Old代目前已应用空间 (字节)YGC:从应用程序启动到采样时年老代中gc次数FGC:从应用程序启动到采样时old代(全gc)gc次数FGCT:从应用程序启动到采样时old代(全gc)gc所用工夫(s)GCT:从应用程序启动到采样时gc用的总工夫(s)jstat -gcoldcapacity pidold代对象信息及其占用量> jstat -gcoldcapacity 18378 OGCMN OGCMX OGC OC YGC FGC FGCT GCT 171008.0 2711552.0 344064.0 344064.0 248 5 0.794 4.036OGCMN :old代中初始化(最小)的大小 (字节)OGCMX :old代的最大容量(字节)OGC :old代以后新生成的容量 (字节)OC :Old代的容量 (字节)YGC :从应用程序启动到采样时年老代中gc次数FGC :从应用程序启动到采样时old代(全gc)gc次数FGCT :从应用程序启动到采样时old代(全gc)gc所用工夫(s)GCT:从应用程序启动到采样时gc用的总工夫(s)jstat -gcutil pid统计GC信息> jstat -gcutil 18378 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 99.86 0.00 49.82 47.18 76.57 62.85 248 3.242 5 0.794 4.036S0 :年老代中第一个survivor(幸存区)已应用的占以后容量百分比S1 :年老代中第二个survivor(幸存区)已应用的占以后容量百分比E :年老代中Eden(伊甸园)已应用的占以后容量百分比O :old代已应用的占以后容量百分比P :perm代已应用的占以后容量百分比YGC :从应用程序启动到采样时年老代中gc次数YGCT :从应用程序启动到采样时年老代中gc所用工夫(s)FGC :从应用程序启动到采样时old代(全gc)gc次数FGCT :从应用程序启动到采样时old代(全gc)gc所用工夫(s)GCT:从应用程序启动到采样时gc用的总工夫(s)jstat -gccause pid显示垃圾回收的相干信息(通-gcutil),同时显示最初一次或以后正在产生的垃圾回收的诱因。> jstat -gccause 18378 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC 99.86 0.00 50.26 47.18 76.57 62.85 248 3.242 5 0.794 4.036 Allocation Failure No GCLGCC:最初一次GC起因GCC:以后GC起因(No GC 为以后没有执行GC)jstat -printcompilation pid以后JVM执行信息> jstat -printcompilation 18378Compiled Size Type Method 29469 44 1 hudson/util/LogTaskListener$LogOutputStream <init>Compiled :编译工作的数目Size :办法生成的字节码的大小Type:编译类型Method:类名和办法名用来标识编译的办法。类名应用/做为一个命名空间分隔符。办法名是给定类中的办法。上述格局是由-XX:+PrintComplation选项进行设置的 ...

November 30, 2020 · 3 min · jiezi

关于jvm:查看JVM运行参数jinfo命令

jinfo 是 JDK 自带的命令,能够用来查看正在运行的 java 应用程序的扩大参数,包含Java System属性和JVM命令行参数;也能够动静的批改正在运行的 JVM 一些参数。当零碎解体时,jinfo能够从core文件外面晓得解体的Java应用程序的配置信息查看jvm所有参数默认值> java -XX:+PrintFlagsInitial -version查看jvm所有的参数> java -XX:+PrintFlagsFinal -version查看以后JVM运行的参数用法: > jinfo -flags pid查看零碎所有的Java过程 > jcmd24386 sun.tools.jcmd.JCmd18378 plan.war查看plan.war过程的运行参数 > jinfo -flags 18378Attaching to process ID 18378, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.261-b12Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=262144000 -XX:MaxHeapSize=4164943872 -XX:MaxNewSize=1388314624 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=87031808 -XX:OldSize=175112192 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC Command line: 查看具体某一个参数的值 > jinfo -flag InitialHeapSize 18378开启/敞开某个JVM参数应用 jinfo 能够在不重启虚拟机的状况下,能够动静的批改 jvm 的参数。尤其在线上的环境特地有用。形容:开启或者敞开对应名称的参数,次要是针对 boolean 值的参数设置的> jinfo -flag [+|-]name pid> jinfo -flag PrintGC 18378-XX:-PrintGC> jinfo -flag +PrintGC 18378> jinfo -flag PrintGC 18378-XX:+PrintGC> jinfo -flag -PrintGC 18378> jinfo -flag PrintGC 18378-XX:-PrintGC批改某个JVM过程的值jinfo尽管能够在java程序运行时动静地批改虚拟机参数,但并不是所有的参数都反对动静批改> jinfo -flag name=value pid输入以后JVM过程所有的零碎属性> jinfo -sysprops pid> jinfo -sysprops 18378Attaching to process ID 18378, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.261-b12java.runtime.name = Java(TM) SE Runtime Environmentjna.platform.library.path = /usr/lib64:/lib64:/usr/lib:/lib:/usr/lib64/mysqljava.vm.version = 25.261-b12sun.boot.library.path = /usr/local/jdk1.8/jre/lib/amd64mail.smtp.sendpartial = truejava.vendor.url = http://java.oracle.com/java.vm.vendor = Oracle Corporationpath.separator = :guice.disable.misplaced.annotation.check = truefile.encoding.pkg = sun.iojava.vm.name = Java HotSpot(TM) 64-Bit Server VMjna.loaded = truesun.os.patch.level = unknownsun.java.launcher = SUN_STANDARDuser.country = USuser.dir = /rootjava.vm.specification.name = Java Virtual Machine Specificationjava.runtime.version = 1.8.0_261-b12java.awt.graphicsenv = sun.awt.X11GraphicsEnvironmentos.arch = amd64java.endorsed.dirs = /usr/local/jdk1.8/jre/lib/endorsedline.separator = java.io.tmpdir = /tmpjava.vm.specification.vendor = Oracle Corporationos.name = Linuxmail.smtps.sendpartial = truesun.jnu.encoding = UTF-8jnidispatch.path = /root/.cache/JNA/temp/jna4559128793649671554.tmpjetty.git.hash = de97d26f7bd222a0e16831e353d702a7a422f711java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/libjava.specification.name = Java Platform API Specificationjava.class.version = 52.0sun.management.compiler = HotSpot 64-Bit Tiered Compilersos.version = 3.10.0-957.el7.x86_64user.home = /rootuser.timezone = Asia/Shanghaijava.awt.printerjob = sun.print.PSPrinterJobfile.encoding = UTF-8java.specification.version = 1.8user.name = rootjava.class.path = jenkins.warjava.vm.specification.version = 1.8sun.arch.data.model = 64sun.java.command = jenkins.warjava.home = /usr/local/jdk1.8/jreuser.language = enjava.specification.vendor = Oracle Corporationawt.toolkit = sun.awt.X11.XToolkitjava.vm.info = mixed modejava.version = 1.8.0_261java.ext.dirs = /usr/local/jdk1.8/jre/lib/ext:/usr/java/packages/lib/extsun.boot.class.path = /usr/local/jdk1.8/jre/lib/resources.jar:/usr/local/jdk1.8/jre/lib/rt.jar:/usr/local/jdk1.8/jre/lib/sunrsasign.jar:/usr/local/jdk1.8/jre/lib/jsse.jar:/usr/local/jdk1.8/jre/lib/jce.jar:/usr/local/jdk1.8/jre/lib/charsets.jar:/usr/local/jdk1.8/jre/lib/jfr.jar:/usr/local/jdk1.8/jre/classesjava.awt.headless = truejava.vendor = Oracle Corporationfile.separator = /java.vendor.url.bug = http://bugreport.sun.com/bugreport/sun.io.unicode.encoding = UnicodeLittlesun.font.fontmanager = sun.awt.X11FontManagersun.cpu.endian = littleexecutable-war = /root/jenkins.warsun.cpu.isalist = ...

November 29, 2020 · 2 min · jiezi

关于jvm:JVM-ZGC介绍

什么是ZGCZGC收集器(Z Garbage Collector)由Oracle公司研发.2018年提交了JEP 333将ZGC提交给了OpenJDK,推动进入OpenJDK11的公布清单中。ZGC收集器是基于Region内存布局,临时不设分代,应用读屏障,着色指针和内存多重映射等技术来实现并发的标记整顿算法,以低提早为指标的一款收集器。指标在对吞吐量影响不大的状况下,对任意大小堆收集进展工夫都管制在10ms以内的低提早。ZGC堆内存布局与G1一样,ZGC也采纳基于Region的堆内存布局ZGC的Region具备动态性动静的创立和销毁动静的Region容量大小 大小分类: 小型Region(Small Region),固定大小2MB,寄存小于256KB的小对象中型Region(Medium Region),固定大小32MB,寄存大于256KB小于4MB的对象大型Region(Large Region),大小不固定,能够动态变化,但必须是2MB的整数倍,用于放大于4MB的大对象,每个大型Region只会放一个大对象,所以理论容量可能会小于中型Region,最小到4MB。大型Region在ZGC实现中不会被重调配,因为复制一个大对象代价太高。着色指针着色指针是一种间接将大量额定的信息存储在指针上的技术。目前在Linux下64位的操作系统中高18位是不能用来寻址的,然而残余的46为却能够反对64T的空间,到目前为止咱们简直还用不到这么多内存。于是ZGC将46位中的高4位取出,用来存储4个标记位,残余的42位能够反对4TB(2的42次幂)的内存,也间接导致ZGC能够治理的内存不超过4TB,如图所示: Marked0/marked1: 判断对象是否已标记Remapped: 判断利用是否已指向新的地址Finalizable: 判断对象是否只能被Finalizer拜访这几个bits在不同的状态也就代表这个援用的不同色彩对象标记过程就是打个三色标记,这些标记实质上只和对象援用无关,和对象自身无关。某个对象只有它的援用关系能力决定它的存活。ZGC应用了内存多重映射(Multi-Mapping)将多个不同的虚拟内存地址映射到同一个物理内存地址上,这是一种多对一映射。因为染色指针只是从新定义内存中某些指针的其中几位,OS又不反对,OS只会把整个指针当做一个内存地址来看待,只是它本人瞎想,为了解决这个问题,应用了古代处理器的虚拟内存映射技术 读屏障比方在 ZGC 中,会对加载的援用进行测试,查看是否设置了某些位(查看着色指针,是“bad color”还是“good color”),如果是“bad color”,要走“slow path”,并执行特定的操作(比方mark、relocate、remap 等操作),将“bad color ”转变为“good color”,这样一来,下次load 时就能够走“fast path”了。ZGC回收流程初始标记(STW) 进行用户线程,标记GC Root对象. 1 , 2, 4被标记为存活对象。并发标记 并发递归从GC Root开始遍历可达对象。5,8被标记为存活对象挪动对象 比照发现3,6,7是过期的对象,两头灰色的Region须要被清理压缩,所以将4,5,8挪动到左边空的Region,挪动过程中有个forward table记录这种转变。修改指针 因为4,5,8产生了挪动,所以须要修改.平台反对状况PlatformSupportedSinceCommentLinux/x64okJDK 11 Linux/AArch64okJDK 13 macOSokJDK 14 WindowsokJDK 14Requires Windows version 1803 \(Windows 10 or Windows Server 2019\) or later\.

November 27, 2020 · 1 min · jiezi

关于jvm:JVM调优简单工具使用

前言JVM调优是咱们在我的项目上常常应用的工具,个别用到jps跟jmap;jps是jdk提供的一个查看以后java过程的小工具, 能够看做是JavaVirtual Machine Process Status Tool的缩写。非常简单实用。 命令格局:jps [options] [hostid]内容1、jpsjps是jdk提供的一个查看以后java过程的小工具, 能够看做是JavaVirtual Machine Process Status Tool的缩写。非常简单实用。 命令格局:jps [options] [hostid][options]选项 : -q:仅输入VM标识符,不包含classname,jar name,arguments in main method -m:输入main method的参数 -l:输入齐全的包名,利用主类名,jar的齐全路径名 -v:输入jvm参数 -V:输入通过flag文件传递到JVM中的参数(.hotspotrc文件或-XX:Flags=所指定的文件 -Joption:传递参数到vm,例如:-J-Xms512m [hostid]:[protocol:][[//]hostname][:port][/servername] 命令的输入格局 :lvmid [ [ classname| JARfilename | "Unknown"] [ arg* ] [ jvmarg* ] ]1.1 jpsjps 1.2 jps -ljps –l:输入主类或者jar的齐全路径名 1.3 jps -vjps –v :输入jvm参数 1.4 jps -qjps –q :仅仅显示java过程号 1.5 jps -mlv ...

November 27, 2020 · 1 min · jiezi

关于jvm:G1垃圾收集器

G1垃圾收集器简介 Garbage First(简称:G1)收集器是垃圾收集器技术倒退历史上的一个里程碑,它创始了收集器面向部分收集的设计思路和基于Region的内存布局模式.G1是一款次要面向服务端利用的垃圾收集器,HotSpot开发团队赋予它的冀望是将来能够替换掉JDK5中公布的CMS收集器. JDK9公布之日,G1发表取代了Parallel Scavenge加Parallel Old的组合,成为服务端模式下默认的垃圾收集器,而CMS则被申明为(Deprecate)应用的收集器. G1实现了可控进展工夫的垃圾收集器,通过-XX:MaxGCPauseMillis参数进行设置,默认是200ms。 Region简介G1创始了基于Region的堆内存布局,尽管G1也遵循分代思维,然而堆内存布局和其它垃圾收集的内存布局有着微小的变动. G1垃圾收集器不再保持固定大小分代分区,而是把间断的堆内存分成大小一样的独立区域(Region),每一个Region能够依据须要表演新生代(Eden),Survivor空间,老年代。 G1垃圾收集器能够依据表演不同角色的Region采纳不同的策略去解决,这样无论是新创建的对象,还是曾经存活了一段时间的对象, 或则经验过很屡次垃圾收集然而还存活下来的对象,都会有很好的成果。 G1中五种不同的Region新生代(Eden Region)年老代(Survivor Region)老年代(Old Region)巨型对象(Humongous Region)未调配(Free Region)巨型对象区(Humongous Region)Region中有一种非凡的Humongous Region,专门用来存储大对象。G1收集器规定只有对象的大小超过了Region大小的个别就会被认为是巨型对象 。每个Region的大小能够通过-XX:G1HeapRegionSize来调整(1MB-32MB,且为2的N次幂)。G1收集器通常把Humongous Region看做老年代的一部分。对象划分的规定对象大小小于一半Region,间接存储到标记为Eden的Region对象大小大于一半Region然而小于一个Region,存储到标记为Humongous的Region中对象大小超过一个Region大小,存储到标记为Humongous的多个间断Region中GC类型youngGC:回收Eden区和Survivor区MixedGC:回收所有的新生代和局部老年区FullGC:回收整个堆Remenbered Set,简称RSet因为分代的内存不间断,导致GC搜寻垃圾对象时,须要扫描整个堆。为了解决这个问题,G1为每个Region都保护了一个Remenbered Set,用来记录对象的援用状况,当GC产生的时候依据Remenbered Set的索引状况去搜寻。Remenbered Set存储的援用关系类型1.分区内援用2.新生代分区Y1援用新生代分区Y23.新生代分区Y1援用老年代分区O14.老年代分区O1援用新生代分区Y15.老年代分区O1援用老年代分区O2youngGC时,GC Root次要是两类,栈空间和老年代到新生代辨别的援用(2,3)关系。MixedGC时,因为只收集老年代区,所以老年代分区援用(4,5)关系将被应用。 G1垃圾收集器执行步骤初始标记标记GC Roots可能间接关联的对象(Root Region),批改TAMS指针的值,使的下一阶段用户线程并发运行时,能在正确的Region中调配对象。须要进展用户线程,但耗时很短,且借助Minor GC时同步实现。 TAMS:Top at Mark Start,Region 中的指针,用于并发标记时为对象分配内存空间。根分区扫描拿到初始标记的Root Region,扫描整个堆的所有Region的Rset看是否有Root Region,并标记Region并发标记遍历上一步标记过的Region对堆中对象进行可达性剖析,递归扫描整个堆里的对象图,找到要回收的对象。耗时较长,能够和用户线程并行。耗时较长,可遇用户线程并行,当对象扫描实现当前,还要重新处理SATB记录下的在并发时有援用变动的对象。 从新标记短暂的暂停用户线程,用于解决并发标记阶段遗留下来最初大量的SATB记录。筛选回收负责更新Region的统计数据,对各个Region的回收价值进行排序,依据用户所期待的进展工夫,来制订回收打算,能够自由选择任意组合的Region进行回收,而后将须要回收的Region中的对象复制到空的Region,再清理掉整个旧的Region.这里波及挪动存活对象,所以也要暂停用户线程,由多条收集器线程并行实现。G1提高效率的有哪些点应用Rset升高了扫描范畴从新标记阶段应用SATB速度比CMS的增量更快清理过程中,抉择局部回收价值高的Region进行清理(MixedGC),而不是所有的Region进步了清理效率。

November 25, 2020 · 1 min · jiezi

关于jvm:Java虚拟机怎么确定对象已经死了

怎么确定对象曾经死了? 怎么确定对象曾经死了?怎么确定一个对象曾经死了? 援用计数算法给对象中增加一个援用计数器,每当有个中央援用它,计数器值就加1,援用生效,计数器减1,任何时刻计数器为0的对象就不能再利用了。 很难解决对象之间的互相循环援用。 援用计数收集器能够很快的执行,并且交错在程序运行中,对程序须要不被长时间打断的实时环境比拟无利,但其很难解决对象之间互相循环援用的问题。如上面的程序和示意图所示,对象objA和objB之间的援用计数永远不可能为 0,那么这两个对象就永远不能被回收。 public class ReferenceCountingGC { public Object instance = null; public static void testGC(){ ReferenceCountingGC objA = new ReferenceCountingGC (); ReferenceCountingGC objB = new ReferenceCountingGC (); // 对象之间互相循环援用,对象objA和objB之间的援用计数永远不可能为 0 objB.instance = objA; objA.instance = objB; objA = null; objB = null; System.gc(); }上述代码最初面两句将objA和objB赋值为null,也就是说objA和objB指向的对象曾经不可能再被拜访,然而因为它们相互援用对方,导致它们的援用计数器都不为 0,那么垃圾收集器就永远不会回收它们。 可达性剖析算法通过一系列“GC roots”的对象作为起始点,从这些节点开始向下搜寻,搜寻所走过的门路成为援用链(refenecre chain) ,当一个对象到GcRoot 没有任何的援用链,则证实此对象不可用。 Gcroot对象包含: 虚拟机栈(栈帧中的本地变量表) 中援用的对象。办法区中类动态属性援用的对象办法区中常量援用的对象本地办法栈中JNI(native办法)援用的对象。 生存还是死亡即时当一个对象不可达到,也并非非死不可,这时它们处于一个疾驶的阶段要真正判处一个对象死亡,至多要经验2次标记。第一次标记为不可达到。当对象没有笼罩finalize() 办法,或者finalize办法曾经被虚拟机调用了,虚拟机都被视为没必要执行(没必要执行是不是间接回收?) 如果一个对象被断定有必要执行,则将这个对象放在一个F-Queue的队列中,并由一个线程去执行它。finalize办法是对象逃脱死亡命运的最初机会。当在finalize办法中从新将之间赋值给了某个变量,那么第二次标记就会被移除。如果对象第二次还没有逃脱,那么根本就被回收了。 public class FinalizeEscapeGc { private static FinalizeEscapeGc instance = null; public void alive() { System.out.println("i'm alive"); } /** * 该办法只调用一次 * @throws Throwable */ @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("finalize()"); FinalizeEscapeGc.instance = this; } public static void main(String[] args) throws InterruptedException { instance = new FinalizeEscapeGc(); instance = null; System.gc(); Thread.sleep(500);// finalize() method has a low priority to excute if (instance != null) { instance.alive(); } else { System.out.println("ooops,i'm dead"); } instance = null; System.gc(); Thread.sleep(500); if (instance != null) { instance.alive(); } else { System.out.println("ooops,i'm dead"); } }}输入: ...

November 25, 2020 · 1 min · jiezi

关于jvm:一文图解JVM面试的全部考点

前言本文将通过简介Class字节码文件,Class类加载,类加载完后的JVM运行时数据区,运行中的GC垃圾回收等相干常识带大家理解在面试时JVM常问的考点。注:本文为 转载文章,原文作者:ElasticForce , 原文地址:JVM面试常考点全在这:多图看懂Java虚拟机 。 本文主线:①、Class字节码文件 ②、Class类加载 ③、JVM运行时数据区 ④、GC 垃圾回收 ⑤、常见的垃圾回收器 Class字节码文件class文件也叫字节码文件,由Java源代码(.java文件)编译而成的字节码(.class文件)。Class文件格式: 无符号数:专用的数据类型,以u1、u2、u4、u8来代表1个字节、2个字节、4个字节、8个字节的无符号数。表:通常以“_info" 结尾,由多个无符号数和其它表形成的复合数据类型。 魔数:标识这个文件是否是一个能被虛拟机所承受的class文件,值是固定的:0xCAFEBABE 副版本号和主版本号:判断是否是以后虚拟机所反对的版本 常量池:能够看作是class文件的资源仓库,蕴含class文件构造及其子结构中援用的所有字符串常量、类或接口名、字段名和其余常量。 Class类加载类的生命周期 加载:通过类的 全限定类名 查找并加载类的二进制字节流连贯:目标是将加载的二进制字节流的动态存储构造转化为办法区的运行时数据结构 验证:确保被加载的class文件中的字节流,符合规范,保障不会对虚拟机造成危害,如类文件构造查看、元数据验证、字节码验证、符号援用验证筹备:为类的动态变量分配内存,并初始化(默认值)解析:把常量池中的符号援用转换成间接援用,让类可能间接援用到想要依赖的指标初始化:执行类结构器< clinit >办法(类结构器不是实例结构器),为类的动态变量赋初始值应用:分为被动应用和被动应用,JVM只会在每个类或接口首次被动应用时才初始化该类 被动应用: ①、创立类实例 ②、拜访类或接口的动态变量、调用静态方法 ③、反射调用某个类 ④、初始化某个类的子类,也会初始化该类 ⑤、实现的接口蕴含默认办法,该接口会被初始化 被动应用:①、通过子类拜访父类的动态变量,不会初始化子类 ②、拜访类中的常量,不会初始化该类 类加载器: 启动类加载器:负责将<JAVA_HOME>/lib,或者-Xbootclasspath参数指定的门路中的,必须是JVM辨认的类库加载进内存(依照名字辨认例如rt.jar,不装载不能辨认的文件)扩大类加载器:负责加载<JRE_HOME> /lib/ext,或者java.ext.dirs零碎变量所指定门路中的所有类库应用程序类加载器:负责加载classpath门路中的所有类库双亲委派模型:JVM中的ClassLoader除了启动类加载器外,其余的类加载器都应该有本人的父级加载器(如上图所示)。双亲委派模型的工作流程:一个类加载器接管到类加载申请后,本人不去尝试加载这个类,而是先委派给父类加载器,层层委派,始终到启动类加载器如果父级加载器不能实现加载申请,比方在它的搜寻门路下找不到这个类(ClassNotFoundException),就反馈给子类加载器,让子类加载器进行加载双亲委派模型的作用:保障了Java程序的稳固运作,使得一些公共类只会被加载一次,且避免了外围类库被用户自定义的同名类所篡改JVM运行时数据区留神:下图为 JDK1.7 版本: 在JDK1.8中,曾经将办法区移除了,应用 元空间 进行代替,并且元空间也不是位于JVM的运行时的数据区中了,而是位于 本地内存 中,本地内存指的是什么呢? 先来看一张大图,就明确本地内存是什么了!留神:下图为 JDK1.7 版本的: 先对上图进行形容下 : 当要启动一个Java程序的时候,会创立一个相应的过程; 每个过程都有本人的虚拟地址空间,JVM 用到的内存(包含堆、栈和办法区)就是从过程的虚拟地址空间上调配的。请留神的是,JVM 内存只是过程空间的一部分,除此之外过程空间内还有代码段、数据段、内存映射区、内核空间等。 在 JVM 的角度看,JVM 内存之外的局部叫作本地内存。 而后在来看 JDK1.8 版本的一张大图,应该霎时就会明确了: 程序计数器:程序计数器也叫PC寄存器(Program Counter),是线程公有的,即每个线程都有一个程序计数器,在线程创立时会创立对应的程序计数器,用来存储指向下一条指令的地址。 程序计数器占用很小的内存空间,JVM标准中没有规定这块内存会OOM。 虚拟机栈:虚拟机中每个线程都有本人独有的虚拟机栈,该栈和线程同时创立,用于存储栈帧(Frame),每个办法被调用时会生成一个栈帧压栈,栈帧中存储了局部变量表、操作数栈、动静链接、办法返回地址。 栈帧是用来存储数据和局部过程后果的数据结构,同时也用来解决动静链接、办法返回值和异样分派。 栈帧随着办法调用而创立,随着办法完结而销毁。 每个栈帧都有本人的:局部变量表、操作数栈和指向以后办法所属的类的运行时常量池的援用。 正在执行的栈帧称为以后栈帧,对应的办法则是以后办法,定义了该办法的类就是以后类 。 ...

November 23, 2020 · 2 min · jiezi

关于jvm:JVM肝了一周吐血整理出这份超硬核的JVM笔记升级版

写在后面最近,始终有小伙伴让我整顿下对于JVM的常识,通过十几天的收集与整顿,初版算是整理出来了。心愿对大家有所帮忙。也能够加作者冰河的微信:sun_shine_lyz进行交换。JDK 是什么?JDK 是用于反对 Java 程序开发的最小环境。 Java 程序设计语言Java 虚拟机Java API类库JRE 是什么?JRE 是反对 Java 程序运行的规范环境。 Java SE API 子集Java 虚拟机Java历史版本的个性?Java Version SE 5.0引入泛型;加强循环,能够应用迭代形式;主动装箱与主动拆箱;类型平安的枚举;可变参数;动态引入;元数据(注解);引入Instrumentation。Java Version SE 6反对脚本语言;引入JDBC 4.0 API;引入Java Compiler API;可插拔注解;减少对Native PKI(Public Key Infrastructure)、Java GSS(Generic Security Service)、Kerberos和LDAP(Lightweight Directory Access Protocol)的反对;继承Web Services;做了很多优化。Java Version SE 7switch语句块中容许以字符串作为分支条件;在创立泛型对象时利用类型推断;在一个语句块中捕捉多种异样;反对动静语言;反对try-with-resources;引入Java NIO.2开发包;数值类型能够用2进制字符串示意,并且能够在字符串示意中增加下划线;钻石型语法;null值的主动解决。Java 8函数式接口Lambda表达式Stream API接口的加强工夫日期加强API反复注解与类型注解默认办法与静态方法Optional 容器类运行时数据区域包含哪些?程序计数器Java 虚拟机栈本地办法栈Java 堆办法区运行时常量池间接内存程序计数器(线程公有)程序计数器(Program Counter Register)是一块较小的内存空间,能够看作是以后线程所执行字节码的行号指示器。分支、循环、跳转、异样解决、线程复原等根底性能都须要依赖这个计数器实现。 因为 Java 虚拟机的多线程是通过线程轮流切换并调配处理器执行工夫的形式实现的。为了线程切换后能复原到正确的执行地位,每条线程都须要一个独立的程序计数器,各线程之间的计数器互不影响,独立存储。 如果线程正在执行的是一个 Java 办法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 办法,这个计数器的值为空。程序计数器是惟一一个没有规定任何 OutOfMemoryError 的区域。 Java 虚拟机栈(线程公有)Java 虚拟机栈(Java Virtual Machine Stacks)是线程公有的,生命周期与线程雷同。 虚拟机栈形容的是 Java 办法执行的内存模型:每个办法被执行的时候都会创立一个栈帧(Stack Frame),存储 ...

November 22, 2020 · 2 min · jiezi

关于jvm:给你们想要的内存溢出MAT排查工具

关注“Java后端技术全栈” 回复“000”获取大量电子书 本文总结了排查内存溢出问题的MAT工具,先来看看本文目录: Java 堆内存剖析工具。 1@RestController 2public class MatController { 3 4    List<User> list = new ArrayList<>(); 5 6    @GetMapping("/mat") 7    public String mat() { 8        for (int i = 0; i < 10000000; i++) { 9            User user = new User();10            user.setId(1L);11            user.setUserName("老田" + i);12            list.add(user);13        }14        return "ok";15    }16}设置JVM启动参数 1-Xms20m -Xmx20m -XX:MaxPermSize=20m -XX:+HeapDumpOnOutOfMemoryError拜访http://localhost:8080/mat 不过一会就报异样了。而后 与之同时,生成了一个文件 实战MAT下载地址: http://www.eclipse.org/mat/do... 下载到本地并解压 双击MemoryAnalyzer.exe关上 咱们把后面生成的java_pid4928.hprof文件导入进来 点击finish 我的天啦,立即就把咱们问题定位到了,MatController这个类的嫌疑最大(其实线上环境个别都不会这么容易)。 点击图中的最大区域 outgoing references 对象的引出incoming references  对象的引入另外Path to GC Roots这是疾速剖析的一个罕用性能,显示和 GC Roots 之间的门路。 树状图左上角 有个图标,树状图 进入树状图 一眼就晓得咱们的MatController有问题,竟然排在最后面了。另外两个要害属性: Shallow Heap:浅堆,示意对象自身的内存占用。Retained Heap:深堆,示意对象本身的内存占用而且在MatController类上还有个Regex,反对正则表达式,于是我就输出了我特有的命名tian,按下回车键: 而后,便把tian相干的全副输入。 同理,如果咱们在排查线上问题是,也能够输出咱们特有关键字等来疾速定位问题。每个公司基本上都有本人一套独立的包目录。咱们就能够应用那个特有的关键字进行搜寻。 点开MatController类,上面有个List,而后List存了User对象,这不就是咱们下面的代码么? 柱状图同样在左上角有个柱状图 ...

November 21, 2020 · 1 min · jiezi

关于jvm:JVM-GC-垃圾收集算法总结

jvm的垃圾收集算法总结起来有4种,严格来说应该是3种,上面一一具体介绍。因为垃圾收集算法波及程序细节,而且各个平台的虚拟机操作内存的办法不同,因而这里不过多介绍算法的实现,重视几种算法的思维及倒退过程。 1. 标记 - 革除算法最根底的算法“标记-革除”(Mark-Sweep)算法,算法分为“标记”和“革除”两个阶段:首先标记出所有要回收的对象,在标记实现后同一回收掉。之所以说它是最根底的收集算法,是因为后续的算法都是基于这种思路并对其有余进行改良失去的。次要有余: 一个是效率问题,标记和革除两个效率都不高。另一个是空间问题,标记革除后产生大量不间断的内存碎片,空间碎片太多可能会导致当前在程序运行中须要调配较大对象时,无奈找到足够的间断内存而不得不提前触发另一次GC。标记-革除算法执行过程如图所示。 从图中能够看出,有很多不间断的内存空间碎片。 2. 复制算法为了解决效率问题,一种称为“复制”的收集算法呈现了,它将可用的内存空间分为大小相等的两块,每次只应用其中一块。当一块内存用完了,就将还存活的对象都复制到另一块上,而后再把本人应用的空间一次清理掉。这样使的每次都对整个半区进行内存回收,内存调配是不必思考空间碎片问题,只有一动堆顶指针,按程序分配内存即可,实现简略,运行高效。有余:将内存放大为原来的一半,每次只有一半的内存空间可用,代价高了一点。复制算法的执行过程如下图: 当初的商业虚拟机都采纳这种算法回收新生代,IBM公司的专门钻研表明,新生代中的对象98%是“朝生夕死”的,所以不须要依照 1: 1 的比例来划分内存空间,而是将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次应用 Eden 和其中的一个Survivor。当回收时,将 Eden 和 Survivor 中还存活的对象一次性地复制到另一块 Survivor 空间上,最初清理掉 Eden 和方才用过的 Survivor 空间。HotSpot 虚拟机默认 Eden 和 Survivor 的大小比例是 8: 1 ,也就是每次新生代可用内存空间为整个新生代容量的 90%(80%+10%),只有10%的内存会被节约。当然,98%的对象可回收只是个别场景下的数据,咱们没有方法每次回收都只有不大于10%的对象存活,当 Survivor 空间不够用时,须要依赖其余内存(这里指老年代)进行调配担保(Handle Promotion)。调配担保的艰深讲法:比方咱们去银行借款,如果咱们信用很好,在98%的状况下能按时偿还,银行可能也会默认咱们能按时还款,只须要有个担保人能保障咱们不能按时还款,能够从他的账户扣钱,那银行就认为没危险了。内存调配担保也一样,如果另一块 Survivor 空间没有足够空间来寄存上次收集后还存活的对象,这些对象将间接通过调配担保机制进入老年代。 3. “标记-整顿”算法复制算法在对象存活率高的状况下要进行较多的复制操作,效率会变低。更要害的是,如果不想节约50%的空间,就须要有额定的内存空间调配担保,以应答被应用内存中所有对象都100%存活的极其状况,所以老年代个别不间接选用这种算法。依据老年代的特点,有人提出了另外一种“标记-整顿”(Mark-Compact)算法,标记过程与“标记-革除”算法一样,但后续步骤不是间接对可回收对象进行清理,而是让所有存活对象都向一端挪动,而后间接清理掉端边界以外的内存。回收过程如下图所示: 4. 分代收集算法以后商业虚拟机垃圾收集都采纳分代收集(Generational Collection)算法,这种算法没有什么新的思维,只是依据对象存活周期不同将内存划分为几块。个别是把Java堆分为新生代和老年代,这样能够依据各个年代的特点采纳最合适的算法。在新生代,每次垃圾收集时发现都有少量的对象死去,只有大量存活,那就选用复制算法,只须要付出大量的存活对象复制老本即可实现收集。而老年代中对象因为存活率高、没有额定的空间对它调配担保,就必须应用“标记-清理”或“标记-整顿”算法来进行回收♻️。 最初贴上一张 JVM 内存分区示意图,加深一下各个分区的印象,有助了解分代收集的思路。

November 19, 2020 · 1 min · jiezi

关于jvm:一文让你彻底明白JVM参数该怎么设置

前言在网上查了很多如何配置JVM参数的解说文章,然而生产环境里JVM参数的值到底配置为多少,却没能失去一个具体的标准;的确,生产环境受到各方面的影响,设置适合的JVM参数切实比拟艰难,然而本文将会给大家一个绝对正当的参数设置指标。本文主线①、JVM运行时数据区剖析 ②、JVM参数设置时的留神点 ③、简略的GC垃圾回收过程形容 ④、最终JVM参数配置指南 本文为转载文章,原作者:蓝山牧童, 原文地址:jdk1.8——jvm剖析与调优JVM运行时数据区剖析上面将次要剖析下 1.7、1.8 两个不同的JDK版本下的JVM运行时数据区。JDK1.7及以前JDK 1.7及以前,Java 类信息、常量池、动态变量都存储在 Perm(永恒代)里。类的元数据和动态变量在类加载的时候调配到 Perm,当类被卸载的时候垃圾收集器从 Perm 解决掉。 JDK1.8JDK 1.8 的对 JVM 架构的革新将类元数据放到 本地内存中 ,另外,将 常量池和动态变量 放到 Java 堆里。HotSopt VM 将会为类的元数据明确调配和开释本地内存。 在这种架构下,类元信息就冲破了原来 -XX:MaxPermSize 最大办法区大小参数的限度,所以PermSize的JVM配置参数也是有效的,当初能够应用更多的本地内存。 这样就从肯定水平上解决了在运行时应用反射、代理等操作生成的大量 类实例 的问题,从而极大的升高了触发Full GC的问题,以及升高了呈现 OutOfMemoryError: PermGen 办法区内存溢出的问题。 JDK1.7的JVM局部运行时数据区展现 干货内容:能够发现最显著的一个变动是元空间从 虚拟机转移到本地内存 ;默认状况下,元空间的大小仅受本地内存的限度。这意味着当前简直不再会因为永恒代空间不够而抛出OOM异样了。 jdk1.8以前版本的class和jar包数据存储在permGen上面 ,permGen大小是固定的,而且我的项目之间无奈共用私有的class,所以很容易碰到OOM异样。 改成metaSpaces后,各个我的项目会共享同样的class内存空间,比方多个我的项目都援用了apache-common包,在metaSpaces中只会存储一份apache-common的class,进步了内存的利用率,垃圾回收更有效率。 JVM参数设置的留神点1、在jdk1.7及以前,生产环境个别有如下配置:-XX:PermSize=512M -XX:MaxPermSize=1024M示意在JVM里存储Java类信息,常量池和动态变量的 永恒代(办法区) 区域初始大小为512M,最大为1024M。在我的项目启动后,这个值是固定的,如果我的项目 class(类实例) 过多,很可能会导致OutOfMemoryError: PermGen异样。 降级JDK1.8之后,下面的perm配置曾经变成:-XX:MetaspaceSize=512M XX:MaxMetaspaceSize=1024M元空间MetaspaceSize如果不做配置,通过jinfo查看默认MetaspaceSize大小(约21M),MaxMetaspaceSize最大元空间则很大很大,后面说过MetaSpace只受本地内存大小限度 jinfo -flag MetaspaceSize 1234 #后果为:-XX:MetaspaceSize=21807104jinfo -flag MaxMetaspaceSize 1234 #后果为:-XX:MaxMetaspaceSize=18446744073709547520干货内容:MetaspaceSize触发FullGC的阈值,默认约为21M,如做了配置,最小阈值为自定义配置大小。空间应用达到阈值,触发FullGC,同时对该值扩充。当然如果元空间理论应用小于阈值,在GC的时候也会对该值放大。 MaxMetaspaceSize为元空间的最大值,如果设置太小,就会和下面提到的一样,可能会导致频繁FullGC,甚至OOM。 GC 垃圾回收过程首先贴上一张摘抄自网上的大图,联合此大图能够更加清晰的阐明GC的过程: ...

November 16, 2020 · 1 min · jiezi

关于jvm:垃圾回收算法手册自动内存管理的艺术-pdf-高清版

最近在写 JVM 的文章,R 大举荐的入门根底 GC 书籍,我曾经看了一遍感觉很不错,分享一下。 这本手册很全,看完了之后 GC 的根底算是打下了,此书在 GC 畛域位置高尚。 值得一阅。 《垃圾回收算法手册++主动内存治理的艺术》链接:https://pan.baidu.com/s/1_IE2... 提取码:zowu

November 14, 2020 · 1 min · jiezi

关于jvm:垃圾回收的算法与实现-PDF-高清版

最近在写 JVM 的文章,R大举荐的入门根底 GC 书籍,我曾经看了一遍感觉很不错,分享一下。 整本书分为对算法概念解说和GC在语言中的实现来讲。循序渐进,条例清晰。 介绍了各种GC算法、它们的优劣和利用。图示很多,有助于了解,伪代码局部很精髓,要细看。 《垃圾回收的算法与实现》 链接:https://pan.baidu.com/s/16g-H... 提取码:v73a

November 14, 2020 · 1 min · jiezi

关于jvm:那些去请求说的线程都怎样了

不晓得你有没有想过,那些去申请锁的线程都怎么了?有些可能申请到了锁,马上就能执行业务代码。然而如果有一个锁被很多个线程须要,那么这些线程是如何被解决的呢? 明天咱们走进synchronized 重量级锁,看看那些没有申请到锁的线程都怎么了。 ps: 如果你不想看剖析后果,能够拉到最初,开端有一张总结图,一图胜千言 之前文章剖析过synchroinzed中锁的优化,然而如果存在大量竞争的状况下,那么最终还是都会变成重量级锁。所以咱们这里开始间接剖析重量级锁的代码。 申请锁在ObjectMonitor::enter函数中,有很多判断和优化执行的逻辑,然而外围还是通过EnterI函数理论进入队列将将以后线程阻塞 void ObjectMonitor::EnterI(TRAPS) { Thread * const Self = THREAD; // CAS尝试将以后线程设置为持有锁的线程 if (TryLock (Self) > 0) { assert(_succ != Self, "invariant"); assert(_owner == Self, "invariant"); assert(_Responsible != Self, "invariant"); return; } // 通过自旋形式调用tryLock再次尝试,操作系统认为会有一些奥妙影响 if (TrySpin(Self) > 0) { assert(_owner == Self, "invariant"); assert(_succ != Self, "invariant"); assert(_Responsible != Self, "invariant"); return; } ... // 将以后线程构建成ObjectWaiter ObjectWaiter node(Self); Self->_ParkEvent->reset(); node._prev = (ObjectWaiter *) 0xBAD; node.TState = ObjectWaiter::TS_CXQ; ObjectWaiter * nxt; for (;;) { // 通过CAS形式将ObjectWaiter对象插入CXQ队列头部中 node._next = nxt = _cxq; if (Atomic::cmpxchg(&node, &_cxq, nxt) == nxt) break; // 因为cxq扭转,导致CAS失败,这里进行tryLock重试 if (TryLock (Self) > 0) { assert(_succ != Self, "invariant"); assert(_owner == Self, "invariant"); assert(_Responsible != Self, "invariant"); return; } } // 阻塞以后线程 for (;;) { if (TryLock(Self) > 0) break; assert(_owner != Self, "invariant"); // park self if (_Responsible == Self) { Self->_ParkEvent->park((jlong) recheckInterval); recheckInterval *= 8; if (recheckInterval > MAX_RECHECK_INTERVAL) { recheckInterval = MAX_RECHECK_INTERVAL; } } else { Self->_ParkEvent->park(); } ... if (TryLock(Self) > 0) break; ++nWakeups; if (TrySpin(Self) > 0) break; ... } ... // Self曾经获取到锁了,须要将它从CXQ或者EntryList中移除 UnlinkAfterAcquire(Self, &node); ...}在入队之前,会调用tryLock尝试通过CAS操作将_owner(以后ObjectMonitor对象锁持有的线程指针)字段设置为Self(指向以后执行的线程),如果设置胜利,示意以后线程取得了锁,否则没有。int ObjectMonitor::TryLock(Thread * Self) { void * own = _owner; if (own != NULL) return 0; if (Atomic::replace_if_null(Self, &_owner)) { return 1; } return -1;}如果tryLock没有胜利,又会再次调用tryLock(trySpin中调用了tryLock)去尝试获取锁,因为这样能够通知操作系统我迫切需要这个资源,心愿能尽量调配给我。不过这种亲和力并不是肯定能失去保障的协定,只是一种踊跃的操作。通过 ObjectWaiter对象将以后线程包裹起来,入到 CXQ 队列的头部阻塞以后线程(通过pthread_cond_wait)当线程被唤醒而获取了锁,调用UnlinkAfterAcquire办法将ObjectWaiter从CXQ或者EntryList中移除外围数据结构ObjectMonitor对象中保留了 sychronized 阻塞的线程的队列,以及实现了不同的队列调度策略,因而咱们有必须先来意识下这个对象的一些重要属性 ...

November 12, 2020 · 5 min · jiezi

关于jvm:JVM老年代垃圾收集器Serial-Old和Parallel-Old

Serial Old垃圾收集器老年代垃圾收集器,与Serial一样,是一个单线程垃圾收集器,不同的是用的算法不一样(标记-整顿) 依据老年代的特点,有人设计了标记-整顿(Mark Compact)算法,标记过程和标记-革除算法一样,然而后续步骤不是间接对可回收对象进行清理,而是让存活对象向一端挪动,而后革除掉边界以外的内存,如下图所示。 复制算法在对象存活比拟高的老年代须要复制大量的对象,效率将会贬斥,如果不想节约50%的内存,就须要有额定的空间进行调配担保,以应答对象100%都存活的极其状况,所以老年代并不实用复制算法。 复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更要害的是,如果不想节约50%的空间,就须要有额定的空间进行调配担保,以应答被应用的内存中所有对象都100%存活的极其状况,所以在老年代个别不能间接选用这种算法。Parallel Old老年代收集器,是Parallel Scavenge老年代版本,用的算法是标记-整顿,在JDK1.6提供,多线程收集

November 10, 2020 · 1 min · jiezi

关于jvm:CMS垃圾收集器

CMS垃圾收集器收集具体步骤 初始标记(Stop the world)并发标记预清理可被终止的预清理从新标记(Stop the world)并发革除并发重置初始标记标记GcRoots间接可达老年对象,新生代存活对象援用的老年代对象.整个过程在JDK1.7中是单线程的在JDK1.8中是多线程的(通过CMSParallelInitialMarkEnabled参数调整)。这个过程会导致STW。 并发标记从初始标记阶段标记过的对象开始,标记其它存活对象,这个阶段垃圾回收线程和利用线程同时运行。因为是同时运行,利用线程还在跑,会导致对象的降职,对象援用的变动,非凡对象间接调配到老年代。这些受到影响的老年代对象所在的Card会被标记成Dirty,用于从新标记阶段扫描,老年代对象的Card被标记为Dirty的可能起因如上面绿线所示。 预清理因为上一个阶段是并发执行的未标记的变动对象只是标记成了Dirty对象,还没有解决,预清理就是来标记这些Dirty对象。如下图:在并发标记阶段3号Card被标记为Dirty。这个阶段是为从新标记阶段做筹备。 预清理将6号标记为存活对象 可被终止的预清理这个阶段也是为从新标记阶段做筹备,在进入从新标记阶段前,最好能进行一个Minor GC,将年老代清理一遍, 这样能够革除大部分年老代的对象(绝大部分年老代对象朝生夕死),尽量缩短从新标记阶段进展工夫,CMS还提供了CMSScavengeBeforeRemark参数,能够在进入从新标记之前强制进行顺次Minor gc。从新标记(remark)预清理和可被终止的预清理都是为从新标记阶段做筹备,因为从新标记阶段会产生(STW),所以要保障尽肯能的进展时间段,不然就会影响应用程序的用户体验。这个阶段扫描的指标是:年老代+GC Roots+Dirty老年代对象,这个阶段是多线程的(XX:+CMSParallelRemarkEnabled)。并发革除用户线程被激活,那些未被标记的对象会被革除。'并发重置CMS垃圾收集器参数回到初始状态,为下一次垃圾收集做筹备。应用CMS垃圾收集器要留神的问题从新标记进展工夫过长80%的工夫花在从新标记阶段,如果发现从新标记阶段进展工夫过长,可尝试增加-XX:+CMSScavengeBeforeRemark,在从新标记之前做一次Minor GC,目标是缩小对老年代对象的有效援用,升高从新标记的开销。内存碎片问题CMS是基于标记-革除算法的,CMS只会删除垃圾对象,不会对内存空间做压缩,会造成内存碎片。咱们须要用 -XX:CMSFullGCsBeforeCompaction=n参数来调整,含意是在上一次CMS并发执行过后,还要执行多少次Full GC才做内存压缩.concurrent mode failure在CMS GC过程中因为应用程序也在跑,当年老代满了,执行了Minor GC这时候,须要将存活对象放入老年代,而此时老年代空间也有余,这时CMS还没有机会回收老年代。能够设置以下两个参数-XX:CMSInitiatingOccupancyFraction=75CMS对内存的占用率达到75%将启动GC,默认为92%,太高将导致promotion failed-XX:+UseCMSInitiatingOccupancyOnly如果没有设置UseCMSInitiatingOccupancyOnly,只设置了CMSInitiatingOccupancyFraction那么JVM只在第一次应用,后续会进行主动调整。为什么要设置以上两个参数,在垃圾收集阶段,用户线程还在运行,所以必须要留够空间让用户线程运行。CMS前五个阶段都是标记存活对象的,除了”初始标记”和”从新标记”阶段会stop the word ,其它三个阶段都是与用户线程一起跑的,就会呈现这样的状况gc线程正在标记存活对象,用户线程同时向老年代晋升新的对象,清理工作还没有开始,old gen曾经没有空间包容更多对象了,这时候就会导致concurrent mode failure, 而后就会应用串行收集器回收老年代的垃圾,导致进展的工夫十分长。CMSInitiatingOccupancyFraction参数要设置一个正当的值,设置大了,会减少concurrent mode failure产生的频率,设置的小了,又会减少CMS频率,所以要依据利用的运行状况来选取一个正当的值。如果发现这两个参数设置大了会导致full gc,设置小了会导致频繁的CMS GC,阐明你的老年代空间过小,应该减少老年代空间的大小了。 promotion failed在进行Minor GC时,Survivor space放不下,对象只能放入老年代,而此时老年代也放不下,大多数状况是老年代内存碎片太多,导致没有间断的空间寄存对象。过早的降职和降职失败产生Minor GC时,如果对象过大(Survivor Space寄存不下)基本上会放到老年代,这种景象被称为对象过早降职,这将导致老年代被中短期对象增张,肯能导致重大的性能问题。如果老年代也满了,会触发Full GC,这将会导致遍历整个堆,降职失败。解决方案如果是因为内存碎片导致的大对象晋升失败,cms须要进行空间整顿压缩;如果是因为晋升过快导致的,阐明Survivor 闲暇空间有余,那么能够尝试调大 Survivor;如果是因为老年代空间不够导致的,尝试将CMS触发的阈值调低。CMS总结CMS只收集老年代,响应速度优先。从新标记会STW,进展工夫较长,所以在这之前进行一次Minor GC,会缩小很多对老年代对象的有效援用。内存碎片问题导致的Full GC,请应用-XX:CMSFullGCsBeforeCompaction=n,有法则对内存进行整顿缩小内存碎片。JDK1.7,JDK1.8设置CMS垃圾收集器XX:+UseConcMarkSweepGC几个重要的CMS参数-XX:CMSFullGCsBeforeCompaction=n Full GC n 次后进行内存压缩整顿-XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSInitiatingOccupancyOnly 内存占用70%将触发CMS GC-XX:+CMSScavengeBeforeRemark CMS GC前执行一次Minor GC

November 8, 2020 · 1 min · jiezi

关于jvm:JVM常用新生代垃圾收集器

[TOC] Serial垃圾收集器Serial是单线程垃圾回收器,当须要执行垃圾回收时,程序会暂停所有工作,而后单线程执行垃圾回收. 单线程的益处就是缩小上下文切换,缩小零碎开销.然而这种形式的毛病也很显著,在GC的过程中,会暂停程序执行. 若GC产生不频繁能够选这个. 对于新声代来说,区域比拟小,进展工夫短.长处简略高效,是Client模式下默认的垃圾收集器;对于资源受限的环境,比方单核(例如Docker中设置单核),单线程效率较高;内存小于一两百兆的桌面程序中,交互无限,则无限的STW是能够承受的。毛病:垃圾回收速度较慢且回收能力无限,频繁的STW会导致较差的应用体验。利用场景是HotSpot在Client模式下默认的新生代垃圾收集器在用户的桌面利用场景中,可用内存个别不大(几十M至一两百M),能够在较短时间内实现垃圾收集(几十MS至一百多MS),只有不频繁产生,这是能够承受的ParNew垃圾收集器ParNew同样用于新生代,是Serial的多线程版本,并且在参数,算法(同样的复制算法)和Serial雷同.Par是Parallel的缩写,多线程的意思,然而这里的多线程仅仅指垃圾收集多线程并行,并不是垃圾收集和程序并行运行.ParNew也须要暂停所有工作,而后多线程并行垃圾收集. 因为是多线程执行,所以在多CPU环境下,效率比Serial高,然而在单CPU环境下,因为线程切换,反而性能比拟差. 利用场景在Server模式下,ParNew是一个十分重要的收集器,因为除Serial外,目前只有ParNew与CMS收集器配合工作. 参数"-XX:+UseConcMarkSweepGC":指定应用CMS后,会默认应用ParNew作为新生代收集器;"-XX:+UseParNewGC":强制指定应用ParNew;"-XX:ParallelGCThreads":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量雷同;Parallel scavenge垃圾收集器Parallel scavenge是一个新生代垃圾收集器,它是用复制算法的垃圾收集器,又是多线程并行的垃圾收集器,和ParNew相似.吞吐量优先的垃圾收集器,是Java1.8默认的新生代垃圾收集器.次要特点Parallel scavenge收集器的指标是达到一个可控的吞吐量,(吞吐量=运行用户代码工夫/(运行用户代码工夫+垃圾收集工夫))应用场景Parallel scavenge收集器的高吞吐量能够最高效率的利用CPU,尽快的实现程序的运算工作,次要适宜后盾运算而不是太多交互的工作(太多交互的工作,适宜用响应工夫优先的CMS垃圾收集器)Parallel scavenge能够准确管制吞吐量,通过两个参数:管制最大垃圾收集进展工夫-XX:MaxGCPauseills,设置吞吐量大小-XX:GCTimeRatio(GCTimeRatio的默认值为99,因而,GC耗时的占比应为1/(1+99)=1%。应用参数的实践成果:GCTimeRatio越大,吞吐量越大,GC的总耗时越小。有可能导致单次MinorGC耗时变长。实用于高运算场景). 它还能够用-XX:+UseAdaptiveSizePolicy参数进行自适应调节(GC Ergonomics),关上后会JVM会依据以后的运行状况动静调整最适宜的吞吐量,配合后面两个参数更好.

November 4, 2020 · 1 min · jiezi

关于jvm:JVM垃圾回收算法标记清除和复制算法

标记革除算法当堆中的无效空间被耗尽时,JVM就会进行整个程序(也被称为stop the world),而后开始两项工作.一是:标记, 而是:革除标记遍历所有GC Roots,将所有GC Roots可达的对象都标记为存活对象.革除遍历堆中所有的对象把没有标记的对象全副革除.在程序运行期间,当堆中的可用内存被耗尽时,GC线程就会启动并进行程序,GC线程将存活的对象标记一遍,没有被标记的对象就是垃圾对象,最初这些垃圾对象会被革除掉,而后从新唤醒应用程序.程序运行时堆中对象的状态(默认为0未标记,1为标记过),如果堆内存的可用空间被耗费完,那么GC线程就会启动,进行掉应用程序,应用根可达性算法进行搜寻标记. 被标记后的对象状态 应用根可达性算法,所有GC Roots可达的对象都被标记为存活对象,此时曾经实现了第一阶段的工作.接下来执行革除操作,执行完革除操作,堆中对象的状态. 没有标记的对象被革除,被标记的对象会被留下,标记为被置为0,应用程序被唤醒. 标记革除的长处是算法简略,毛病如下: 1.效率低下,须要遍历整个堆.进行GC的时候须要进行应用程序2.垃圾回收后的内存空间是不间断的,因为垃圾对象的散布很随便,那么革除后的内存会不间断. 为了解决这个问题,JVM不得不保护一个闲暇链表,又会导致额定的开销. 复制算法复制算法应用了两块等同大小的内存空间,每次只用一块,垃圾回收的时候,把存活的对象间接另外一块内存,而后残余的垃圾对象全副一次性革除.益处是复制存活对象的时候就不必思考内存碎片.惟一的毛病就是内存利用率只有50%. 当初的虚拟机个别都用复制算法回收新生代,IBM的钻研发现,新生代中的对象98%都是朝生夕死,所以并不需要1:1调配对象,而是将内存分为一个大的Eden和两块小的Survivor空间,每次只应用Eden和一块Survivor. 当进行垃圾回收时,将存活对象一次性复制到一块Survivor空间,最初革除掉Eden和应用过的Survivor空间. HotSpot虚拟机Eden:Survivor=8:1,也就新生代可用的内存达到90%,只会有10%的节约.当然98%的对象可被回收只是个别的场景,并没有方法保障每次Survivor都能寄存的下存活对象,若Survivor空间不够时,须要依附老年代进行调配担保. from ,to为Survivor区。

November 1, 2020 · 1 min · jiezi

关于jvm:聊聊JIT是如何影响JVM性能的

花半秒钟就能看透事物本质的人,和花一辈子都看不清事物本质的人,注定是截然不同的命运 --教父集体公众号:月伴飞鱼,欢送关注 之前说好的这期解说并发工具类,不过ReentrantLock源码还没肝完,理由嘛,太忙了,身材不难受,脑袋没货,睡眠不足,剧还没追完........ 但说好的每周一篇干货,不能停,明天就先介绍一篇JVM相干常识 咱们晓得Java虚拟机栈是线程公有的,每个线程对应一个栈,每个线程在执行一个办法时会创立一个对应的栈帧,栈帧负责存储局部变量变量表、操作数栈、动静链接和办法返回地址等信息,每个办法的调用过程,相当于栈帧在Java栈的入栈和出栈过程 然而栈帧的创立是须要消耗资源的,尤其是对于 Java 中常见的 getter、setter 办法来说,这些代码通常只有一行,每次都创立栈帧的话就太节约了。 另外,Java 虚拟机栈对代码的执行,采纳的是字节码解释执行的形式,思考到上面这段代码,变量 a 申明之后,就再也不被应用,要是依照字节码指令解释执行的话,就要做很多无用功。 public class A{ int attr = 0; public void test(){ int a = attr; System.out.println("月伴飞鱼"); }}执行如下命令: javap -v A能够看到这段代码的字节码指令 咱们可能看到 aload_0,getfield ,istore_1 这三个无用的字节码指令操作。 aload_0 从局部变量0中装载援用类型值,getfield 从对象中获取字段,istore_1 将int类型值存入局部变量1另外,咱们晓得垃圾回收器回收的指标区域次要是堆,堆上创立的对象越多,GC 的压力就越大。要是能把一些变量,间接在栈上调配,那 GC 的压力就会小一些。 其实,咱们说的这几个优化的可能性,JVM 曾经通过JIT 编译器(Just In Time Compiler)去做了,JIT 最次要的指标是把解释执行变成编译执行。 为了进步热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相干的机器码,并进行各种档次的优化,这就是 JIT 编译器的性能。 如上图,JVM 会将调用次数很高,或者在 for 循环里频繁被应用的代码,编译成机器码,而后缓存起来,下次调用雷同办法的时候,就能够间接应用。 那 JIT 编译都有哪些伎俩呢?接下来咱们具体介绍。 ...

October 31, 2020 · 2 min · jiezi

关于jvm:JVM判断对象是否存活

援用计数法可达性剖析算法援用计数法给对象增加一个援用计数器,每当有一个中央援用,计数器就加1,当援用生效,计数器减1,计数器为0的对象没有被应用,Java中没有应用援用计数法,起因是援用计数法无奈解决对象间的循环援用问题。 package com.rumenz;public class Testy { public Object instance = null; public static void main(String[] args) throws InterruptedException { Testy objA = new Testy(); Testy objB = new Testy(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; //假如在这行产生了gc,objA和objB是否被回收 System.gc(); //拖延时间查看堆内存对象 Thread.sleep(50000); }}VM设置参数 -XX:+PrintGCDetails -XX:-UseAdaptiveSizePolicy -XX:SurvivorRatio=8 -XX:NewSize=10M -XX:MaxNewSize=10M-XX:+PrintGCDetails 启用日志-XX:-UseAdaptiveSizePolicy 禁用动静调整,使SurvivorRatio能够起作用-XX:SurvivorRatio=8 设置Eden:Survivior=8-XX:NewSize=10M -XX:MaxNewSize=10M 设置整个新生代的大小为10M 应用jmap -histo pid查看堆内的对象 断开栈和堆对象的援用objA = null;objB = null; jmap -histo pid ...

October 26, 2020 · 1 min · jiezi

关于jvm:彻底吃透-JVM-虚拟机内存各个区域就这一篇

一、引言对于从事 C、C++ 程序开发的开发人员来说,在内存治理畛域,他们既是领有最高势力的“皇帝”,又是从事最根底工作的劳动人民——既领有每一个对象的“所有权”,又负担着每一个对象生命从开始到终结的保护责任。 对于 Java 程序员来说,在虚拟机主动内存管理机制的帮忙下,不再须要为每一个 new 操作去写配对的delete/free 代码,不容易呈现内存透露和内存溢出问题,看起来由虚拟机治理内存所有都很美妙。 不过,也正是因为Java程序员把管制内存的势力交给了Java虚拟机,一旦呈现内存透露和溢出方面的问题,如果不理解虚拟机是怎么应用内存的,那排查谬误、修改问题将会成为一项异样艰巨的工作。 二、运行时数据区域Java 虚拟机在执行 Java 程序的过程中会把它治理的内存划分成若干个不同的数据区域。 运行时数据区域 这些组成部分一些事线程公有的,其余的则是线程共享的。 线程公有的: 程序计数器虚拟机栈本地办法栈线程共享的: 堆办法区间接内存三、程序计数器程序计数器(Program Counter Register)是一块较小的内存空间,它能够看作是以后线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里,字节码解释器工作时就是通过扭转这个计数器的值来选取下一条须要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异样解决、线程复原等根底性能都须要依赖这个计数器来实现。 因为Java虚拟机的多线程是通过线程轮流切换、调配处理器执行工夫的形式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令 因而,为了线程切换后能复原到正确的执行地位,每条线程都须要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,咱们称这类内存区域为“线程公有”的内存 如果线程正在执行的是一个Java办法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)办法,这个计数器值则应为空(Undefined)。此内存区域是惟一一个在《Java虚拟机标准》中没有规定任何 OutOfMemoryError 状况的区域 四、Java 虚拟机栈与程序计数器一样,Java虚拟机栈也是线程公有的,它的生命周期和线程雷同,形容的是 Java 办法执行的内存模型。 Java 内存能够毛糙的辨别为堆内存(Heap)和栈内存(Stack),其中栈就是当初说的虚拟机栈,或者说是虚拟机栈中局部变量表局部。 (实际上,Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都领有:局部变量表、操作数栈、动静链接、办法进口信息。) 局部变量表次要寄存了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象援用(reference类型,它不同于对象自身,可能是一个指向对象起始地址的援用指针,也可能是指向一个代表对象的句柄或其余与此对象相干的地位)。 Java 虚拟机栈会呈现两种异样:StackOverFlowError 和 OutOfMemoryError。 StackOverFlowError: 若Java虚拟机栈的内存大小不容许动静扩大,那么当线程申请栈的深度超过以后Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异样。OutOfMemoryError: 若 Java 虚拟机栈的内存大小容许动静扩大,且当线程申请栈时内存用完了,无奈再动静扩大了,此时抛出OutOfMemoryError异样。Java 虚拟机栈也是线程公有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创立而创立,随着线程的死亡而死亡。 五、本地办法栈本地办法栈(Native Method Stacks)与虚拟机栈所施展的作用是十分类似的,其区别只是虚拟机栈为虚拟机执行Java办法(也就是字节码)服务,而本地办法栈则是为虚拟机应用到的本地(Native)办法服务 《Java虚拟机标准》对本地办法栈中办法应用的语言、应用形式与数据结构并没有任何强制规定,因而具体的虚拟机能够依据须要自在实现它,甚至有的Java虚拟机(譬如Hot-Spot虚拟机)间接就把本地办法栈和虚拟机栈合二为一。与虚拟机栈一样,本地办法栈也会在栈深度溢出或者栈扩大失败时别离抛出StackOverflowError和OutOfMemoryError异样。 六、Java 堆对于Java应用程序来说,Java堆(Java Heap)是虚拟机所治理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创立。此内存区域的惟一目标就是寄存对象实例,Java世界里“简直”所有的对象实例都在这里分配内存 在《Java虚拟机标准》中对Java堆的形容是:“所有的对象实例以及数组都该当在堆上调配”,而这里笔者写的“简直”是指从实现角度来看,随着Java语言的倒退,当初曾经能看到些许迹象表明日后可能呈现值类型的反对,即便只思考当初,因为即时编译技术的提高,尤其是逃逸剖析技术的日渐弱小,栈上调配、标量替换[插图]优化伎俩曾经导致一些奥妙的变动悄悄产生,所以说Java对象实例都调配在堆上也慢慢变得不是那么相对了。 Java堆是垃圾收集器治理的内存区域,因而一些材料中它也被称作“GC堆”(Garbage Collected Heap,幸好国内没翻译成“垃圾堆”)。从回收内存的角度看,因为古代垃圾收集器大部分都是基于分代收集实践设计的,所以Java堆中常常会呈现“新生代”“老年代”“永恒代”“Eden空间”“From Survivor空间”“To Survivor空间”等名词,在这里笔者想先阐明的是这些区域划分仅仅是一部分垃圾收集器的独特个性或者说设计格调而已,而非某个Java虚拟机具体实现的固有内存布局,更不是《Java虚拟机标准》里对Java堆的进一步粗疏划分。不少材料上常常写着相似于“Java虚拟机的堆内存分为新生代、老年代、永恒代、Eden、Survivor……”这样的内容。在十年之前(以G1收集器的呈现为分界),作为业界相对支流的HotSpot虚拟机,它外部的垃圾收集器全副都基于“经典分代”来设计,须要新生代、老年代收集器搭配能力工作,在这种背景下,上述说法还算是不会产生太大歧义。然而到了明天,垃圾收集器技术与十年前已不可同日而语,HotSpot外面也呈现了不采纳分代设计的新垃圾收集器,再依照下面的提法就有很多须要商讨的中央了。 如果从分配内存的角度看,所有线程共享的Java堆中能够划分出多个线程公有的调配缓冲区(Thread Local Allocation Buffer,TLAB),以晋升对象调配时的效率。不过无论从什么角度,无论如何划分,都不会扭转Java堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将Java堆细分的目标只是为了更好地回收内存,或者更快地分配内存。在本章中,咱们仅仅针对内存区域的作用进行探讨,Java堆中的上述各个区域的调配、回收等细节将会是下一章的主题。 依据《Java虚拟机标准》的规定,Java堆能够处于物理上不间断的内存空间中,但在逻辑上它应该被视为间断的,这点就像咱们用磁盘空间去存储文件一样,并不要求每个文件都间断寄存。但对于大对象(典型的如数组对象),少数虚拟机实现出于实现简略、存储高效的思考,很可能会要求间断的内存空间。 Java堆既能够被实现成固定大小的,也能够是可扩大的,不过以后支流的Java虚拟机都是依照可扩大来实现的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存实现实例调配,并且堆也无奈再扩大时,Java虚拟机将会抛出OutOfMemoryError异样。 七、办法区办法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、动态变量、即时编译器编译后的代码缓存等数据。尽管《Java虚拟机标准》中把办法区形容为堆的一个逻辑局部,然而它却有一个别名叫作“非堆”(Non-Heap),目标是与Java堆辨别开来。 说到办法区,不得不提一下永恒代 这个概念,尤其是在JDK 8以前,许多Java程序员都习惯在HotSpot虚拟机上开发、部署程序,很多人都更违心把办法区称说为“永恒代”(Permanent Generation),或将两者一概而论 实质上这两者并不是等价的,因为仅仅是过后的HotSpot虚拟机设计团队抉择把收集器的分代设计扩大至办法区,或者说应用永恒代来实现办法区而已,这样使得HotSpot的垃圾收集器可能像治理Java堆一样治理这部分内存,省去专门为办法区编写内存治理代码的工作。 ...

October 24, 2020 · 1 min · jiezi

关于jvm:JAVA的对象访问定位

创建对象是为了拜访对象,Java程序通过栈的援用(reference)数据来操作堆上的对象。因为reference类型在Java虚拟机标准中只规定了一个指向对象的援用。并没有规定通过该援用怎么定位,拜访堆中的对象。具体须要看虚拟机的实现。 两种拜访形式: 句柄拜访间接拜访句柄拜访Java堆中会划分一个句柄池,reference存储的就是对象的句柄地址,而句柄中寄存的是对象的实例数据和类型数据的地址信息。 间接拜访Java堆对象布局就必须思考如何寄存拜访类型数据的相干信息,reference存储的就是对象的地址。 句柄拜访和间接拜访的特点句柄拜访:reference寄存的是句柄地址(比较稳定),在对象挪动时(垃圾回收),只会扭转句柄中实例数据的地址,而reference无需扭转。间接拜访:因为节俭了一次指针开销访问速度比拟快,因为对象的拜访在Java堆上拜访特地频繁。Sun HotSpot虚拟机采纳的是间接拜访。

October 23, 2020 · 1 min · jiezi

关于jvm:JVM内存模型

总览 JVM规范中的五个组成部分办法区堆程序计数器本地办法栈虚拟机栈JDK1.7的运行时数据区 永恒代是办法区的实现jdk1.6之前字符串常量池在办法区jdk1.7之后字符串常量池被挪动到堆区JDK1.8的运行时数据区 jdk1.8去掉了永恒代引入了元数据区Jdk1.7中的运行时常量池挪动到元数据区元数据区存在于间接内存中为什么移除永恒代办法区大小难以设定,容易产生内存溢出。永恒代寄存着Class相干信息,个别信息在编译期就能确定,然而如果在一些动静生成的Class的利用中,如:Spring中的动静代理,大量的JSP页面或动静生成的JSP页面,因为办法区在一开始就要调配好,因而难以确定大小,容易产生内存溢出。GC简单效率低,办法区寄存元数据和各种常量,然而这些数据被类的实例所援用,导致垃圾回收十分艰难.促成HotSpot VM和JRockit VM交融,JRockit VM没有办法区什么是元空间元空间和永恒代相似都是对JVM标准中办法区的实现。区别在于元空间不在JVM虚拟机中,因而元空间的空间受本地内存制约。元空间特点每个加载器都有本人的空间不会独自回收某个类元空间对象的地位是固定的如果发现某个加载器不在存活,则将整个空间回收

October 21, 2020 · 1 min · jiezi

关于jvm:JVM学习JVM指令手册

一、栈和局部变量操作将常量压入栈的指令aconst_null 将null对象援用压入栈iconst_m1 将int类型常量-1压入栈iconst_0 将int类型常量0压入栈iconst_1 将int类型常量1压入栈iconst_2 将int类型常量2压入栈iconst_3 将int类型常量3压入栈iconst_4 将int类型常量4压入栈iconst_5 将int类型常量5压入栈lconst_0 将long类型常量0压入栈lconst_1 将long类型常量1压入栈fconst_0 将float类型常量0压入栈fconst_1 将float类型常量1压入栈dconst_0 将double类型常量0压入栈dconst_1 将double类型常量1压入栈bipush 将一个8位带符号整数压入栈sipush 将16位带符号整数压入栈ldc 把常量池中的项压入栈ldc_w 把常量池中的项压入栈(应用宽索引)ldc2_w 把常量池中long类型或者double类型的项压入栈(应用宽索引)从栈中的局部变量中装载值的指令iload 从局部变量中装载int类型值lload 从局部变量中装载long类型值fload 从局部变量中装载float类型值dload 从局部变量中装载double类型值aload 从局部变量中装载援用类型值(refernce)iload_0 从局部变量0中装载int类型值iload_1 从局部变量1中装载int类型值iload_2 从局部变量2中装载int类型值iload_3 从局部变量3中装载int类型值lload_0 从局部变量0中装载long类型值lload_1 从局部变量1中装载long类型值lload_2 从局部变量2中装载long类型值lload_3 从局部变量3中装载long类型值fload_0 从局部变量0中装载float类型值fload_1 从局部变量1中装载float类型值fload_2 从局部变量2中装载float类型值fload_3 从局部变量3中装载float类型值dload_0 从局部变量0中装载double类型值dload_1 从局部变量1中装载double类型值dload_2 从局部变量2中装载double类型值dload_3 从局部变量3中装载double类型值aload_0 从局部变量0中装载援用类型值aload_1 从局部变量1中装载援用类型值aload_2 从局部变量2中装载援用类型值aload_3 从局部变量3中装载援用类型值iaload 从数组中装载int类型值laload 从数组中装载long类型值faload 从数组中装载float类型值daload 从数组中装载double类型值aaload 从数组中装载援用类型值baload 从数组中装载byte类型或boolean类型值caload 从数组中装载char类型值saload 从数组中装载short类型值将栈中的值存入局部变量的指令istore 将int类型值存入局部变量lstore 将long类型值存入局部变量fstore 将float类型值存入局部变量dstore 将double类型值存入局部变量astore 将将援用类型或returnAddress类型值存入局部变量istore_0 将int类型值存入局部变量0istore_1 将int类型值存入局部变量1istore_2 将int类型值存入局部变量2istore_3 将int类型值存入局部变量3lstore_0 将long类型值存入局部变量0lstore_1 将long类型值存入局部变量1lstore_2 将long类型值存入局部变量2lstore_3 将long类型值存入局部变量3fstore_0 将float类型值存入局部变量0fstore_1 将float类型值存入局部变量1fstore_2 将float类型值存入局部变量2fstore_3 将float类型值存入局部变量3dstore_0 将double类型值存入局部变量0dstore_1 将double类型值存入局部变量1dstore_2 将double类型值存入局部变量2dstore_3 将double类型值存入局部变量3astore_0 将援用类型或returnAddress类型值存入局部变量0astore_1 将援用类型或returnAddress类型值存入局部变量1astore_2 将援用类型或returnAddress类型值存入局部变量2astore_3 将援用类型或returnAddress类型值存入局部变量3iastore 将int类型值存入数组中lastore 将long类型值存入数组中fastore 将float类型值存入数组中dastore 将double类型值存入数组中aastore 将援用类型值存入数组中bastore 将byte类型或者boolean类型值存入数组中castore 将char类型值存入数组中sastore 将short类型值存入数组中wide指令wide 应用附加字节扩大局部变量索引通用(无类型)栈操作nop 不做任何操作pop 弹出栈顶端一个字长的内容pop2 弹出栈顶端两个字长的内容dup 复制栈顶部一个字长内容dup_x1 复制栈顶部一个字长的内容,而后将复制内容及原来弹出的两个字长的内容压入栈dup_x2 复制栈顶部一个字长的内容,而后将复制内容及原来弹出的三个字长的内容压入栈dup2 复制栈顶部两个字长内容dup2_x1 复制栈顶部两个字长的内容,而后将复制内容及原来弹出的三个字长的内容压入栈dup2_x2 复制栈顶部两个字长的内容,而后将复制内容及原来弹出的四个字长的内容压入栈swap 替换栈顶部两个字长内容二、类型转换i2l 把int类型的数据转化为long类型i2f 把int类型的数据转化为float类型i2d 把int类型的数据转化为double类型l2i 把long类型的数据转化为int类型l2f 把long类型的数据转化为float类型l2d 把long类型的数据转化为double类型f2i 把float类型的数据转化为int类型f2l 把float类型的数据转化为long类型f2d 把float类型的数据转化为double类型d2i 把double类型的数据转化为int类型d2l 把double类型的数据转化为long类型d2f 把double类型的数据转化为float类型i2b 把int类型的数据转化为byte类型i2c 把int类型的数据转化为char类型i2s 把int类型的数据转化为short类型三、整数运算iadd 执行int类型的加法ladd 执行long类型的加法isub 执行int类型的减法lsub 执行long类型的减法imul 执行int类型的乘法lmul 执行long类型的乘法idiv 执行int类型的除法ldiv 执行long类型的除法irem 计算int类型除法的余数lrem 计算long类型除法的余数ineg 对一个int类型值进行取反操作lneg 对一个long类型值进行取反操作iinc 把一个常量值加到一个int类型的局部变量上四、逻辑运算移位操作ishl 执行int类型的向左移位操作lshl 执行long类型的向左移位操作ishr 执行int类型的向右移位操作lshr 执行long类型的向右移位操作iushr 执行int类型的向右逻辑移位操作lushr 执行long类型的向右逻辑移位操作按位布尔运算iand 对int类型值进行“逻辑与”操作land 对long类型值进行“逻辑与”操作ior 对int类型值进行“逻辑或”操作lor 对long类型值进行“逻辑或”操作ixor 对int类型值进行“逻辑异或”操作lxor 对long类型值进行“逻辑异或”操作浮点运算fadd 执行float类型的加法dadd 执行double类型的加法fsub 执行float类型的减法dsub 执行double类型的减法fmul 执行float类型的乘法dmul 执行double类型的乘法fdiv 执行float类型的除法ddiv 执行double类型的除法frem 计算float类型除法的余数drem 计算double类型除法的余数fneg 将一个float类型的数值取反dneg 将一个double类型的数值取反五、对象和数组对象操作指令new 创立一个新对象checkcast 确定对象为所给定的类型。后跟指标类,判断栈顶元素是否为指标类 / 接口的实例。如果不是便抛出异样getfield 从对象中获取字段putfield 设置对象中字段的值getstatic 从类中获取动态字段putstatic 设置类中动态字段的值instanceof 判断对象是否为给定的类型。后跟指标类,判断栈顶元素是否为指标类 / 接口的实例。是则压入 1,否则压入 0数组操作指令newarray 调配数据成员类型为基本上数据类型的新数组anewarray 调配数据成员类型为援用类型的新数组arraylength 获取数组长度multianewarray 调配新的多维数组六、控制流条件分支指令ifeq 如果等于0,则跳转ifne 如果不等于0,则跳转iflt 如果小于0,则跳转ifge 如果大于等于0,则跳转ifgt 如果大于0,则跳转ifle 如果小于等于0,则跳转if_icmpcq 如果两个int值相等,则跳转if_icmpne 如果两个int类型值不相等,则跳转if_icmplt 如果一个int类型值小于另外一个int类型值,则跳转if_icmpge 如果一个int类型值大于或者等于另外一个int类型值,则跳转if_icmpgt 如果一个int类型值大于另外一个int类型值,则跳转if_icmple 如果一个int类型值小于或者等于另外一个int类型值,则跳转ifnull 如果等于null,则跳转ifnonnull 如果不等于null,则跳转if_acmpeq 如果两个对象援用相等,则跳转if_acmpnc 如果两个对象援用不相等,则跳转比拟指令lcmp 比拟long类型值fcmpl 比拟float类型值(当遇到NaN时,返回-1)fcmpg 比拟float类型值(当遇到NaN时,返回1)dcmpl 比拟double类型值(当遇到NaN时,返回-1)dcmpg 比拟double类型值(当遇到NaN时,返回1)无条件转移指令goto 无条件跳转goto_w 无条件跳转(宽索引)表跳转指令tableswitch 通过索引拜访跳转表,并跳转lookupswitch 通过键值匹配拜访跳转表,并执行跳转操作异样athrow 抛出异样或谬误。将栈顶异样抛出finally子句jsr 跳转到子例程jsr_w 跳转到子例程(宽索引)rct 从子例程返回七、办法调用与返回办法调用指令invokcvirtual 运行时依照对象的类来调用实例办法invokespecial 依据编译时类型来调用实例办法invokestatic 调用类(动态)办法invokcinterface 调用接口办法办法返回指令ireturn 从办法中返回int类型的数据lreturn 从办法中返回long类型的数据freturn 从办法中返回float类型的数据dreturn 从办法中返回double类型的数据areturn 从办法中返回援用类型的数据return 从办法中返回,返回值为void线程同步montiorenter 进入并获取对象监视器。即:为栈顶对象加锁monitorexit 开释并退出对象监视器。即:为栈顶对象解锁八、JVM指令助记符变量到操作数栈:iload,iload_,lload,lload_,fload,fload_,dload,dload_,aload,aload_操作数栈到变量:istore,istore_,lstore,lstore_,fstore,fstore_,dstore,dstor_,astore,astore_常数到操作数栈:bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,iconst_,lconst_,fconst_,dconst_加:iadd,ladd,fadd,dadd减:isub,lsub,fsub,dsub乘:imul,lmul,fmul,dmul除:idiv,ldiv,fdiv,ddiv余数:irem,lrem,frem,drem取负:ineg,lneg,fneg,dneg移位:ishl,lshr,iushr,lshl,lshr,lushr按位或:ior,lor按位与:iand,land按位异或:ixor,lxor类型转换:i2l,i2f,i2d,l2f,l2d,f2d(放宽数值转换)i2b,i2c,i2s,l2i,f2i,f2l,d2i,d2l,d2f(缩窄数值转换)创立类实便:new创立新数组:newarray,anewarray,multianwarray拜访类的域和类实例域:getfield,putfield,getstatic,putstatic把数据装载到操作数栈:baload,caload,saload,iaload,laload,faload,daload,aaload从操作数栈存存储到数组:bastore,castore,sastore,iastore,lastore,fastore,dastore,aastore获取数组长度:arraylength检相类实例或数组属性:instanceof,checkcast操作数栈治理:pop,pop2,dup,dup2,dup_xl,dup2_xl,dup_x2,dup2_x2,swap有条件转移:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnonnull,if_icmpeq,if_icmpene,if_icmplt,if_icmpgt,if_icmple,if_icmpge,if_acmpeq,if_acmpne,lcmp,fcmplfcmpg,dcmpl,dcmpg复合条件转移:tableswitch,lookupswitch无条件转移:goto,goto_w,jsr,jsr_w,ret调度对象的实便办法:invokevirtual调用由接口实现的办法:invokeinterface调用须要非凡解决的实例办法:invokespecial调用命名类中的静态方法:invokestatic办法返回:ireturn,lreturn,freturn,dreturn,areturn,return异样:athrowfinally关键字的实现应用:jsr,jsr_w,ret

October 21, 2020 · 1 min · jiezi

关于jvm:JVM程序计数器虚拟机栈本地方法栈

程序计数器它记录了程序执行字节码的行号和指令,字节码解释器的工作就是改变程序计数器的值,切换下一条须要执行的指令(分支,循环,跳转,异样等)。java虚拟机是多线程通过轮流切换CPU工夫片的形式实现,在同一时间内,CPU只会执行一个线程中的一个指令,为了每次切换回来都能到正确的执行地位,每个线程都会有一个独立的线程计数器,每个计数器不会相互影响,并且是线程公有的。因为不是开发者操作,所以是不会产生异样的。虚拟机栈虚拟机栈也是线程公有的,它的申明周期与线程一样(和线程同生死)。如果线程申请栈的深度大于虚拟机所容许的深度则会报错StackOverFlow的谬误。如果虚拟机能够动静扩大,如果扩大后无奈取得到内存,就会报错OutOfMemoryError。java虚拟机栈形容的是Java办法执行的内存模型,每个办法执行的同时都会创立一个栈帧,对于咱们来说次要关注栈内存,也是办法内的局部变量。栈帧栈帧虚拟机进行办法调用和办法执行的数据结构,它是虚拟机运行时数据区虚拟机栈的栈元素。栈帧存储了办法的局部变量表,操作数栈,动静链接和办法返回地址信息。在程序编译期,栈帧须要多大的局部变量表内存,多深的操作数栈曾经确定。在流动线程中,栈顶的栈帧才是无效的,与这个栈帧关联的形式是以后办法,执行引擎运行的所有字节码指令都只会作用于以后栈帧。 本地办法栈本地办法栈和虚拟机栈施展的作用根本一样。区别是:本地办法栈执行的是Native办法服务,而虚拟机栈执行的是java办法。在HotSpot vm中本地办法栈和虚拟机栈合二为一。 留神: 当一个线程调用本地办法时,就会进入了一个不受java虚拟机限度的世界,它和虚拟机有着同样的权限。本地办法能够通过本地接口拜访虚拟机运行时数据区。它能够间接应用本地处理器的寄存器。并不是所有的JVM都反对本地办法。如果JVM产品不打算反对Native办法,也能够不必实现本地办法栈。

October 19, 2020 · 1 min · jiezi

关于jvm:看这一篇学会读jvm字节码

装置插件在setting-Plugins 搜寻jclass装置下图中的插件。 查看字节码1、首先咱们写一个简略的java代码来察看其字节码: public class TestExep { public static void main(String[] args) { new TestExep().f(); } public int f(){ int a = 1+3; a++; return a; }}2、运行后,点击源程序-》点击view->Show Bytecode With jclasslib 3、 咱们当初看到上图左边一块就是jclasslib解析进去的字节码:次要有上面几块内容: 1) General Information: 类的一些根本信息,次要信息如下: Major Version = 52 示意编译此类文件的jdk版本1.8 Constant pool count = 27 是常量池大小 Access Flag = 0x0021 [public] 示意类的拜访修饰符 This class : 本类 Super class : 父类 Interfaces count = 0 实现接口数0(类中没有实现任何接口) Fields count = 0 属性数(没有定义任何属性) Methods count = 3 这是办法数(类中只写了两个办法,为什么有3个办法呢,因为还有一个是类的默认构造方法<init>) 2) Constant pool: 常量池 ...

October 19, 2020 · 2 min · jiezi

关于jvm:深入理解-JVM-垃圾回收算法-复制算法

通常来说,在整个程序的运行过程中,垃圾回收只会占用很小一部分工夫,赋值器的执行会占用更多的工夫,因而,内存调配速度的快慢将间接决定整个程序的性能。很显著,后面提到的标记-清理算法并不是一个很好的范例,尽管它的算法简略且实现容易,但存在很重大的内存碎片化问题,会重大影响内存的调配速度。 标记-整顿算法能够铲除碎片问题,而且调配速度也很快,但在垃圾回收过程中会进行屡次堆遍历,进而显著减少了回收工夫。 本文将介绍第三种根本的垃圾回收算法:半区复制算法。回收器在整个过程中通过复制的形式来进行堆整顿,从而晋升内存调配速度,且回收过程中只需对存活对象便当一次,其最大的毛病是堆的可用空间升高了一半。 复制算法是一种典型的以空间换工夫的算法。 实现原理在复制算法中,回收器将堆空间划分为两个大小相等的半区 (semispace),别离是 起源空间(fromspace) 和 指标空间(tospace) 。在进行垃圾回收时,回收器将存活对象从起源空间复制到指标空间,复制完结后,所有存活对象严密排布在指标空间一端,最初将起源空间和指标空间调换。半区复制算法的概要如下图所示。 接下来看下代码如何实现?次要流程很简略,有一个 free 指针指向TOSPACE的终点,从根节点开始遍历,将根节点及其援用的子节点全副复制到TOSPACE,每复制一个对象,就把 free 指针向后挪动相应大小的地位,最初替换FROMSPACE和TOSPACE,大抵可用如下代码形容: collect() {// 变量后面加*示意指针// free指向TOSPACE半区的起始地位*free = *to_start;for(root in Roots) {copy(*free, root);}// 替换FROMSPACE和TOSPACEswap(*from_start,*to_start);}复制代码外围函数 copy 的实现如下所示: copy(*free,obj) {// 查看obj是否曾经复制实现// 这里的tag仅是一个逻辑上的域if(obj.tag != COPIED) {// 将obj真正的复制到free指向的空间copy_data(*free,obj);// 给obj.tag贴上COPIED这个标签// 即便有多个指向obj的指针,obj也不会被复制屡次obj.tag = COPIED;// 复制实现后把对象的新地址寄存在老对象的forwarding域中obj.forwarding = *free;// 依照obj的长度将free指针向前挪动*free += obj.size;// 递归调用copy函数复制其关联的子对象for(child ingetRefNode(obj.forwarding)){*child = copy(*free,child);}}returnobj.forwarding;}复制代码在这段代码中须要留神两个问题,其一是 tag=COPIED 只是一个逻辑上的概念,用来辨别对象是否曾经实现复制,以确保即便这个对象被屡次援用,也仅会复制一次;另外一个问题则是 forwarding 域, forwarding指针 在后面曾经屡次提到过,次要是用来保留对象挪动后的新地址,比方在标记整顿算法中,对象挪动后须要遍历更新对象的援用关系,就须要应用 forwarding指针 来查找其挪动后的地址,而在复制算法中,其作用相似,如果遇到已复制实现的对象,间接通过forwarding域把对象的新地址返回即可。整个复制算法的根本致流程如下图所示。 接下来用一个具体的示例看看复制算法的大抵流程。堆中对象的关系如下图所示,其中free指针指向TOSPACE的终点地位。 首先,从根节点登程,找到它间接援用的对象B和E,其中对象B首先被复制到TOSPACE。B被复制后的堆的关系如下图所示。 这里将B被复制后生成的对象成为B',而原来的对象B中 tag 域曾经打上复制实现的标签,而 forwarding指针 也寄存着B'的地址。 ...

October 19, 2020 · 1 min · jiezi

关于jvm:JVM常量池

Class文件常量池class文件是以字节为单位的二进制数据流,java编译器将java源码文件编译成.class字节码文件寄存在磁盘上,.class中就蕴含文件常量池(非运行时常量池),在编译期间就确定了,.class文件遵循jvm虚拟机标准. Java源码: package com.rumenz;public class rumenz{ public Integer id=10; public String name="入门"; public final int age=100; public void setId(Integer id){ this.id=id; }}查看字节码 > javac rumenz.java> javap -v rumenz正告: 二进制文件rumenz蕴含com.rumenz.rumenzClassfile /code/rumenz.class Last modified 2020-10-17; size 542 bytes MD5 checksum 6a8a73fb6327c1a64e9ad54e53e94afd Compiled from "rumenz.java"public class com.rumenz.rumenz minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Methodref #9.#24 // java/lang/Object."<init>":()V #2 = Fieldref #8.#25 // com/rumenz/rumenz.id:I #3 = String #26 // 入门 #4 = Fieldref #8.#27 // com/rumenz/rumenz.name:Ljava/lang/String; #5 = Methodref #28.#29 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer; #6 = Fieldref #8.#30 // com/rumenz/rumenz.age:Ljava/lang/Integer; #7 = Methodref #28.#31 // java/lang/Integer.intValue:()I #8 = Class #32 // com/rumenz/rumenz #9 = Class #33 // java/lang/Object #10 = Utf8 id #11 = Utf8 I #12 = Utf8 name #13 = Utf8 Ljava/lang/String; #14 = Utf8 age #15 = Utf8 Ljava/lang/Integer; #16 = Utf8 <init> #17 = Utf8 ()V #18 = Utf8 Code #19 = Utf8 LineNumberTable #20 = Utf8 setId #21 = Utf8 (Ljava/lang/Integer;)V #22 = Utf8 SourceFile #23 = Utf8 rumenz.java #24 = NameAndType #16:#17 // "<init>":()V #25 = NameAndType #10:#11 // id:I #26 = Utf8 入门 #27 = NameAndType #12:#13 // name:Ljava/lang/String; #28 = Class #34 // java/lang/Integer #29 = NameAndType #35:#36 // valueOf:(I)Ljava/lang/Integer; #30 = NameAndType #14:#15 // age:Ljava/lang/Integer; #31 = NameAndType #37:#38 // intValue:()I #32 = Utf8 com/rumenz/rumenz #33 = Utf8 java/lang/Object #34 = Utf8 java/lang/Integer #35 = Utf8 valueOf #36 = Utf8 (I)Ljava/lang/Integer; #37 = Utf8 intValue #38 = Utf8 ()I{ public int id; descriptor: I flags: ACC_PUBLIC public java.lang.String name; descriptor: Ljava/lang/String; flags: ACC_PUBLIC public final java.lang.Integer age; descriptor: Ljava/lang/Integer; flags: ACC_PUBLIC, ACC_FINAL public com.rumenz.rumenz(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: bipush 10 7: putfield #2 // Field id:I 10: aload_0 11: ldc #3 // String 入门 13: putfield #4 // Field name:Ljava/lang/String; 16: aload_0 17: bipush 100 19: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 22: putfield #6 // Field age:Ljava/lang/Integer; 25: return LineNumberTable: line 11: 0 line 12: 4 line 13: 10 line 14: 16 public void setId(java.lang.Integer); descriptor: (Ljava/lang/Integer;)V flags: ACC_PUBLIC Code: stack=3, locals=3, args_size=2 0: aload_0 1: iconst_3 2: aload_1 3: invokevirtual #7 // Method java/lang/Integer.intValue:()I 6: iadd 7: putfield #2 // Field id:I 10: return LineNumberTable: line 17: 0 line 18: 10}SourceFile: "rumenz.java"什么是常量文本字面量#29 = Utf8 入门final润饰成员变量(动态变量,实例变量,局部变量)对于根本类型public Integer id=10;常量池中只保留了他的字段描述符I和字段名称value,他们的字面量不会存在于常量池。符号援用符号援用次要设波及编译原理方面的概念,包含上面三类常量:类和接口的全限定名,也就是java/lang/String,将原来的.替换成/,次要用于在运行时解析失去类的间接援用。#8 = Class #32 // com/rumenz/rumenz字段的名称和描述符:就是类或者接口中申明的变量,包含类级别的变量和实例级的变量#14 = Utf8 age办法中的名称和描述符:参数类型+返回值 #20 = Utf8 setId运行时常量池运行时常量池是办法区的一部分,所以也是全局共享的,JVM在执行某个类的时候会通过加载,链接(验证,筹备,解析),初始化,在加载的时候须要做:通过一个类的全类限定名获取此类的二进制字节流。在堆内存生成一个java.lang.Class对象,代表加载这个类,做为这个类的入口。一般对象和类对象的区别:一般对象是通过new创立进去的。类对象是JVM创立的单例对象。将字节流的动态存储构造转化成办法区的运行时数据结构class文件常量池进入运行时常量池,所有类独特应用一个运行时常量池,在进入运行时常量的过程中,多个class常量池中雷同的字符串,只会在运行时常量池存在一份,这是一种优化。运行时常量池的作用是存储class文件常量池中的符号援用,同时运行时常量池保留着class文件中形容的符号援用,在类的解析阶段会把这些符号援用转换成间接援用(实例对象的内存地址),翻译进去的间接援用也是存储在运行时常量池中。class文件常量池的大部分数据会被加载到运行时常量池。 ...

October 18, 2020 · 2 min · jiezi

关于jvm:Java虚拟机

java的长处:解脱了硬件平台解放,实现了一次编写,到处运行的现实提供了一个绝对平安的内存治理和拜访机制,防止了绝大多数的内存透露和指针越界实现了热点代码检测和运行时编译及优化,使得java利用能随着运行工夫的减少而取得更高的性能有一套欠缺的利用程序接口,还有有数商业机构和开源社区的第三方类库第一局部Java技术体系java程设计语言各种硬件平台的java虚拟机Class文件格式java api类库第三方java类库JDK : java程序设计语言,java虚拟机和java api。 是反对java程序开发的最小环境。JRE : 把java api类库重的 java SE api子集和java虚拟机这两局部统称为jre。 Jre 是反对java程序经营的规范环境 第二局部 主动内存治理 2 运行时数据区域2.1 程序计数器能够看作是以后线程所执行的字节码行号指示器 如果线程正在执行的是一个java办法,这个计数器记录的是正在执行的虚拟机字节码的指令地址;如果正在执行的是Native办法,这个计数器值为空(Undefined)。此内存区域是惟一一个在java虚拟机标准中没有规定任何OOM状况的区域 2.2虚拟机栈线程公有的,生命周期与线程雷同。虚拟机栈形容的是java办法执行的内存模型:每个办法在执行时都会创立一个栈帧。用于存储局部变量表、操作数栈、动静链接、办法进口等信息。 局部变量表寄存了编译器可知的各种根本类型(boolean、byte、char、short、int、float、long、double)对象援用(refrence类型,他不等同于对象自身,可能是一个执行对象起始地址的援用指针,也可能是只想一个代表对象的句柄或其余与此对象相干的地位)和returnAddress类型(指向了一条字节码指令的地址) 局部变量表所须要的内存空间在编译期间实现调配,当进入一个办法时,这个办法须要在帧中调配多大的局部变量空间是齐全确定的。 2.3本地办法栈本地办法栈与虚拟机栈施展的作用相似,不过虚拟机栈为虚拟机执行java办法(也就是字节码)服务,本地办法栈则为虚拟机应用到的Native办法服务。在虚拟机标准中办法应用的语言、应用的形式与数据结构并没有强制规定,因而具体的虚拟机能够自在实现。本地办法栈与虚拟机一样也会跑出StackOverflowError 和 OutOfMemoryError 2.3java 堆java堆是被虽有线程共享的一块内存区域。此区域的惟一目标就是寄存对象实例。简直所有的对象实例都在这里分配内存。 所有的对象实力以及数组都要在堆上调配,然而随着JIT编译器的倒退和陶艺剖析技术逐步成熟,栈上调配、标亮替换优化技术将会导致一些奥妙的变动,所有的对象都调配在堆上也慢慢变得不那么相对了。因为当初的收集器根本都采纳粉黛手机算法,所以Java堆还能够细分为:新生代和老年代。再粗疏一点: EdenFrom SurvivorTo SurvivorOldPermanent(对于HotSpot)如果在堆中没有内存实现实例调配,并且堆也无奈再扩大时,将会抛出OutOfMemoryError异样 2.5 办法区(Method Area) 与堆一样,是各个线程共享的内存区域。用于存储曾经被虚拟机记录的类信息、常量、动态变量、即时编译器编译后的代码等数据。尽管Java虚拟机标准把办法区形容为堆的一个逻辑,然而它却有一个别名叫做Non-heap,目标应该是与Java堆辨别开 仅对于Hotspot虚拟机来说,存在永恒代这个概念。仅仅是因为HotSpot虚拟机设计团队抉择把GC分代手机扩大至办法去,或者应用永恒代实现办法区而已。对于其余虚拟机则不存在永恒代。 对这一区域的内存回收次要是针对常量池的回收和对类型的卸载。 当办法去无奈满足内存调配需要时将抛出OOM异样 2.6 运行时常量池(Runtime Constant Pool)是办法区的一部分。Class文件中除了有类的范本、字段、办法、接口信息等、还有一项是常量常量池(Constant Pool Table),用于寄存编译期生成的各种字面量和符号援用。 运行时常量池绝对于Class文件常量池的另外一个重要特色是具备动静个性,Java语言并不要求常量肯定只有编译期能力缠身,也就是并非预置入Class文件中常量池的内容能力进入办法区是进入常量池。运行期间可能将新的常量放入池中。 当常量池无奈再申请到内存是会抛出OutOfMemoryError 2.7 间接内存间接内存(Direct memory) 并不是虚拟机运行时数据区的一部分,也不是Java虚拟机标准中定义的区域。然而这部分内存被间接被频繁应用,也可能导致OutOfMemoryError 在JDK1.4中新退出了NIO类,引入了一种基于通道(Channel)与缓冲区(BUffer)的I/O形式,它能够应用Natice函数间接调配堆外内存,而后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的援用进行操作。这样能在一些场景中进步性能,因为防止了Java堆和Native对中的来回复制数据。 3 HotSpot虚拟机探秘3.1 对象的创立虚拟机遇到一条new指令时 查看这个指令的参数是否能在常量池中定位一个类的符号援用,并且查看这个符号援用代表的类是否曾经被加载、解析和初始化过。如果没有,则必须执行相应的类加载过程虚拟机将为新生对象分配内存。对象所需的大小在类加载实现后便能够确定。对于频繁创建对象的状况,并发时,并不是线程平安的。可能呈现正在给对象A分配内存,还没来得及批改,对象B又同时应用了原来的指针来分配内存。解决这个问题有两种计划1、对于分配内存空间的动作同步解决——实际上,虚拟机采纳CAS配上失败重试保障更新操作的原子性2、另一种是把内存调配的动作依照线程划分在不同的空间之中进行,没喝线程在Java堆中事后调配一小块内存,成为本地线程调配缓冲(Thread Local Allocation Buffer, TLAB)能够通过 -XX:+/-UseTLAB参数来设定内存调配完后,虚拟机须要将调配到的内存空间都初始化为零值(不包含对象头)虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例、如何能力找到类的元数据信息、对象的哈希码、对象在GC分代年龄等信息。这些信息寄存在对象头中。依据虚拟机以后的运行状态不同,如是否应用偏差锁等,对象头会有不同的设置形式下面的工作都实现后,从虚拟机的视角来看,一个新的对象曾经缠身了, 然而从Java程序的角度来看,对象的创立才刚刚开始——<Init>办法还没有执行,所有的字段都还是零。所以,一般来说(由字节码中是否追随有invokespecial指令所决定),执行new指令之后会接着执行<init>办法,把对象进行初始化。这样才真正可用的对象才齐全产生进去。3.2 对象的内存布局hotspot虚拟机中,对象在内存中存储的布局能够分为三块区域: 对象头(header)实例数据(Instance data)对齐填充(Padding)对象头hotspot虚拟机的对象头包含两局部 第一局部用于存储对象本身的运行时数据,如Hash码,GC年龄分代,锁状态标记、线程持有的锁、偏差线程ID、偏差工夫戳等这部分在32位和64位虚拟机中别离占32bit和64bit(Mark word)对象头信息是与对象本身定义的数据无关的额定存储老本。Mark word 被设计成一个非固定的数据结构以便在极小的空间存储尽量多的信息,它会依据对象的状态复用本人的存储空间。 ...

October 18, 2020 · 3 min · jiezi

关于jvm:一次年轻代GC长暂停问题的解决与思考

规定引擎零碎,在每次发版启动会手动预热,预热实现当流量切进来之后会偶发的呈现一次长达1-2秒的年老代GC(流量并不大,并且LB下的每一台服务都会呈现该状况) 在这次长暂停之后,每一次的年老代GC暂停工夫又都复原在20-100ms以内 2s尽管看起来不长,然而比照规定引擎每次10ms左右的响应工夫来说,还是不能够承受的;并且因为该规定引擎响应超时,还会导致出单超时失败 问题剖析在剖析该零碎GC日志后发现,2s暂停产生在Young GC阶段,而且每次产生长暂停的Young GC都会随同着新生代对象的降职(Promotion) 外围JVM参数(Oracle JDK7) -Xms10G -Xmx10G -XX:NewSize=4G -XX:PermSize=1g -XX:MaxPermSize=4g -XX:+UseConcMarkSweepGC启动后第一次年老代GC日志 2020-04-23T16:28:31.108+0800: [GC2020-04-23T16:28:31.108+0800: [ParNew2020-04-23T16:28:31.229+0800: [SoftReference, 0 refs, 0.0000950 secs]2020-04-23T16:28:31.229+0800: [WeakReference, 1156 refs, 0.0001040 secs]2020-04-23T16:28:31.229+0800: [FinalReference, 10410 refs, 0.0103720 secs]2020-04-23T16:28:31.240+0800: [PhantomReference, 286 refs, 2 refs, 0.0129420 secs]2020-04-23T16:28:31.253+0800: [JNI Weak Reference, 0.0000000 secs]Desired survivor size 214728704 bytes, new threshold 1 (max 15)- age 1: 315529928 bytes, 315529928 total- age 2: 40956656 bytes, 356486584 total- age 3: 8408040 bytes, 364894624 total: 3544342K->374555K(3774912K), 0.1444710 secs] 3544342K->374555K(10066368K), 0.1446290 secs] [Times: user=1.46 sys=0.09, real=0.15 secs]长暂停年老代GC日志 ...

October 17, 2020 · 3 min · jiezi

关于jvm:Java垃圾回收jconsole分析

环境:jdk1.8+Mac+Idea为了便于察看咱们设置了虚拟机的参数VM oprions,-Xms10m -Xmx10m 代码案例1:新建了一个数组,向外面增加100个OutOfMemorypackage com.rumenz;import java.util.ArrayList;import java.util.List;public class OutOfMemory { public byte []one=new byte[128*1024]; public static void main(String[] args) throws InterruptedException { Thread.sleep(5000); //延时5秒,不便咱们关上`jconsole` append(100); } private static void append(int n) throws InterruptedException { List<OutOfMemory> list=new ArrayList<>(); for (int i = 0; i < n; i++) { Thread.sleep(3000); //拖慢增加速度,不便咱们察看 list.add(new OutOfMemory()); } }}运行程序后迅速关上jconsole,并找到本人编写的类,点击进入,抉择不平安链接 > jconsole 因为咱们应用的是成员变量,所以垃圾回收器统一不能回收内存,所以整个堆的内存趋势是一路上涨. 代码案例2:package com.rumenz;import java.util.ArrayList;import java.util.List;public class OutOfMemory { public OutOfMemory() { byte []one=new byte[128*1024]; } public static void main(String[] args) throws InterruptedException { Thread.sleep(5000); append(100); } private static void append(int n) throws InterruptedException { List<OutOfMemory> list=new ArrayList<>(); for (int i = 0; i < n; i++) { Thread.sleep(3000); list.add(new OutOfMemory()); } }}与下面代码的区别咱们one变量有成员变量变成了局部变量. 局部变量在栈上分配内存,当办法完结,栈空间隐没,栈上的变量或者援用地址将生效,本案例中one对象是调配在堆内存上,栈空间的隐没导致one对象无奈被应用到,随后就会被垃圾回收掉. 所以本案例的堆内存变量将呈现出折线的成果. ...

October 16, 2020 · 1 min · jiezi

关于jvm:05堆内存分代

堆内存详解了解一下,为什么要对堆内存分代?当然,不分代也能实现它所做的事件,之所以分代,其实是为了优化GC性能。 如果没有分代,那么所有的对象都会放在一块内存区域中,GC的时候寻找垃圾对象,就须要对整个内存区域进行扫描,这样会很大水平上影响GC效率,在Java中,很多对象都是 “朝生夕死” 的,如果把内存空间划分区域的话,将新创建的对象放到某个区域中,GC的时候优先回收这部分 “朝生夕死” 的对象,这样就会腾出很大的空间来。 所以就对堆内存进行了分代,上面这个图就是堆内存的分布图 堆内存被划分为两块,别离是年老代和老年代。 年老代年老代其实分为两局部,别离是1个Eden区和2个Survivor区(别离叫from和to),默认比例是8:1:1,个别状况下,新创建的对象根本都会放到Eden区,(除非一些特地大的对象会间接放到老年代),当Eden没有足够的空间的时候,就会触发jvm发动一次Minor GC,如果对象通过一次Minor GC还存活,并且又能被Survivor空间承受,那么将被挪动到Survivor空间当中,对象在Survivor区中每熬过一次Minor GC,年龄就会减少一岁,当它的年龄减少到肯定水平(默认15岁)时,就会被移到老年代中,当然降职老年代的年龄是能够设置的。 复制整顿算法因为年老代中的对象根本都是朝生夕死的(80%以上),所以在年老代的垃圾回收算法应用的是复制整顿算法,复制整顿算法的根本思维就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块下面。 长处:不会产生内存碎片毛病:会开拓新的空间,也就是To survivor,用来保留有用对象复制对象会破费一些工夫在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区的“To”是空的。紧接着进行GC,通过一轮Minor GC后,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会依据他们的年龄值来决定去向。年龄达到肯定值(年龄阈值,能够通过-XX:MaxTenuringThreshold来设置)的对象会被挪动到年轻代中,没有达到阈值的对象会被复制到“To”区域。通过这次GC后,Eden区和From区曾经被清空。这个时候,“From”和“To”会替换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保障名为To的Survivor区域是空的。Minor GC会始终反复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象挪动到年轻代中。 老年代当年老带随着一直地Minor GC ,from survivor中的对象会一直成长,当from survivor中的对象成长大15岁的时候,就会进入老年代,随着Minor GC的继续进行,老年代中对象也会持续增长,最终老年代的空间也会不够用,此时就会执行老年代的GC-->Major GC。Major GC应用的算法是标记革除算法或者标记-压缩算法。 标记革除步骤 【1】首先会去根对象的汇合中进行遍历,发现对象还存在援用,就是存活的对象并打上一个存活的标记【2】再去根对象汇合进行二次遍历,将没有被打上标记的对象革除掉。优缺点 长处:可能进入老年代的对象,个别都绝对稳固,被回收的数量较少,缩小对磁盘的清理,如果采纳复制整顿算法,被复制的对象会有很多。毛病:尽管垃圾失去了回收,然而回收当前,堆内存中呈现了不间断的现状---内存碎片,导致大对象无奈创立标记压缩和标记革除算法基本相同,惟一不同的就是,在革除实现之后,会把存活的对象向内存的一边进行压缩,这样就能够解决内存碎片问题。上篇文章曾经具体介绍过了,这里就不再赘述了。 堆内存常见的参数配置【1】-XX:NewSize和-XX:MaxNewSize用于设置年老代的大小,倡议设为整个堆大小的1/3或者1/4,两个值设为一样大。【2】-XX:SurvivorRatio用于设置Eden和其中一个Survivor的比值,这个值也比拟重要。【3】-XX:+PrintTenuringDistribution这个参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。【4】-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold用于设置降职到老年代的对象年龄的最小值和最大值,每个对象在保持过一次Minor GC之后,年龄就加1。

October 16, 2020 · 1 min · jiezi

关于jvm:初识JVM的组成结构

在初识jdk配置的时候,肯定有听到过jre,jdk和jvm这么三个名词,它们别离是什么呢,其实简略来说: jre、jdk、jvm之间的关系JDK是Java程序员罕用的开发包、目标就是用来编译和调试Java程序的。JRE是指Java运行环境,也就是咱们的写好的程序必须在JRE才可能运行。JVM是Java Virtual Machine(Java虚拟机)的缩写,是指负责将字节码解释成为特定的机器码进行运行,值得注意的是在运行过程中,Java源程序须要通过编译器编译为.class文件,否则JVM不意识。JVM组成构造 类加载子系统负责从文件系统或是从网络中加载class信息,加载的信息寄存在一个称之为办法区的内存空间 办法区用于寄存类的信息、常量信息、常量池信息、包含字符串字面量和数字常量。咱们罕用的反射就是从这个办法区里读取的类信息。 Java堆堆空间是jvm启动的时候创立进去的一块内存区域,简直所有的对象实例都放在这个空间里。这个区域被划分为年老代和老年代的,咱们常常接触的GC垃圾回收机制,就是次要回收堆空间的垃圾数据。堆空间里的数据,是被所有线程所共享的,所以会存在线程平安问题,所以那些锁就是为了解决堆空间数线程平安问题而生的。 间接内存间接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机标准中定义的内存区域,但这部分也是被频繁的读写应用,也可能会导致OutOfMemoryError异样的呈现。 Java的 NIO中的allocateDirect办法是能够间接应用间接内存的,能显著的进步读写的速度。 Java栈一堆一栈,所有线程共享堆空间里的数据,然而栈空间是每个线程独有的,相互间接不能拜访。栈空间是线程创立的时候所创立出的一份内存空间,栈里次要保留一些局部变量、办法参数、Java办法调用,返回值等信息。 本地办法栈本地办法栈和Java栈不同之处在于,能够间接调用Java本地办法,即JDK中用native润饰的办法。 垃圾收集零碎GC垃圾回收,是一个十分重要的知识点,保障咱们程序可能有足够的内存空间运行,回收掉内存中曾经有效的数据,其实能够了解成咱们日常生活中的垃圾回收。回收算法个别有标记革除算法,复制算法,标记整顿算法等。 PC寄存器它是每个线程公有的空间,JVM会为每个线程创立独自的PC寄存器,在任意时刻,一个Java线程总是在执行一个办法,这个办法被称为以后办法,如果以后办法不是本地办法,PC寄存器会执行以后正在被执行的指令,如果是本地办法,则PC寄存器值为undefined,寄存器寄存如以后环境指针、程序计数器、操作栈指针、计算的变量指针等信息。 执行引擎是jvm十分外围的组件,它负责执行jvm的字节码,个别先会编译成机器码后执行。本篇文章带着大家宏观地意识了一下JVM组成构造

October 16, 2020 · 1 min · jiezi

关于jvm:建议收藏2020阿里面试题JVMSpring-Cloud微服务上

前言对于大厂面试,我想要强调的一点就是心态真的很重要,是决定你在面试过程中施展的要害,若不能失常施展,很可能就因为一个小失误与offer失之交臂,所以肯定要器重起来。另外揭示一点,充沛温习,是打消你缓和的心理状态的要害,但你温习充沛了,天然面试过程中就要有底气得多。 JVM面试题java中会存在内存透露吗,请简略形容。会。本人实现堆载的数据结构时有可能会呈现内存泄露,可参看effective java. 64 位 JVM 中,int 的长度是少数?Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的 Java 虚拟机中,int 类型的长度是雷同的。 Serial 与 Parallel GC 之间的不同之处?Serial 与 Parallel 在 GC 执行的时候都会引起 stop-the-world。它们之间次要不同 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,而parallel 收集器应用多个 GC 线程来执行。 32 位和 64 位的 JVM,int 类型变量的长度是少数?32 位和 64 位的 JVM 中,int 类型变量的长度是雷同的,都是 32 位或者 4个字节。 Java 中 WeakReference 与 SoftReference 的区别?尽管 WeakReference 与 SoftReference 都有利于进步 GC 和 内存的效率,然而 WeakReference ,一旦失去最初一个强援用,就会被 GC回收,而软援用尽管不能阻止被回收,然而能够提早到 JVM 内存不足的时候。 ...

October 14, 2020 · 2 min · jiezi

关于jvm:JVM-参数

1. JVM参数分类jvm 参数可分为三类: 规范参数:以 "-" 结尾的参数非标准参数:以 "-X" 结尾的参数不稳固参数:以"-XX" 结尾的参数2. 规范参数(-)规范参数是指在各个JVM版本中根本放弃不变,绝对比较稳定。 规范参数对立都是以 "-" 结尾,如: java -classpath E:/code -Dprofile=dev HelloWorld tom jack留神:其中HelloWorld 是被运行的 HelloWorld.class。HelloWorld 之前就是设置的JVM规范参数(-classpath、-D),HelloWorld 之后的参数(tom、jack)是用来传给 main(String[] args) 办法的args数组变量的,两者地位不要放错查看所有规范参数: 关上一个命令终端,执行 "java -help",就能够展现所有的JVM规范参数 C:\Users\taichangwei>java -helpPicked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8用法: java [-options] class [args...] (执行类) 或 java [-options] -jar jarfile [args...] (执行 jar 文件)其中选项包含: -d32 应用 32 位数据模型 (如果可用) -d64 应用 64 位数据模型 (如果可用) -server 抉择 "server" VM 默认 VM 是 server. -cp <目录和 zip/jar 文件的类搜寻门路> -classpath <目录和 zip/jar 文件的类搜寻门路> 用 ; 分隔的目录, JAR 档案 和 ZIP 档案列表, 用于搜寻类文件。 -D<名称>=<值> 设置零碎属性 -verbose:[class|gc|jni] 启用具体输入 -version 输入产品版本并退出 -version:<值> 正告: 此性能已过期, 将在 将来发行版中删除。 须要指定的版本能力运行 -showversion 输入产品版本并持续 -jre-restrict-search | -no-jre-restrict-search 正告: 此性能已过期, 将在 将来发行版中删除。 在版本搜寻中包含/排除用户专用 JRE -? -help 输入此帮忙音讯 -X 输入非标准选项的帮忙 -ea[:<packagename>...|:<classname>] -enableassertions[:<packagename>...|:<classname>] 按指定的粒度启用断言 -da[:<packagename>...|:<classname>] -disableassertions[:<packagename>...|:<classname>] 禁用具备指定粒度的断言 -esa | -enablesystemassertions 启用零碎断言 -dsa | -disablesystemassertions 禁用零碎断言 -agentlib:<libname>[=<选项>] 加载本机代理库 <libname>, 例如 -agentlib:hprof 另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help -agentpath:<pathname>[=<选项>] 按残缺路径名加载本机代理库 -javaagent:<jarpath>[=<选项>] 加载 Java 编程语言代理, 请参阅 java.lang.instrument -splash:<imagepath> 应用指定的图像显示启动屏幕无关详细信息, 请参阅 http://www.oracle.com/technetwork/java/javase/documentation/index.html。2. 非标准参数(-X)非标准参数示意不保障所有VM实现都反对这些参数,在未来的JVM版本中可能会产生扭转 ...

October 12, 2020 · 3 min · jiezi

关于jvm:JVM笔记二-垃圾回收

1. 如何判断对象能够垃圾回收1.1 援用计数法统计对象被援用的个数,当援用个数为零,即没有中央在应用该对象时,则能够进行垃圾回收。 弊病:当两个对象循环援用时,援用计数都是1,导致两个对象都无奈开释。比方对象A中援用了对象B,对象B援用计数加 1,同时对象B中又援用了对象A,对象A援用计数也加 1,导致这两个对象永远不会被回收。 1.2 可达性分析法JVM中的垃圾回收器通过可达性剖析来摸索所有存活的对象,即扫描堆中的对象,看是否沿着GC Root对象为终点的援用链找到该对象,如果找不到,则示意能够回收。 能够作为GC Root 的对象: 虚拟机栈(栈帧中的本地变量表)中援用的对象。 办法区中类动态属性援用的对象办法区中常量援用的对象本地办法栈中JNI(即个别说的Native办法)援用的对象1.2.2 五种援用类型对于不同的援用类型,垃圾回收时有着不同的解决。java中援用类型个别分为强援用、软援用、弱援用、虚援用、终结器援用。上面别离介绍这五种援用类型。 --- 援用队列 作用:用于回收援用本身。 当 软援用、弱援用、虚援用、终结器援用所关联的对象被回收时,援用本身能够进入援用队列,而后ReferenceHandler线程会对援用队列中的援用进行对立回收解决。 --- 强援用 强援用就是常常应用的援用类型,只有所有 GC Roots 对象都不通过【强援用】援用该对象,该对象能力被垃圾回收 。个别通过赋值null使强援用的对象被回收。如下: //obj 为强援用Object obj = new Object();//obj 赋值null后,通过obj(GC Root对象)援用就找不到方才创立的对象了,随后就会被垃圾回收obj = null;--- 软援用 (SoftReference) 软援用对应的java类是 java.lang.ref.SoftReference<T> 。当对象仅有软援用可达到,在垃圾回收后,内存仍有余时会再次登程垃圾回收,回收该对象,能够配合援用队列来开释软援用本身 。比方上图中的 A2 对象,如果断开B对象的强援用,只有软援用指向A2,垃圾回收时如果内存不足,A2对象也会被回收,而后软援用进入援用队列。 Object obj = new Object();//softRef 为软援用//另一个构造方法 SoftReference(T referent, ReferenceQueue<? super T> q),可传入援用队列来回收援用本身所占内存SoftReference<Object> softRef= new SoftReference<>(obj);//如果内存短缺,get()返回obj对象;如果内存不足,GC时会回收softRef所关联的obj对象,此时get()返回nullsoftRef.get();软援用的在理论利用中,个别是为了防止内存溢出的产生。如果有一批图片资源,须要从磁盘读取并缓存到内存中不便下次读取,而这部分缓存并不是必须的,你不心愿这部分缓存太大导致而内存溢出,那么你就能够思考应用软援用。如下代码,堆内存只有20m,须要读取5张 4m 的图片存入一个list中,别离演示应用强援用与软援用: import java.io.IOException;import java.lang.ref.SoftReference;import java.util.ArrayList;import java.util.List;/** * 演示软援用 * -Xmx20m -XX:+PrintGCDetails -verbose:gc */public class GcDemo01 { private static final int _4MB = 4 * 1024 * 1024; public static void main(String[] args) throws IOException { //strong(); soft(); } //应用强援用导致堆内存溢出 java.lang.OutOfMemoryError: Java heap space public static void strong(){ List<byte[]> list = new ArrayList<>(); for (int i = 0; i < 5; i++) { list.add(new byte[_4MB]); } } //应用软援用,当内存不足垃圾回收时list中个别软援用对象所援用的byte[]对象会被回收,所以ref.get()可能返回null public static void soft() { // list --> SoftReference --> byte[] List<SoftReference<byte[]>> list = new ArrayList<>(); for (int i = 0; i < 5; i++) { SoftReference<byte[]> softRef = new SoftReference<>(new byte[_4MB]); System.out.println(softRef.get()); list.add(softRef); System.out.println(list.size()); } System.out.println("循环完结:" + list.size()); for (SoftReference<byte[]> ref : list) { System.out.println(ref.get()); } }}上诉代码应用软援用形式执行,打印日志如下: ...

October 12, 2020 · 4 min · jiezi

关于jvm:JVM学习本地方法栈堆

一、本地办法栈1.1 本地办法接口(1)什么是本地办法简略地讲,一个Native Method就是一个Java调用非Java代码的接囗。该办法的实现由非Java语言实现,比方C。这个特色并非Java所特有,很多其它的编程语言都有这一机制,比方在C++中,你能够用extern "C" 告知C++编译器去调用一个C的函数。 "A native method is a Java method whose implementation is provided by non-java code." 在定义一个native method时,并不提供实现体(有些像定义一个Java interface),因为其实现体是由非java语言在里面实现的。 例如java.lang.Object中的public final native Class<?> getClass()办法;又如java.lang.Thread中的private native void start0()办法... ... 本地接口的作用是交融不同的编程语言为Java所用,它的初衷是交融C/C++程序。 Tips:标识符native能够与其它java标识符连用,abstract除外。(2)为什么应用本地办法与Java环境的交互 有时Java利用须要与Java里面的环境交互,这是本地办法存在的次要起因。你能够想想Java须要与一些底层零碎,如操作系统或某些硬件替换信息时的状况。本地办法正是这样一种交换机制:它为咱们提供了一个十分简洁的接口,而且咱们无需去理解Java利用之外的繁琐的细节。 与操作系统的交互 JVM反对着Java语言自身和运行时库,它是Java程序赖以生存的平台,它由一个解释器(解释字节码)和一些连贯到本地代码的库组成。然而不管怎样,它毕竟不是一个残缺的零碎,它常常依赖于一底层零碎的反对。这些底层零碎经常是弱小的操作系统。通过应用本地办法,咱们得以用Java实现了jre的与底层零碎的交互,甚至JVM的一些局部就是用C写的。还有,如果咱们要应用一些Java语言自身没有提供封装的操作系统的个性时,咱们也须要应用本地办法。 Sun's Java Sun的解释器是用C实现的,这使得它能像一些一般的C一样与内部交互。jre大部分是用Java实现的,它也通过一些本地办法与外界交互。例如:类java.lang.Thread的setpriority()办法是用Java实现的,然而它实现调用的是该类里的本地办法setpriority()。这个本地办法是用C实现的,并被植入JVM外部,在Windows 95的平台上,这个本地办法最终将调用Win32 setpriority() ApI。这是一个本地办法的具体实现由JVM间接提供,更多的状况是本地办法由内部的动态链接库(external dynamic link library)提供,而后被JVw调用。 现状 目前这类办法应用的越来越少了,除非是与硬件无关的利用,比方通过Java程序驱动打印机或者Java系统管理生产设施,在企业级利用中曾经比拟少见。因为当初的异构畛域间的通信很发达,比方能够应用Socket通信,也能够应用Web Service等等,不多做介绍。 1.2 本地办法栈Java虚拟机栈于治理Java办法的调用,而本地办法栈(Native Method Stack)用于治理本地办法的调用。 本地办法栈,也是线程公有的。 容许被实现成固定或者是可动静扩大的内存大小。(在内存溢出方面是雷同的) 如果线程申请调配的栈容量超过本地办法栈容许的最大容量,Java虚拟机将会抛出一个stackoverflowError 异样。如果本地办法栈能够动静扩大,并且在尝试扩大的时候无奈申请到足够的内存,或者在创立新的线程时没有足够的内存去创立对应的本地办法栈,那么Java虚拟机将会抛出一个outofMemoryError异样。本地办法是应用C语言实现的。 它的具体做法是Native Method Stack中注销native办法,在Execution Engine 执行时加载本地办法库。 当某个线程调用一个本地办法时,它就进入了一个全新的并且不再受虚拟机限度的世界。它和虚拟机领有同样的权限。 本地办法能够通过本地办法接口来拜访虚拟机外部的运行时数据区。它甚至能够间接应用本地处理器中的寄存器间接从本地内存的堆中调配任意数量的内存。并不是所有的JVM都反对本地办法。因为Java虚拟机标准并没有明确要求本地办法栈的应用语言、具体实现形式、数据结构等。如果JVM产品不打算反对native办法,也能够无需实现本地办法栈。 在Hotspot JVM中,间接将本地办法栈和虚拟机栈合二为一。 ...

October 10, 2020 · 5 min · jiezi

关于jvm:JVM笔记一-内存结构

1. 什么是 JVM?1.1 定义JVM,即Java Virtual Machine,JAVA程序的运行环境(JAVA二进制字节码的运行环境) 1.2 益处一次编写,到处运行的基石主动内存治理,垃圾回收性能数据下标越界查看多态1.3 JVM、JRE、JDK 比拟 1.4 常见的 JVMJVM 只是套标准,基于这套标准的实现常见如下几种: 2. 内存构造2.1 整体架构JVM 内存构造次要蕴含:程序计数器、虚拟机栈、本地办法栈、堆、办法区,其中前三者是线程公有的,后两者是线程共享的。整体架构如下图: 2.2 程序计数器2.2.1 作用用于保留 JVM 中下一条要执行的指令的地址。 2.2.2 特点线程公有 CPU会为每个线程调配工夫片,当以后线程的工夫片应用完当前,CPU就会去执行另一个线程的代码。程序计数器是每个线程所公有的,当另一个线程的工夫片用完,又返回来执行以后线程的代码时,通过程序计数器能够晓得应该执行哪一条指令不会存在内存溢出。 因为永远只存储一个指令地址,JVM标准中惟一一个不存在OutOfMemoryError的区域2.3 虚拟机栈2.3.1 定义每个线程运行须要的内存空间,称为虚拟机栈。每个栈由多个栈帧组成,对应着每次调用办法时所占用的内存。每个线程只能有一个流动栈帧,对应着以后正在执行的办法。 应用 IDE 调试演示,从控制台能够看到办法进入虚拟机栈后的样子,如下: 2.3.2 特点线程公有的存在栈内存溢出2.3.3 问题辨析垃圾回收是否波及栈内存? 不须要。因为虚拟机栈中是由一个个栈帧组成的,在办法执行结束后,对应的栈帧就会被弹出栈,内存会主动开释。所以无需通过垃圾回收机制去回收栈内存栈内存的调配越大越好吗? 不是。因为物理内存是肯定的,栈内存越大,能够反对更多的递归调用,然而可执行的线程数就会越少办法内的局部变量是否是线程平安的? 如果办法内局部变量没有逃离办法的作用范畴,则是线程平安的如果局部变量援用了对象,并逃离了办法的作用范畴,则须要思考线程平安问题代码示例: //是线程平安的,因为局部变量sb只在m1()办法内应用 public static void m1() { StringBuilder sb = new StringBuilder(); sb.append(1); sb.append(2); sb.append(3); System.out.println(sb.toString()); } //不是线程平安的,因为局部变量sb是作为参数传过来的,m2()执行的同时可能会被其余线程批改 public static void m2(StringBuilder sb) { sb.append(1); sb.append(2); sb.append(3); System.out.println(sb.toString()); } //不是线程平安的,因为局部变量sb是作为返回值,可能会被多个其余线程拿到并同时批改 public static StringBuilder m3() { StringBuilder sb = new StringBuilder(); sb.append(1); sb.append(2); sb.append(3); return sb; }2.3.4 栈内存溢出Java.lang.stackOverflowError 栈内存溢出 ...

October 7, 2020 · 7 min · jiezi

关于jvm:深入理解java虚拟机读书笔记

java的长处:解脱了硬件平台解放,实现了一次编写,到处运行的现实提供了一个绝对平安的内存治理和拜访机制,防止了绝大多数的内存透露和指针越界实现了热点代码检测和运行时编译及优化,使得java利用能随着运行工夫的减少而取得更高的性能有一套欠缺的利用程序接口,还有有数商业机构和开源社区的第三方类库第一局部Java技术体系java程设计语言各种硬件平台的java虚拟机Class文件格式java api类库第三方java类库JDK : java程序设计语言,java虚拟机和java api。 是反对java程序开发的最小环境。JRE : 把java api类库重的 java SE api子集和java虚拟机这两局部统称为jre。 Jre 是反对java程序经营的规范环境 第二局部 主动内存治理 2 运行时数据区域2.1 程序计数器能够看作是以后线程所执行的字节码行号指示器 如果线程正在执行的是一个java办法,这个计数器记录的是正在执行的虚拟机字节码的指令地址;如果正在执行的是Native办法,这个计数器值为空(Undefined)。此内存区域是惟一一个在java虚拟机标准中没有规定任何OOM状况的区域 2.2虚拟机栈线程公有的,生命周期与线程雷同。虚拟机栈形容的是java办法执行的内存模型:每个办法在执行时都会创立一个栈帧。用于存储局部变量表、操作数栈、动静链接、办法进口等信息。 局部变量表寄存了编译器可知的各种根本类型(boolean、byte、char、short、int、float、long、double)对象援用(refrence类型,他不等同于对象自身,可能是一个执行对象起始地址的援用指针,也可能是只想一个代表对象的句柄或其余与此对象相干的地位)和returnAddress类型(指向了一条字节码指令的地址) 局部变量表所须要的内存空间在编译期间实现调配,当进入一个办法时,这个办法须要在帧中调配多大的局部变量空间是齐全确定的。 2.3本地办法栈本地办法栈与虚拟机栈施展的作用相似,不过虚拟机栈为虚拟机执行java办法(也就是字节码)服务,本地办法栈则为虚拟机应用到的Native办法服务。在虚拟机标准中办法应用的语言、应用的形式与数据结构并没有强制规定,因而具体的虚拟机能够自在实现。本地办法栈与虚拟机一样也会跑出StackOverflowError 和 OutOfMemoryError 2.3java 堆java堆是被虽有线程共享的一块内存区域。此区域的惟一目标就是寄存对象实例。简直所有的对象实例都在这里分配内存。 所有的对象实力以及数组都要在堆上调配,然而随着JIT编译器的倒退和陶艺剖析技术逐步成熟,栈上调配、标亮替换优化技术将会导致一些奥妙的变动,所有的对象都调配在堆上也慢慢变得不那么相对了。因为当初的收集器根本都采纳粉黛手机算法,所以Java堆还能够细分为:新生代和老年代。再粗疏一点: EdenFrom SurvivorTo SurvivorOldPermanent(对于HotSpot)如果在堆中没有内存实现实例调配,并且堆也无奈再扩大时,将会抛出OutOfMemoryError异样 2.5 办法区(Method Area) 与堆一样,是各个线程共享的内存区域。用于存储曾经被虚拟机记录的类信息、常量、动态变量、即时编译器编译后的代码等数据。尽管Java虚拟机标准把办法区形容为堆的一个逻辑,然而它却有一个别名叫做Non-heap,目标应该是与Java堆辨别开 仅对于Hotspot虚拟机来说,存在永恒代这个概念。仅仅是因为HotSpot虚拟机设计团队抉择把GC分代手机扩大至办法去,或者应用永恒代实现办法区而已。对于其余虚拟机则不存在永恒代。 对这一区域的内存回收次要是针对常量池的回收和对类型的卸载。 当办法去无奈满足内存调配需要时将抛出OOM异样 2.6 运行时常量池(Runtime Constant Pool)是办法区的一部分。Class文件中除了有类的范本、字段、办法、接口信息等、还有一项是常量常量池(Constant Pool Table),用于寄存编译期生成的各种字面量和符号援用。 运行时常量池绝对于Class文件常量池的另外一个重要特色是具备动静个性,Java语言并不要求常量肯定只有编译期能力缠身,也就是并非预置入Class文件中常量池的内容能力进入办法区是进入常量池。运行期间可能将新的常量放入池中。 当常量池无奈再申请到内存是会抛出OutOfMemoryError 2.7 间接内存间接内存(Direct memory) 并不是虚拟机运行时数据区的一部分,也不是Java虚拟机标准中定义的区域。然而这部分内存被间接被频繁应用,也可能导致OutOfMemoryError 在JDK1.4中新退出了NIO类,引入了一种基于通道(Channel)与缓冲区(BUffer)的I/O形式,它能够应用Natice函数间接调配堆外内存,而后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的援用进行操作。这样能在一些场景中进步性能,因为防止了Java堆和Native对中的来回复制数据。 3 HotSpot虚拟机探秘3.1 对象的创立虚拟机遇到一条new指令时 查看这个指令的参数是否能在常量池中定位一个类的符号援用,并且查看这个符号援用代表的类是否曾经被加载、解析和初始化过。如果没有,则必须执行相应的类加载过程虚拟机将为新生对象分配内存。对象所需的大小在类加载实现后便能够确定。对于频繁创建对象的状况,并发时,并不是线程平安的。可能呈现正在给对象A分配内存,还没来得及批改,对象B又同时应用了原来的指针来分配内存。解决这个问题有两种计划1、对于分配内存空间的动作同步解决——实际上,虚拟机采纳CAS配上失败重试保障更新操作的原子性2、另一种是把内存调配的动作依照线程划分在不同的空间之中进行,没喝线程在Java堆中事后调配一小块内存,成为本地线程调配缓冲(Thread Local Allocation Buffer, TLAB)能够通过 -XX:+/-UseTLAB参数来设定内存调配完后,虚拟机须要将调配到的内存空间都初始化为零值(不包含对象头)虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例、如何能力找到类的元数据信息、对象的哈希码、对象在GC分代年龄等信息。这些信息寄存在对象头中。依据虚拟机以后的运行状态不同,如是否应用偏差锁等,对象头会有不同的设置形式下面的工作都实现后,从虚拟机的视角来看,一个新的对象曾经缠身了, 然而从Java程序的角度来看,对象的创立才刚刚开始——<Init>办法还没有执行,所有的字段都还是零。所以,一般来说(由字节码中是否追随有invokespecial指令所决定),执行new指令之后会接着执行<init>办法,把对象进行初始化。这样才真正可用的对象才齐全产生进去

October 6, 2020 · 1 min · jiezi

关于jvm:深入理解JVM内存模型

内存模型

September 27, 2020 · 1 min · jiezi