Jvm
关于jvm:Jvm监控工具Jvisualvm的安装配置
阐明:在linux零碎中对jvm有一套监控命令,命令的应用办法即作用本文不做赘述,可自行学习(参考地址:http://blog.csdn.net/fenglibing/article/details/6411999)。 本文重点介绍监控工具jvisualvm(另称为VisualVM)的应用。 Jvisualvm的启动1、 须要装置jdk 2、 在jdk的装置门路下的bin目录中找到jvisualvm.exe,双击即可启动 (我本地的门路为D:Program FilesJavajdk1.8.0_20binjvisualvm.exe) 启动后的界面如图 Jvisualvm监控本地资源如果java程序运行在本机,则jvisualvm启动之后,默认在“本地”一栏中能够间接看到 Jvisualvm监控近程资源应用jmx形式因为我监控的指标机应用的是tomcat,因而以tomcat为例进行阐明 Tomcat加war包的配置这种比拟场景,将服务打成war包放在tomcat中,批改tomcat的配置即可进行监控。 步骤1:在须要监控的指标机上找到tomcat的配置(linux为catalina.sh,Windows为catalina.bat,上面以linux的catalina.sh为例进行阐明) 步骤2:在catalina.sh中增加以下配置 JAVA_OPTS="-Dcom.sun.management.jmxremote.port=9998 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=172.31.7.37" 增加后的成果如图; 步骤三:增加之后重启tomcat 步骤四:在jvisualvm的“近程”栏下右键增加近程主机 步骤五:在近程主机中右键抉择增加jmx链接 步骤六:在jmx链接的配置中输出指标机ip及端口,连贯即可 步骤七:双节已增加的近程机器,即可关上次要的监控界面 ~~~~ 服务内嵌tomcat的配置这种形式在linux下也比拟常见,服务不是war包放在tomcat下,而是通过援用tomcat的jar包调用内置的tomcat来运行。这种形式通常在服务的启动脚本中增加相干监控配置,常见的启动脚本命名为start.sh、server.sh等 以基线我的项目的安全监管服务文字鉴黄接口为例: 须要监控的服务为safety-server,启动目录:safety-server/bin/ start.sh 在服务的bin目录下,编辑启动脚本:vim start.sh 操作1:执行脚本中增加配置项参数: 操作2:给配置参数设定配置信息 操作3:重启服务 Jstatd形式VisualVM在监控本地JVM的时候是很不便的。只有利用程序运行起来,咱们就能够从VisualVM外面监控进去。然而有些性能在该监控模式下会有限度 设置的步骤如下: 步骤一:在远程目标机上找到java的jdk装置门路(能够应用这个命令进行查找:find / -name java) 步骤二:在jdk的bin目录下查找是否存在jstatd小插件 查找命令:ls -l | grep jst 查找后果: 步骤三:在目录下创立安全策略文件,文件名为jstatd.all.policy (文件名能够本人取) 策略文件内容为 grant codebase "file:${java.home}/../lib/tools.jar" {permission java.security.AllPermission; }; 即如图所示: 步骤四:查看homename值,是否为指标机本人的ip ...
关于jvm:初识JVM
JVM是什么jvm和Java的关系 JVM是什么虚拟机是一种抽象化的计算机,通过在理论的计算机上仿真模仿各种计算机性能来实现的。Java虚拟机有本人欠缺的硬体架构,如处理器、堆栈、寄存器等,还具备相应的指令系统。Java虚拟机屏蔽了与具体操作系统平台相干的信息,使得Java程序只需生成在Java虚拟机上运行的指标代码(字节码),就能够在多种平台上不加批改地运行。 所谓的java虚拟机,就是一台虚构的机器。它是一款软件,用来执行一系列虚构计算机指令,大体上虚拟机能够分为零碎虚拟机和程序虚拟机。visual Box、VMare就属于零碎虚拟机。他们齐全是对物理计算机的仿真,提供一个可运行残缺操作系统的软件平台。而java虚拟机就是典型程序虚拟机,它专门为执行单个计算机程序而设计,在java虚拟机中执行的指令咱们称之为java字节码指令。 比方一台服务器上运行着两个用Java编写的程序,一个博客零碎,一个电商零碎,那么这两个Java程序、Java虚拟机、服务器之间是什么关系呢? 从上图能够看进去,Java写的博客零碎,电商零碎都是运行在jvm之上,这两个Java程序启动也对应着会启动两个JVM实例。JVM是运行在服务器的零碎之上,服务器能够是linux零碎,能够是windows零碎,能够是centos零碎,各种零碎都能够。这也就是常说的Java是一次编译到处运行。可能这么说还是不通俗易懂,至多我刚开始学Java的时候我的老师这么说我就没有了解。那么我就说说我本人工作后的了解。 咱们跑Java程序之前首先必定是装jdk,咱们在windows装jdk环境是用的windows版本的jdk,在linux上装jdk用的linux版本的jdk;jdk咱们就能够了解为由Java程序设计语言、Java虚拟机、Java类库组成(Java Development kit)。咱们写的Java程序能够在服务器上跑的前提是这个服务器装了jdk环境。也就是只有是服务器装了jdk环境,咱们用maven或者gradle或者其余工具编译打包好的Java程序jar包就能够间接运行,我不须要在Java程序外面去适配你的windos环境还是linux环境还是centos环境。 jvm和Java的关系jvm和Java是什么关系呢,jvm和Java是不是只能相互依存呢?我刚开始学Java的时候接触到jvm,我就说这么认为的,因为jvm又叫Java虚拟机,所以我认为jvm就是专门为Java设计的。Java有Java本人的标准,依据这个标准也衍生出多种不同的jdk,比方Oracle的jdk,sun的jdk(sun曾经被oracle收买)IBM的jdk;虚拟机也有虚拟机本人的标准,依据这个标准也衍生出了多种不同的虚拟机,比方:武林盟主HotSpot虚拟机,小家碧玉Mobile Embedded VM,天下第二BEA JRockit/IBM J9虚拟机等。然而2018年4月,Oracle Labs新出了一个虚拟机Graal VM,口号是:“Run Programs Faster Anywhere”与Java刚诞生时候的“Write Once,Run Anywhere”一唱一和,Graal VM是一个增强版虚拟机,能够作为“任何语言”的运行平台应用,这里“任何语言”包含了Java、Scala、Groovy、Kotlin等基于Java虚拟机之上的语言,还包含了C、C++、Rust等基于LLVM的语言,同时反对其余像JavaScript、Ruby、Python和R语言等。Graal VM能够无额定开销地混合应用这些编程语言,反对不同语言中混用对方的接口和对象,也可能反对这些语言应用曾经编写好的本地库文件。 虚拟机的介绍就先说到这,置信聪慧的你曾经对虚拟机有一个大抵的意识,下一章将虚拟机的内存构造,这也是当初Java面试的高频题,然而大多数人必定都只是理解堆,栈,程序计数器,本地办法栈,而后再理解一下堆中分了年老代,老年代这些,这也是培训机构教jvm局部的模板,然而每年培训出那么多人,你难道就不想比他们多晓得那么一点吗?各位人才应该都懂的,咱们下一章见。 各位人才,各位大佬,有不对的中央各位请不吝指教!都看到这儿了,点赞珍藏转发三连一下?祝各位早日找到女朋友。喜爱的敌人能够关注一下我的公众号敲代码的蛋蛋,一起成长,一起骚起来呀!!! 我是敲代码的蛋蛋,一个致力触摸编程门栏的老手。明天的你是否比昨天的你更优良了呢? 参考文档: <<深刻了解Java虚拟机>> ----周志明 <<百度百科>>
关于jvm:记一次堆外内存泄漏排查过程
本文波及以下内容 开启NMT查看JVM内存应用状况通过pmap命令查看过程物理内存应用状况smaps查看过程内存地址gdb命令dump内存块背景最近收到运维反馈,说有我的项目的一个节点的RSS曾经是Xmx的两倍多了,因为是ECS机器所以我的项目能够始终运行,幸好机器内存短缺,不然就可能影响到其余利用了。 排查问题通过跳板机登录到指标机器,执行top命令,再按c,看到对应的过程所占用的RES有8个多G(这里过后遗记截图了),然而实际上咱们配置的Xmx只有3G,而且程序还是失常运行的,所以不会是堆占用了这么多,于是就把问题方向指向了非堆的内存。 首先想到通过Arthas查看内存散布状况,执行dashboard命令,查看内存散布状况 发现堆和非堆的内存加起来也就2个G不到,然而这里看到的非堆只蕴含了code_cache和metaspace的局部,那有没有可能是其余非堆的局部有问题呢? NMTNMT是Native Memory Tracking的缩写,是Java7U40引入的HotSpot新个性,开启后能够通过jcmd命令来对JVM内存应用状况进行跟踪。留神,依据Java官网文档,开启NMT会有5%-10%的性能损耗。-XX:NativeMemoryTracking=[off | summary | detail] # off: 默认敞开 # summary: 只统计各个分类的内存应用状况.# detail: Collect memory usage by individual call sites.增加-XX:NativeMemoryTracking=detail命令到启动参数中,而后重启我的项目。 跑了一段时间后top看了下过程的RES,发现曾经5个多G了 执行jcmd <pid> VM.native_memory summary scale=MB 从图中能够看到堆和非堆的总应用内存(committed)也就2G多,那还有3个G的内存去哪里了呢。 pmappmap命令是Linux上用来开过程地址空间的执行pmap -x <pid> | sort -n -k3 > pmap-sorted.txt命令能够依据理论内存排序 查看pmap-sorted.txt文件,发现有大量的64M内存块难道是linux glibc 中经典的 64M 内存问题?之前看挖坑的张师傅写过一篇文章(一次 Java 过程 OOM 的排查剖析(glibc 篇))讲过这个问题,于是筹备参考一下排查思路 尝试设置环境变量MALLOC_ARENA_MAX=1,重启我的项目,跑了一段时间当前,再次执行pmap命令查看内存状况,发现并没有什么变动,看来并不是这个起因,文章前面的步骤就没有什么参考意义了。 smaps + gdb既然能够看到有这么多异样的内存块,那有没有方法晓得外面存的是什么内容呢,答案是必定的。通过一番材料查阅,发现能够通过gdb的命令把指定地址范畴的内存块dump进去。 要执行gdb的dump须要先晓得一个地址范畴,通过smaps能够输入过程应用的内存块详细信息,包含地址范畴和起源 cat /proc/<pid>/smaps > smaps.txt查看smaps.txt,找到有问题的内存块地址,比方下图中的 7fb9b0000000-7fb9b3ffe000 启动gdb ...
关于jvm:七款经典垃圾回收器
SerialSerial 是一款单线程的垃圾回收器,在进行垃圾回收的时候会产生Stop the world 。对于其余垃圾收集器而言,他的内存占用是最小的。 ParNewParNew 实际上是Serial 收集器的并行版本 Parallel Scavenge重视吞吐量的垃圾收集器。吞吐量= 运行用户代码的工夫/(垃圾回收的工夫+运行用户代码的工夫)他有两个参数用于准确管制吞吐量大小:-XX:MaxGCPauseMills:管制垃圾回收的工夫 这个值并不是越小越好,咱们晓得,内存越小,进行垃圾回收的工夫就越短。如果咱们将这个值设置地较小,那么回收的内存局部也就小。这也就导致gc 进行垃圾回收的频率也越高,这样吞吐量也就降落了。-XX:GCTimeRatio:垃圾回收工夫的比例这个值默认是99 .也就是说 垃圾回收的工夫只占1%(1/1+99) Serial Old是serial收集器的老年代版本有两个用处 在jdk5及之前的版本中与Parallel Scavenge 收集器搭配应用在cms收集器产生失败的后备预案Parallel Old是Parallel Scavenge 的老年代版本 CMS(Current Mark and Sweep)真正意义上的并发收集器,第一次实现了让_垃圾_收集线程与用户线程(基本上)同时工作整个过程能够分为四局部: 初始标记扫描和GCRoots间接关联的对象,速度很快并发标记遍历整个对象图,工夫较长从新标记标记产生变动的那一部分对象的标记记录并发分明清标记阶段断定死亡的对象毛病: Garbage First基于region的垃圾回收器
关于jvm:jvm疯狂吞占内存罪魁祸首是谁
导读 JVM是Java Virtual Machine的缩写,中文名为Java虚拟机。它是一种用于计算设施的标准,是一个虚构进去的计算机,次要通过在理论的计算机上仿真模仿各种计算机性能来实现的。在理论使用过程中,易观技术人员留神到一台开发机上各个微服务过程占用内存很高,随即便开展了考察...... 景象:前段时间发现某台开发机上各个微服务过程占用内存很高,这里记录下解决思路,仅供参考。 Centos6.10+Jdk1.8+SpringBoot1.4.4环境下各个JVM过程所占内存应用状况VIRT和RES都很高...... 以其中某个过程为例(过程启动日期为8月9日,排查时候的工夫是8月10日10:54:58,也就是说该过程运行工夫应该不会超过48小时) top命令查看该过程占用内存状况(能够看到此过程曾经占用2.7G物理内存) 为了排除掉是因为中途有压力测试的嫌疑,将此服务进行了重启,然而刚起的过程(19146), 占内存状况RES:1.8G, VIRT:33.4G … JVM过程动不动就是2G以上的内存,然而开发环境并没什么业务申请,谁是罪魁祸首 ? 解决问题之前,先温习下几个基础知识。1. 什么是RES和VIRT? RES:resident memory usage 常驻内存(1)过程以后应用的内存大小,但不包含swap out (2)蕴含其余过程的共享 (3)如果申请100m的内存,理论应用10m,它只增长10m,与VIRT相同 (4)对于库占用内存的状况,它只统计加载的库文件所占内存大小 RES = CODE + DATA VIRT:virtual memory usage(1)过程“须要的”虚拟内存大小,包含过程应用的库、代码、数据等 (2)如果过程申请100m的内存,但理论只应用了10m,那么它会增长100m,而不是理论的使用量 VIRT = SWAP + RES 2. Linux与过程内存模型 3. JVM内存模型(1.7与1.8之间的区别) 所以JVM过程内存大小大抵为: 非heap(非heap=元空间+栈内存+…)+heap+JVM过程运行所需内存+其余数据 那么会是jvm内存透露引起的吗?应用Jmap命令将整个heap dump下来,而后用jvisualvm剖析 能够看到,堆内存一切正常(dump会引起FGC,但并不影响此论断) 那么可能是SpringBoot的起因吗?为了验证此问题,通过部署零碎在开发机上起了1个没有任何业务代码的springboot过程,仅仅是引入注册核心 查看此过程内存占用状况: 显著曾经设置了Xmx为512MB,尽管Xmx不等于最终JVM所占总内存,但至多也不会偏差太多; 那么应用jmap命令查看以后jvm堆内存配置和应用状况(上面的图2是在图1现场5分钟之后截取的) **(图1) ** (图2) 所以从2次的jmap后果中,能够得出以下几个论断: 咱们的Xmx设置并没有失效,因为MaxHeapSize≠Xmx图1中jvm占用内存计算:元空间(20.79MB)+ eden(834MB)+年轻代(21MB)+线程栈(38*1024KB)+JVM过程自身运行内存+ NIO的DirectBuffer +JIT+JNI+…≈top(Res) 1.1G ...
关于jvm:JVM-类加载机制
1. JVM细节版架构图 2. 类加载器子系统的作用 类加载器子系统负责从文件系统或者网络中加载Class文件,Class文件在文件结尾有特定的文件标识。ClassLoader只负责class文件的加载,至于它是否能够运行,则由Execution Engine决定。加载的类信息寄存于一块称为办法区的内存空间。除了类的信息外,办法区中还会寄存运行时常量池信息,可能还包含字符串字面量和数字常量(这部分常量信息是Class文件中常量池局部的内存映射)3. 类的加载过程 1. 加载 loading通过一个类的全限定名获取定义此类的二进制字节流。将这个字节流所代表的动态存储构造转化为办法区的运行时存储构造。在内存中生成一个代表这个类的 java.lang.Class 对象,作为办法区这个类的各种数据的拜访入口。 2. 链接 linking1. 验证1.目标在于确保Class文件的字节流中蕴含信息合乎以后虚拟机要求,保障被加载类的正确性,不会危害虚拟机本身平安。 2.次要包含四种验证,文件格式验证,源数据验证,字节码验证,符号援用验证。 2. 筹备 prepare1.为类变量分配内存并且设置该类变量的默认初始值,即零值。2.这里不蕴含用 final 润饰的 static 变量,因为 final 在编译的时候就调配了,筹备阶段会显式初始化。3.这一阶段不会为实例变量调配初始化,类变量会调配在办法区中,而实例变量是会随着对象一起调配到java堆中。 3. 解析1.将常量池内的符号援用转换为间接援用的过程。 2.事实上,解析过程在某些状况下能够在初始化阶段之后再开始,这是为了反对 Java 的动静绑定。 3.符号援用就是一组符号来形容所援用的指标。符号援用的字面量模式明确定义在《java虚拟机标准》的class文件格式中。间接援用就是间接指向指标的指针、绝对偏移量或一个间接定位到指标的句柄。 4.解析动作次要针对类或接口、字段、类办法、接口办法、办法类型等。对应常量池中的CONSTANT_Class_info/CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。 3. 初始化初始化阶段就是执行类结构器办法<clinit>()的过程。此办法不需定义,是 javac 编译器主动收集类中的所有类变量的赋值动作和动态代码块中的语句合并而来。结构器办法中指令按语句在源文件中呈现的程序执行。<clinit>()不同于类的结构器。(关联:结构器是虚拟机视角下的<init>())若该类具备父类,JVM会保障子类的<clinit>()执行前,父类的<clinit>()曾经执行结束。虚拟机必须保障一个类的<clinit>()办法在多线程下被同步加锁,因为类只能加载一次。public class ClassInitTest{ private static int num = 1; static{ num = 2; number = 20; System.out.println(num); // System.out.println(number); // 报错:非法的前向援用 } private static int number = 10; // linking之prepare:number=0 --> initial: 20 -->10 public static void main(String[] args){ System.out.println(ClassIniTest.num); // 2 System.out.println(ClassInitTest.number); //10 } }上述代码<clinit>()办法的字节码:只收集动态变量和动态代码块中的赋值语句 ...
关于jvm:JVM-垃圾收集器与内存分配策略
1. 判断对象是否被回收1. 援用计数算法在对象中增加一个援用计数器,每当有一个中央援用它时, 计数器值加1;当援用生效时, 计数器值减1;任何时刻计数器为零的对象就是不可能再被应用的。(很难解决对象互相循环援用的问题。) 两个对象互相援用时,虚拟机也会进行回收,因而Java虚拟机并不是通过计数算法来判断对象存活的。 2. 可达性剖析算法以 GC Roots 为起始点依据援用关系进行搜寻,可达的对象都是存活的,不可达的对象可被回收。 虚拟机栈中局部变量表中援用的对象本地办法栈中 JNI(Native 办法) 中援用的对象办法区中类动态属性援用的对象办法区中的常量援用的对象3. 援用无论是通过援用计数算法判断对象的援用数量, 还是通过可达性剖析算法判断对象是否援用可达, 判断对象是否存活都和“援用相干”。 1. 强援用被强援用的对象不会被回收。 应用 new 一个新对象的形式来创立强援用。 Object obj = new Object()2. 软援用被软援用关联的对象只有在内存不够的状况下才会被回收。应用 SoftReference 类来创立软援用。 3. 弱援用被弱援用关联的对象只能存活到下一次垃圾收集产生为止。所以肯定会被回收。 应用 WeakReference 类来实现弱援用。 4. 虚援用又称为幽灵援用或者幻影援用,一个对象是否有虚援用的存在,不会对其生存工夫造成影响,也无奈通过虚援用失去一个对象。 为一个对象设置虚援用的惟一目标是能在这个对象被回收时收到一个零碎告诉。 应用 PhantomReference 来创立虚援用。 4. finalize()当一个对象可被回收时,如果须要执行该对象的 finalize() 办法,那么就有可能在该办法中让对象从新被援用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 办法自救,前面回收时不会再调用该办法。 finalize() 办法运行代价很高,不确定性大,无奈保障各个对象的调用程序,因而最好不要应用。应用 try-finally 或者其余形式能够做得更好。 5. 回收办法区因为办法区次要寄存永恒代对象,而永恒代对象的回收率比新生代低很多,所以在办法区上进行回收性价比不高。 次要是对常量池的回收和对类的卸载。 为了防止内存溢出,在大量应用反射和动静代理的场景都须要虚拟机具备类卸载性能。 类的卸载条件很多,须要同时满足以下三个条件,并且满足了条件也不肯定会被卸载: 该类所有的实例都曾经被回收,此时堆中不存在该类的任何实例。加载该类的 ClassLoader 曾经被回收。该类对应的 Class 对象没有在任何中央被援用,也就无奈在任何中央通过反射拜访该类办法。2. 垃圾收集算法1. 分代收集实践将回收对象根据其年龄(对象熬过垃圾收集过程的次数)将Java堆划分出不同的区域。个别将堆分为新生代和老年代。 新生代应用:标记 - 复制算法老年代应用:标记 - 革除 或者 标记 - 整顿 算法长处: ...
关于jvm:JVM-运行时数据区域
1. 程序计数器记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本中央法令为空)。每条线程都须要一个独立的程序计数器。 2. Java虚拟机栈与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程公有的,它的生命周期与线程雷同。 每个 Java 办法在执行的同时会创立一个栈帧用于存储局部变量表、操作数栈、常量池援用等信息。从办法调用直至执行实现的过程,对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。 局部变量表所需内存空间在编译期间实现调配,寄存了: 根本数据类型(boolean、byte、char、shrot、int、float、long、double)对象援用(指向对象起始地址的援用指针)两种异样:StackOverflowError:线程申请栈深度大于虚拟机所容许深度OutofMemoryError: 无奈申请到足够空间 3. 本地办法栈本地办法栈与 Java 虚拟机栈相似,它们之间的区别只不过是本地办法栈为本地办法服务。 4. Java 堆是被所有线程共享的一块内存区域,所有对象都在这里分配内存,是垃圾收集的次要区域("GC 堆")。异样 OutofMemoryError:Java堆中没有内存实现实例调配时抛出该异样。 5. 办法区与Java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类型信息、常量、动态变量、即时编译器编译后的代码缓存等数据。 对这块区域进行垃圾回收的次要指标是对常量池的回收和对类的卸载,然而个别比拟难实现。 异样 OutofMemoryError:办法区无奈满足新的内存调配要求时。 6. 运行时常量池运行时常量池是办法区的一部分。 Class 文件中的常量池表(寄存编译器生成的字面量和符号援用)会在类加载后被放入办法区的运行时常量池中。 动态性: 解决编译器产生常量,运行期间也能够将新的常量放入池中,例如String类的intern()办法. 异样 OutofMemoryError:收到办法区内存的限度,常量池无奈再申请到内存时抛出该异样。 7. 间接内存在 JDK 1.4 中新引入了 NIO 类,它能够应用 Native 函数库间接调配堆外内存,而后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的援用进行操作。这样能在一些场景中显著进步性能,因为防止了在堆内存(Java堆)和堆外内存(Native堆)来回拷贝数据。 异样 OutofMemoryError:本机间接内存的调配会受到本机总内存大小以及处理器寻址空间的限度。 参考资料https://github.com/CyC2018/CS...https://www.cnblogs.com/czwbi...
关于jvm:十个问题弄清JVMGC二
简介: 每个java开发同学不论是日常工作中还是面试里,都会遇到JDK、JVM和GC的问题。本文会从以下10个问题为切入点,带着大家一起全面理解一下JVM的方方面面。 每个java开发同学不论是日常工作中还是面试里,都会遇到JDK、JVM和GC的问题。本文会从以下10个问题为切入点,带着大家一起全面理解一下JVM的方方面面。 JVM、JRE和JDK的区别和分割JVM是什么?以及它的次要作用JVM的外围性能有哪些类加载机制和过程运行时数据区的逻辑构造JVM的内存模型如何确定对象是垃圾垃圾收集的算法有哪些各种问世的垃圾收集器JVM调优的参数配置上一篇文章结尾时咱们谈到,就JVM的设计规范,从应用用处角度JVM的内存大体的分为:线程公有内存区 和 线程共享内存区。 线程公有内存区在类加载器编译某个class文件时就确定了执行时须要的“程序计数器”和“虚构栈帧”等所需的空间,并且会随同着以后执行线程的产生而产生,执行线程的沦亡而沦亡,因而“线程公有内存区”并不需要思考内存治理和垃圾回收的问题。线程共享内存区在虚拟机启动时创立,被所有线程共享,是Java虚拟机所治理内存中最应该关注的和最大的一块。首先咱们来一起看一下“线程共享内存区”的内存模型是什么样的? 6、JVM的内存模型 如图所示,JVM的内存构造分为堆和非堆两大块区域。 其中“非堆”就是上篇文章咱们提到的办法区或叫元数据区,用来存储class类信息的。而“堆”是用来存储JVM各线程执行期间所创立的实例对象或数组的。堆辨别为两大块,一个是Old区,一个是Young区。Young辨别为两大块,一个是Survivor区(S0+S1),一块是Eden区S0和S1一样大,也能够叫From和To。之所以这样划分,设计者的目标无非就是为了内存治理,也就是咱们说的垃圾回收。那么什么样的对象是垃圾?垃圾回收算法有哪些?目前罕用的垃圾回收器又有哪些?这篇文章咱们一起弄清楚这些问题和知识点。 7、如何确定一个对象是垃圾?要想进行垃圾回收,得先晓得什么样的对象是垃圾。目前确认对象是否为垃圾的算法次要有两种:援用计数法和可达性分析法。 1、援用计数法:在对象中增加了一个援用计数器,当有中央援用这个对象时,援用计数器的值就加1,当援用生效的时候,援用计数器的值就减1。当援用计数器的值为0时,JVM就开始回收这个对象。对于某个对象而言,只有应用程序中持有该对象的援用,就阐明该对象不是垃圾,如果一个对象没有任何指针对其援用,它就是垃圾。这种办法尽管很简略、高效,然而JVM个别不会抉择这个办法,因为这个办法会呈现一个弊病:当对象之间互相指向时,两个对象的援用计数器的值都会加1,而因为两个对象时互相指向,所以援用不会生效,这样JVM就无奈回收。 2、可达性分析法:针对援用计数算法的弊病,JVM采纳了另一种算法,以一些"GC Roots"的对象作为起始点向下搜寻,搜寻所走过的门路称为援用链(Reference Chain),当一个对象到GC Roots没有任何援用链相连时,则证实此对象是不可用的,即能够进行垃圾回收。否则,证实这个对象有用,不是垃圾。 上图中的obj7和obj8尽管它们相互援用,但从GC Roots登程这两个对象不可达,所以会被标记为垃圾。JVM会把以下几类对象作为GC Roots: (1) 虚拟机栈(栈帧中本地变量表)中援用的对象;(2) 办法区中类动态属性援用的对象;(3) 办法区中常量援用的对象;(4) 本地办法栈中JNI(Native办法)援用的对象。注:在可达性剖析算法中不可达的对象,并不是间接被回收,这时它们处于缓刑状态,至多须要进行两次标记才会确定该对象是否被回收:第一次标记:如果对象在进行可达性剖析后发现没有与GC Roots相连接的援用链,那它将会被第一次标记; 第二次标记:第一次标记后接着会进行一次筛选,筛选的条件是此对象是否有必要执行finalize()办法(该办法可将此对象与GC Roots建立联系)。在finalize()办法中没有从新与援用链建设关联关系的,将被进行第二次标记。 第二次标记胜利的对象将真的会被回收,如果对象在finalize()办法中从新与援用链建设了关联关系,那么将会逃离本次回收,持续存活。 8、垃圾收集的算法有哪些晓得了如何JVM确定哪些对象是垃圾后,上面咱们来看一下,面对这些垃圾对象,JVM的回收算法都有哪些。 1、 标记-革除算法(Mark-Sweep)第一步“标记”,如下图所示把堆里所有的对象都扫描一遍,找出哪些是垃圾须要回收的对象,并且把它们标记进去。 第二步“革除”,把第一步标记为“UnReference Object”(无援用或不可达)的对象革除掉,开释内存空间。 这种算法的毛病次要有两点: (1) 标记和革除两个过程都比拟耗时,效率不高 (2) 革除后会产生大量不间断的内存碎片空间,碎片空间太多可能会导致当程序后续须要创立较大对象时,无奈找到足够间断的内存空间而不得不再次触发垃圾回收。 2、 标记-复制算法(Mark-Copying)将内存划分为两块区域,每次应用其中一块,当其中一块用满,触发垃圾回收的时候,将存活的对象复制到另一块下来,而后把之前应用的那一块进行格式化,一次性革除洁净。 (革除前) (革除后) “标记-复制”算法的毛病不言而喻,就是内存空间利用率低。 3、 标记-整顿算法(Mark-Compact)标记整顿算法标记过程依然与"标记-革除"算法一样,然而后续步骤不是间接对可回收对象进行清理,而是让所有存活的对象都向一端挪动,而后间接清理掉端边界以外的内存。 将所有存活的对象向一边挪动,清理掉存活边界以外的全副内存空间。 联合这三种算法咱们能够看到, “标记-复制”算法的长处是回收效率高,但空间利用率上有肯定的节约。而“标记-整顿”算法因为须要向一侧挪动等一系列操作,其效率绝对低一些,但对内存空间治理上非常优异。因而,“标记-复制”算法实用于那些生命周期短、回收频率高的内存对象,而标记-整顿”算法实用于那些生命周期长、回收频率低,但重视回收一次内存空间失去足够开释的场景。因而JVM的设计者将JVM的堆内存,分为了两大块区域Young区和Old区,Young区存储的就是那些生命周期短,应用一两次就不再应用的对象,回收一次基本上该区域十之有八的对象全副被回收清理掉,因而Young区采纳的垃圾回收算法也就是“标记-复制”算法。Old区存储的是那些生命周期长,通过屡次回收后依然存活的对象,就把它们放到Old区中,平时不再去判断这些对象的可达性,直到Old区不够用为止,再进行一次对立的回收,开释出足够的间断的内存空间。 9、各种问世的垃圾收集器鉴于Young区和Old区须要采纳不同的垃圾回收算法,因而在JVM的整个垃圾收集器的演进各个时代里,针对Young区和Old区每个时代都是不同的垃圾收集机制。从JDK1.3开始到目前,JVM垃圾收集器的演进大体分为四个时代:串行时代、并行时代、并发时代和G1时代。 1、串行时代:Serial(Young区)+ Serial Old(Old区)JDK3(1.3)的时候,大略是2000年左右,那个时代根本计算机都是单核一个CPU的,因而垃圾回收最后的设计实现也是基于单核单线程工作的。并且垃圾回收线程的执行绝对于失常业务线程执行来说还是STW(stop the world)的,应用一个CPU或者一条收集线程去实现垃圾收集工作,这个线程执行的时候其它线程须要进行。 串行收集器采纳单线程stop-the-world的形式进行收集。当内存不足时,串行GC设置进展标识,待所有线程都进入平安点(Safepoint)时,利用线程暂停,串行GC开始工作,采纳单线程形式回收空间并整顿内存。单线程也意味着复杂度更低、占用内存更少,但同时也意味着不能无效利用多核优势。因而,串行收集器特地适宜堆内存不高、单核甚至双核CPU的场合。 2、并行时代:Parallel Scavenge(Young区) + Parallel Old(Old区)并行收集器是以关注吞吐量为指标的垃圾收集器,也是server模式下的默认收集器配置,对吞吐量的关注次要体现在年老代Parallel Scavenge收集器上。 ...
关于jvm:从10个问题切入带大家了解JVM的方方面面
每个java开发同学不论是日常工作中还是面试里,都会遇到JDK、JVM和GC的问题。本文会从以下10个问题为切入点,带着大家一起全面理解一下JVM的方方面面。 JVM、JRE和JDK的区别和分割JVM是什么?以及它的次要作用JVM的外围性能有哪些类加载机制和过程运行时数据区的逻辑构造JVM的内存模型如何确定对象是垃圾垃圾收集的算法有哪些各种问世的垃圾收集器JVM调优的参数配置上一篇文章结尾时咱们谈到,就JVM的设计规范,从应用用处角度JVM的内存大体的分为:线程公有内存区 和 线程共享内存区。 线程公有内存区在类加载器编译某个class文件时就确定了执行时须要的“程序计数器”和“虚构栈帧”等所需的空间,并且会随同着以后执行线程的产生而产生,执行线程的沦亡而沦亡,因而“线程公有内存区”并不需要思考内存治理和垃圾回收的问题。线程共享内存区在虚拟机启动时创立,被所有线程共享,是Java虚拟机所治理内存中最应该关注的和最大的一块。首先咱们来一起看一下“线程共享内存区”的内存模型是什么样的? 6、JVM的内存模型 如图所示,JVM的内存构造分为堆和非堆两大块区域。 其中“非堆”就是上篇文章咱们提到的办法区或叫元数据区,用来存储class类信息的。而“堆”是用来存储JVM各线程执行期间所创立的实例对象或数组的。堆辨别为两大块,一个是Old区,一个是Young区。Young辨别为两大块,一个是Survivor区(S0+S1),一块是Eden区S0和S1一样大,也能够叫From和To。之所以这样划分,设计者的目标无非就是为了内存治理,也就是咱们说的垃圾回收。那么什么样的对象是垃圾?垃圾回收算法有哪些?目前罕用的垃圾回收器又有哪些?这篇文章咱们一起弄清楚这些问题和知识点。 7、如何确定一个对象是垃圾?要想进行垃圾回收,得先晓得什么样的对象是垃圾。目前确认对象是否为垃圾的算法次要有两种:援用计数法和可达性分析法。 1、援用计数法:在对象中增加了一个援用计数器,当有中央援用这个对象时,援用计数器的值就加1,当援用生效的时候,援用计数器的值就减1。当援用计数器的值为0时,JVM就开始回收这个对象。对于某个对象而言,只有应用程序中持有该对象的援用,就阐明该对象不是垃圾,如果一个对象没有任何指针对其援用,它就是垃圾。这种办法尽管很简略、高效,然而JVM个别不会抉择这个办法,因为这个办法会呈现一个弊病:当对象之间互相指向时,两个对象的援用计数器的值都会加1,而因为两个对象时互相指向,所以援用不会生效,这样JVM就无奈回收。 2、可达性分析法:针对援用计数算法的弊病,JVM采纳了另一种算法,以一些"GC Roots"的对象作为起始点向下搜寻,搜寻所走过的门路称为援用链(Reference Chain),当一个对象到GC Roots没有任何援用链相连时,则证实此对象是不可用的,即能够进行垃圾回收。否则,证实这个对象有用,不是垃圾。 上图中的obj7和obj8尽管它们相互援用,但从GC Roots登程这两个对象不可达,所以会被标记为垃圾。JVM会把以下几类对象作为GC Roots: (1) 虚拟机栈(栈帧中本地变量表)中援用的对象;(2) 办法区中类动态属性援用的对象;(3) 办法区中常量援用的对象;(4) 本地办法栈中JNI(Native办法)援用的对象。注:在可达性剖析算法中不可达的对象,并不是间接被回收,这时它们处于缓刑状态,至多须要进行两次标记才会确定该对象是否被回收:第一次标记:如果对象在进行可达性剖析后发现没有与GC Roots相连接的援用链,那它将会被第一次标记; 第二次标记:第一次标记后接着会进行一次筛选,筛选的条件是此对象是否有必要执行finalize()办法(该办法可将此对象与GC Roots建立联系)。在finalize()办法中没有从新与援用链建设关联关系的,将被进行第二次标记。 第二次标记胜利的对象将真的会被回收,如果对象在finalize()办法中从新与援用链建设了关联关系,那么将会逃离本次回收,持续存活。 8、垃圾收集的算法有哪些晓得了如何JVM确定哪些对象是垃圾后,上面咱们来看一下,面对这些垃圾对象,JVM的回收算法都有哪些。 1、 标记-革除算法(Mark-Sweep)第一步“标记”,如下图所示把堆里所有的对象都扫描一遍,找出哪些是垃圾须要回收的对象,并且把它们标记进去。 第二步“革除”,把第一步标记为“UnReference Object”(无援用或不可达)的对象革除掉,开释内存空间。这种算法的毛病次要有两点: (1) 标记和革除两个过程都比拟耗时,效率不高 (2) 革除后会产生大量不间断的内存碎片空间,碎片空间太多可能会导致当程序后续须要创立较大对象时,无奈找到足够间断的内存空间而不得不再次触发垃圾回收。 2、 标记-复制算法(Mark-Copying)将内存划分为两块区域,每次应用其中一块,当其中一块用满,触发垃圾回收的时候,将存活的对象复制到另一块下来,而后把之前应用的那一块进行格式化,一次性革除洁净。 (革除前) (革除后) “标记-复制”算法的毛病不言而喻,就是内存空间利用率低。 3、 标记-整顿算法(Mark-Compact)标记整顿算法标记过程依然与"标记-革除"算法一样,然而后续步骤不是间接对可回收对象进行清理,而是让所有存活的对象都向一端挪动,而后间接清理掉端边界以外的内存。 将所有存活的对象向一边挪动,清理掉存活边界以外的全副内存空间。 联合这三种算法咱们能够看到, “标记-复制”算法的长处是回收效率高,但空间利用率上有肯定的节约。而“标记-整顿”算法因为须要向一侧挪动等一系列操作,其效率绝对低一些,但对内存空间治理上非常优异。因而,“标记-复制”算法实用于那些生命周期短、回收频率高的内存对象,而标记-整顿”算法实用于那些生命周期长、回收频率低,但重视回收一次内存空间失去足够开释的场景。因而JVM的设计者将JVM的堆内存,分为了两大块区域Young区和Old区,Young区存储的就是那些生命周期短,应用一两次就不再应用的对象,回收一次基本上该区域十之有八的对象全副被回收清理掉,因而Young区采纳的垃圾回收算法也就是“标记-复制”算法。Old区存储的是那些生命周期长,通过屡次回收后依然存活的对象,就把它们放到Old区中,平时不再去判断这些对象的可达性,直到Old区不够用为止,再进行一次对立的回收,开释出足够的间断的内存空间。 9、各种问世的垃圾收集器鉴于Young区和Old区须要采纳不同的垃圾回收算法,因而在JVM的整个垃圾收集器的演进各个时代里,针对Young区和Old区每个时代都是不同的垃圾收集机制。从JDK1.3开始到目前,JVM垃圾收集器的演进大体分为四个时代:串行时代、并行时代、并发时代和G1时代。 1、串行时代:Serial(Young区)+ Serial Old(Old区)JDK3(1.3)的时候,大略是2000年左右,那个时代根本计算机都是单核一个CPU的,因而垃圾回收最后的设计实现也是基于单核单线程工作的。并且垃圾回收线程的执行绝对于失常业务线程执行来说还是STW(stop the world)的,应用一个CPU或者一条收集线程去实现垃圾收集工作,这个线程执行的时候其它线程须要进行。 串行收集器采纳单线程stop-the-world的形式进行收集。当内存不足时,串行GC设置进展标识,待所有线程都进入平安点(Safepoint)时,利用线程暂停,串行GC开始工作,采纳单线程形式回收空间并整顿内存。单线程也意味着复杂度更低、占用内存更少,但同时也意味着不能无效利用多核优势。因而,串行收集器特地适宜堆内存不高、单核甚至双核CPU的场合。 2、并行时代:Parallel Scavenge(Young区) + Parallel Old(Old区)并行收集器是以关注吞吐量为指标的垃圾收集器,也是server模式下的默认收集器配置,对吞吐量的关注次要体现在年老代Parallel Scavenge收集器上。 并行收集器与串行收集器工作模式类似,都是stop-the-world形式,只是暂停时并行地进行垃圾收集。年老代采纳复制算法,老年代采纳标记-整顿,在回收的同时还会对内存进行压缩。关注吞吐量次要指年老代的Parallel Scavenge收集器,通过两个指标参数-XX:MaxGCPauseMills和-XX:GCTimeRatio,调整新生代空间大小,来升高GC触发的频率。并行收集器适宜对吞吐量要求远远高于提早要求的场景,并且在满足最差延时的状况下,并行收集器将提供最佳的吞吐量。 3、 并发时代:CMS(Old区)并发标记革除(CMS)是以关注提早为指标、非常优良的垃圾回收算法,CMS是针对Old区的垃圾回收实现。 老年代CMS每个收集周期都要经验:初始标记、并发标记、从新标记、并发革除。其中,初始标记以STW的形式标记所有的根对象;并发标记则同利用线程一起并行,标记出根对象的可达门路;在进行垃圾回收前,CMS再以一个STW进行从新标记,标记那些由mutator线程(指引起数据变动的线程,即利用线程)批改而可能错过的可达对象;最初失去的不可达对象将在并发革除阶段进行回收。值得注意的是,初始标记和从新标记都已优化为多线程执行。CMS非常适合堆内存大、CPU核数多的服务器端利用,也是G1呈现之前大型利用的首选收集器。 - 但CMS有以下两个缺点:(1)因为它是标记-革除不是标记-整顿,因而会产生内存碎片,Old区会随着工夫的推移而究竟被耗尽或产生无奈调配大对象的状况。最初不得不通过底层的担保机制(CMS背地有串行的回收作为兜底)进行一次Full GC,并进行内存压缩。(2)因为标记和革除都是通利用线程并发进行,两类线程同时执行时会减少堆内存的占用,一旦某一时刻内存不够用,就会触发底层担保机制,又采纳串行回收进行一次STW的垃圾回收。4、G1时代:Garbage FirstG1收集器时代,Java堆的内存布局与就与其余收集器有很大差异,它将整个Java堆划分为多个大小相等的独立区域(Region),尽管还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不须要间断)的汇合。 ...
关于jvm:偏向锁的批量重偏向与批量撤销机制
前言从网上看了很多批量重偏差与批量撤销的介绍,但都只知其一;不知其二,本着钻研的精力,应用数据进行剖析批量重偏差与批量撤销的工作机制。 筹备首先,要先晓得偏差锁的偏差锁机制,着重看下撤销机制。而后,要晓得【批量重偏差与批量撤销】的钻研对象是Class,即锁对象对应的Class,而非锁对象自身。 // 钻研的是对象.classsynchronized(对象) {}// 并不是说的这种,这种不思考synchronized(对象.class) {}如对机制不理解,能够看下这个博客https://blog.csdn.net/sinat_41832255/article/details/89309944另外几个jvm参数: -XX:BiasedLockingBulkRebiasThreshold = 20 // 默认偏差锁批量重偏差阈值-XX:BiasedLockingBulkRevokeThreshold = 40 // 默认偏差锁批量撤销阈值-XX:+UseBiasedLocking // 应用偏差锁,jdk6之后默认开启-XX:BiasedLockingStartupDelay = 0 // 提早偏差工夫, 默认不为0,意思为jvm启动多少ms当前开启偏差锁机制(此处设为0,不提早)在这里,我把博客中的图拷过去,不便大家思考。 注释咱们次要看下图中的 “是否开启重偏差?” 这个条件分支,来看下什么是重偏差? 重偏差字面了解就是从新偏差,那什么状况下会从新偏差呢批量重偏差咱们首先要晓得的是,锁对象Class刚开始的状况下,是没有开启重偏差的,意思就是 “是否开启重偏差?” 这个分支刚开始的时候是始终走 “否” 的,即会始终撤销偏差锁 ,当达到BiasedLockingBulkRebiasThreshold(20)次数的时候,容许重偏差。 上面咱们看下代码 public class TestBiasedLocking { public static void main(String[] args) throws DecoderException, InterruptedException { // 首先咱们创立一个list,来寄存锁对象 List<TestBiasedLocking> list = new LinkedList<>(); // 线程1 new Thread(() -> { for (int i = 0; i < 50; i++) { TestBiasedLocking testBiasedLocking = new TestBiasedLocking(); list.add(testBiasedLocking); // 新建锁对象 synchronized (testBiasedLocking) { System.out.println("第" + (i + 1) + "次加锁-线程1"); System.out.println(ClassLayout.parseInstance(testBiasedLocking).toPrintable()); // 打印对象头信息 } } }, "线程1").start(); // 让线程1跑一会儿 Thread.sleep(2000); // 线程2 new Thread(() -> { for (int i = 0; i < 30; i++) { TestBiasedLocking testBiasedLocking = list.get(i); synchronized (testBiasedLocking) { System.out.println("第" + (i + 1) + "次加锁-线程2"); System.out.println(ClassLayout.parseInstance(testBiasedLocking).toPrintable()); // 打印对象头信息 } } }, "线程2").start(); LockSupport.park(); }} ...
关于jvm:深入理解Java虚拟机JVM高级特性与最佳实践-第2版
深刻了解Java虚拟机_JVM高级个性与最佳实际 第2版 下载地址: https://pan.baidu.com/s/15_ADwgj7VPdMJhcMVUxYlQ 扫码上面二维码关注公众号回复100013 获取分享码 本书目录构造如下: 前言 第一局部 走近Java 第1章 走近Java 1.1 概述 1.2 Java技术体系 1.3 Java发展史 1.4 Java虚拟机发展史 1.4.1 Sun Classic Exact VM 1.4.2 Sun HotSpot VM 1.4.3 Sun Mobile-Embedded VM Meta-Circular VM 1.4.4 BEA JRockit IBM J9 VM 1.4.5 Azul VM BEA Liquid VM 1.4.6 Apache Harmony Google Android Dalvik VM 1.4.7 Microsoft JVM及其他 1.5 瞻望Java技术的将来 1.5.1 模块化 1.5.2 混合语言 1.5.3 多核并行 1.5.4 进一步丰盛语法 1.5.5 64位虚拟机 1.6 实战:本人编译JDK 1.6.1 获取JDK源码 1.6.2 零碎需要 1.6.3 构建编译环境 1.6.4 进行编译 1.6.5 在IDE工具中进行源码调试 1.7 本章小结 第二局部 主动内存管理机制 第2章 Java内存区域与内存溢出异样 2.1 概述 2.2 运行时数据区域 2.2.1 程序计数器 2.2.2 Java虚拟机栈 2.2.3 本地办法栈 2.2.4 Java堆 2.2.5 办法区 2.2.6 运行时常量池 2.2.7 间接内存 2.3 HotSpot虚拟机对象探秘 2.3.1 对象的创立 2.3.2 对象的内存布局 2.3.3 对象的拜访定位 2.4 实战:OutOfMemoryError异样 2.4.1 Java堆溢出 2.4.2 虚拟机栈和本地办法栈溢出 2.4.3 办法区和运行时常量池溢出 2.4.4 本机间接内存溢出 2.5 本章小结 第3章 垃圾收集器与内存调配策略 3.1 概述 3.2 对象已死吗 3.2.1 援用计数算法 3.2.2 可达性剖析算法 3.2.3 再谈援用 3.2.4 生存还是死亡 3.2.5 回收办法区 3.3 垃圾收集算法 3.3.1 标记-革除算法 3.3.2 复制算法 3.3.3 标记-整顿算法 3.3.4 分代收集算法 3.4 HotSpot的算法实现 3.4.1 枚举根节点 3.4.2 平安点 3.4.3 平安区域 3.5 垃圾收集器 3.5.1 Serial收集器 3.5.2 ParNew收集器 3.5.3 Parallel Scavenge收集器 3.5.4 Serial Old收集器 3.5.5 Parallel Old收集器 3.5.6 CMS收集器 3.5.7 G1收集器 3.5.8 了解GC日志 3.5.9 垃圾收集器参数总结 3.6 内存调配与回收策略 3.6.1 对象优先在Eden调配 3.6.2 大对象间接进入老年代 3.6.3 长期存活的对象将进入老年代 3.6.4 动静对象年龄断定 3.6.5 空间调配担保 3.7 本章小结 第4章 虚拟机性能监控与故障解决工具 4.1 概述 4.2 JDK的命令行工具 4.2.1 jps:虚拟机过程情况工具 4.2.2 jstat:虚拟机统计信息监督工具 4.2.3 jinfo:Java配置信息工具 4.2.4 jmap:Java内存映像工具 4.2.5 jhat:虚拟机堆转储快照剖析工具 4.2.6 jstack:Java堆栈跟踪工具 4.2.7 HSDIS:JIT生成代码反汇编 4.3 JDK的可视化工具 4.3.1 JConsole:Java监督与治理控制台 4.3.2 VisualVM:多合一故障解决工具 4.4 本章小结 第5章 调优案例剖析与实战 5.1 概述 5.2 案例剖析 5.2.1 高性能硬件上的程序部署策略 5.2.2 集群间同步导致的内存溢出 5.2.3 堆外内存导致的溢出谬误 5.2.4 外部命令导致系统迟缓 5.2.5 服务器JVM过程解体 5.2.6 不失当数据结构导致内存占用过大 5.2.7 由Windows虚拟内存导致的长时间进展 5.3 实战:Eclipse运行速度调优 5.3.1 调优前的程序运行状态 5.3.2 降级JDK 1.6的性能变动及兼容问题 5.3.3 编译工夫和类加载工夫的优化 5.3.4 调整内存设置管制垃圾收集频率 5.3.5 抉择收集器升高提早 5.4 本章小结 第三局部 虚拟机执行子系统 第6章 类文件构造 6.1 概述 6.2 无关性的基石 6.3 Class类文件的构造 6.3.1 魔数与Class文件的版本 6.3.2 常量池 6.3.3 拜访标记 6.3.4 类索引、父类索引与接口索引汇合 6.3.5 字段表汇合 6.3.6 办法表汇合 6.3.7 属性表汇合 6.4 字节码指令简介 6.4.1 字节码与数据类型 6.4.2 加载和存储指令 6.4.3 运算指令 6.4.4 类型转换指令 6.4.5 对象创立与拜访指令 6.4.6 操作数栈治理指令 6.4.7 管制转移指令 6.4.8 办法调用和返回指令 6.4.9 异样解决指令 6.4.10 同步指令 6.5 私有设计和公有实现 6.6 Class文件构造的倒退 6.7 本章小结 第7章 虚拟机类加载机制 7.1 概述 7.2 类加载的机会 7.3 类加载的过程 7.3.1 加载 7.3.2 验证 7.3.3 筹备 7.3.4 解析 7.3.5 初始化 7.4 类加载器 7.4.1 类与类加载器 7.4.2 双亲委派模型 7.4.3 毁坏双亲委派模型 7.5 本章小结 第8章 虚拟机字节码执行引擎 8.1 概述 8.2 运行时栈帧构造 8.2.1 局部变量表 8.2.2 操作数栈 8.2.3 动静连贯 8.2.4 办法返回地址 8.2.5 附加信息 8.3 办法调用 8.3.1 解析 8.3.2 分派 8.3.3 动静类型语言反对 8.4 基于栈的字节码解释执行引擎 8.4.1 解释执行 8.4.2 基于栈的指令集与基于寄存器的指令集 8.4.3 基于栈的解释器执行过程 8.5 本章小结 第9章 类加载及执行子系统的案例与实战 9.1 概述 9.2 案例剖析 9.2.1 Tomcat:正统的类加载器架构 9.2.2 OSGi:灵便的类加载器架构 9.2.3 字节码生成技术与动静代理的实现 9.2.4 Retrotranslator:逾越JDK版本 9.3 实战:本人入手实现近程执行性能 9.3.1 指标 9.3.2 思路 9.3.3 实现 9.3.4 验证 9.4 本章小结 第四局部 程序编译与代码优化 第10章 晚期(编译期)优化 10.1 概述 10.2 Javac编译器 10.2.1 Javac的源码与调试 10.2.2 解析与填充符号表 10.2.3 注解处理器 10.2.4 语义剖析与字节码生成 10.3 Java语法糖的滋味 10.3.1 泛型与类型擦除 10.3.2 主动装箱、拆箱与遍历循环 10.3.3 条件编译 10.4 实战:插入式注解处理器 10.4.1 实战指标 10.4.2 代码实现 10.4.3 运行与测试 10.4.4 其余利用案例 10.5 本章小结 第11章 早期(运行期)优化 11.1 概述 11.2 HotSpot虚拟机内的即时编译器 11.2.1 解释器与编译器 11.2.2 编译对象与触发条件 11.2.3 编译过程 11.2.4 查看及剖析即时编译后果 11.3 编译优化技术 11.3.1 优化技术概览 11.3.2 公共子表达式打消 11.3.3 数组边界查看打消 11.3.4 办法内联 11.3.5 逃逸剖析 11.4 Java与CC++的编译器比照 11.5 本章小结 第五局部 高效并发 第12章 Java内存模型与线程 12.1 概述 12.2 硬件的效率与一致性 12.3 Java内存模型 12.3.1 主内存与工作内存 12.3.2 内存间交互操作 12.3.3 对于volatile型变量的非凡规定 12.3.4 对于long和double型变量的非凡规定 12.3.5 原子性、可见性与有序性 12.3.6 后行产生准则 12.4 Java与线程 12.4.1 线程的实现 12.4.2 Java线程调度 12.4.3 状态转换 12.5 本章小结 第13章 线程平安与锁优化 13.1 概述 13.2 线程平安 13.2.1 Java语言中的线程平安 13.2.2 线程平安的实现办法 13.3 锁优化 13.3.1 自旋锁与自适应自旋 13.3.2 锁打消 13.3.3 锁粗化 13.3.4 轻量级锁 13.3.5 偏差锁 13.4 本章小结 附 录 附录A 编译Windows版的OpenJDK 附录B 虚拟机字节码指令表 附录C HotSpot虚拟机次要参数表 附录D 对象查询语言(OQL)简介 附录E JDK历史版本轨迹 ...
关于jvm:深入理解Java虚拟机JVM高级特性与最佳实践-第2版
深刻了解Java虚拟机_JVM高级个性与最佳实际 第2版 下载地址: https://pan.baidu.com/s/15_ADwgj7VPdMJhcMVUxYlQ 扫码上面二维码关注公众号回复100013 获取分享码 本书目录构造如下: 前言 第一局部 走近Java 第1章 走近Java 1.1 概述 1.2 Java技术体系 1.3 Java发展史 1.4 Java虚拟机发展史 1.4.1 Sun Classic Exact VM 1.4.2 Sun HotSpot VM 1.4.3 Sun Mobile-Embedded VM Meta-Circular VM 1.4.4 BEA JRockit IBM J9 VM 1.4.5 Azul VM BEA Liquid VM 1.4.6 Apache Harmony Google Android Dalvik VM 1.4.7 Microsoft JVM及其他 1.5 瞻望Java技术的将来 1.5.1 模块化 1.5.2 混合语言 1.5.3 多核并行 1.5.4 进一步丰盛语法 1.5.5 64位虚拟机 1.6 实战:本人编译JDK 1.6.1 获取JDK源码 1.6.2 零碎需要 1.6.3 构建编译环境 1.6.4 进行编译 1.6.5 在IDE工具中进行源码调试 1.7 本章小结 第二局部 主动内存管理机制 第2章 Java内存区域与内存溢出异样 2.1 概述 2.2 运行时数据区域 2.2.1 程序计数器 2.2.2 Java虚拟机栈 2.2.3 本地办法栈 2.2.4 Java堆 2.2.5 办法区 2.2.6 运行时常量池 2.2.7 间接内存 2.3 HotSpot虚拟机对象探秘 2.3.1 对象的创立 2.3.2 对象的内存布局 2.3.3 对象的拜访定位 2.4 实战:OutOfMemoryError异样 2.4.1 Java堆溢出 2.4.2 虚拟机栈和本地办法栈溢出 2.4.3 办法区和运行时常量池溢出 2.4.4 本机间接内存溢出 2.5 本章小结 第3章 垃圾收集器与内存调配策略 3.1 概述 3.2 对象已死吗 3.2.1 援用计数算法 3.2.2 可达性剖析算法 3.2.3 再谈援用 3.2.4 生存还是死亡 3.2.5 回收办法区 3.3 垃圾收集算法 3.3.1 标记-革除算法 3.3.2 复制算法 3.3.3 标记-整顿算法 3.3.4 分代收集算法 3.4 HotSpot的算法实现 3.4.1 枚举根节点 3.4.2 平安点 3.4.3 平安区域 3.5 垃圾收集器 3.5.1 Serial收集器 3.5.2 ParNew收集器 3.5.3 Parallel Scavenge收集器 3.5.4 Serial Old收集器 3.5.5 Parallel Old收集器 3.5.6 CMS收集器 3.5.7 G1收集器 3.5.8 了解GC日志 3.5.9 垃圾收集器参数总结 3.6 内存调配与回收策略 3.6.1 对象优先在Eden调配 3.6.2 大对象间接进入老年代 3.6.3 长期存活的对象将进入老年代 3.6.4 动静对象年龄断定 3.6.5 空间调配担保 3.7 本章小结 第4章 虚拟机性能监控与故障解决工具 4.1 概述 4.2 JDK的命令行工具 4.2.1 jps:虚拟机过程情况工具 4.2.2 jstat:虚拟机统计信息监督工具 4.2.3 jinfo:Java配置信息工具 4.2.4 jmap:Java内存映像工具 4.2.5 jhat:虚拟机堆转储快照剖析工具 4.2.6 jstack:Java堆栈跟踪工具 4.2.7 HSDIS:JIT生成代码反汇编 4.3 JDK的可视化工具 4.3.1 JConsole:Java监督与治理控制台 4.3.2 VisualVM:多合一故障解决工具 4.4 本章小结 第5章 调优案例剖析与实战 5.1 概述 5.2 案例剖析 5.2.1 高性能硬件上的程序部署策略 5.2.2 集群间同步导致的内存溢出 5.2.3 堆外内存导致的溢出谬误 5.2.4 外部命令导致系统迟缓 5.2.5 服务器JVM过程解体 5.2.6 不失当数据结构导致内存占用过大 5.2.7 由Windows虚拟内存导致的长时间进展 5.3 实战:Eclipse运行速度调优 5.3.1 调优前的程序运行状态 5.3.2 降级JDK 1.6的性能变动及兼容问题 5.3.3 编译工夫和类加载工夫的优化 5.3.4 调整内存设置管制垃圾收集频率 5.3.5 抉择收集器升高提早 5.4 本章小结 第三局部 虚拟机执行子系统 第6章 类文件构造 6.1 概述 6.2 无关性的基石 6.3 Class类文件的构造 6.3.1 魔数与Class文件的版本 6.3.2 常量池 6.3.3 拜访标记 6.3.4 类索引、父类索引与接口索引汇合 6.3.5 字段表汇合 6.3.6 办法表汇合 6.3.7 属性表汇合 6.4 字节码指令简介 6.4.1 字节码与数据类型 6.4.2 加载和存储指令 6.4.3 运算指令 6.4.4 类型转换指令 6.4.5 对象创立与拜访指令 6.4.6 操作数栈治理指令 6.4.7 管制转移指令 6.4.8 办法调用和返回指令 6.4.9 异样解决指令 6.4.10 同步指令 6.5 私有设计和公有实现 6.6 Class文件构造的倒退 6.7 本章小结 第7章 虚拟机类加载机制 7.1 概述 7.2 类加载的机会 7.3 类加载的过程 7.3.1 加载 7.3.2 验证 7.3.3 筹备 7.3.4 解析 7.3.5 初始化 7.4 类加载器 7.4.1 类与类加载器 7.4.2 双亲委派模型 7.4.3 毁坏双亲委派模型 7.5 本章小结 第8章 虚拟机字节码执行引擎 8.1 概述 8.2 运行时栈帧构造 8.2.1 局部变量表 8.2.2 操作数栈 8.2.3 动静连贯 8.2.4 办法返回地址 8.2.5 附加信息 8.3 办法调用 8.3.1 解析 8.3.2 分派 8.3.3 动静类型语言反对 8.4 基于栈的字节码解释执行引擎 8.4.1 解释执行 8.4.2 基于栈的指令集与基于寄存器的指令集 8.4.3 基于栈的解释器执行过程 8.5 本章小结 第9章 类加载及执行子系统的案例与实战 9.1 概述 9.2 案例剖析 9.2.1 Tomcat:正统的类加载器架构 9.2.2 OSGi:灵便的类加载器架构 9.2.3 字节码生成技术与动静代理的实现 9.2.4 Retrotranslator:逾越JDK版本 9.3 实战:本人入手实现近程执行性能 9.3.1 指标 9.3.2 思路 9.3.3 实现 9.3.4 验证 9.4 本章小结 第四局部 程序编译与代码优化 第10章 晚期(编译期)优化 10.1 概述 10.2 Javac编译器 10.2.1 Javac的源码与调试 10.2.2 解析与填充符号表 10.2.3 注解处理器 10.2.4 语义剖析与字节码生成 10.3 Java语法糖的滋味 10.3.1 泛型与类型擦除 10.3.2 主动装箱、拆箱与遍历循环 10.3.3 条件编译 10.4 实战:插入式注解处理器 10.4.1 实战指标 10.4.2 代码实现 10.4.3 运行与测试 10.4.4 其余利用案例 10.5 本章小结 第11章 早期(运行期)优化 11.1 概述 11.2 HotSpot虚拟机内的即时编译器 11.2.1 解释器与编译器 11.2.2 编译对象与触发条件 11.2.3 编译过程 11.2.4 查看及剖析即时编译后果 11.3 编译优化技术 11.3.1 优化技术概览 11.3.2 公共子表达式打消 11.3.3 数组边界查看打消 11.3.4 办法内联 11.3.5 逃逸剖析 11.4 Java与CC++的编译器比照 11.5 本章小结 第五局部 高效并发 第12章 Java内存模型与线程 12.1 概述 12.2 硬件的效率与一致性 12.3 Java内存模型 12.3.1 主内存与工作内存 12.3.2 内存间交互操作 12.3.3 对于volatile型变量的非凡规定 12.3.4 对于long和double型变量的非凡规定 12.3.5 原子性、可见性与有序性 12.3.6 后行产生准则 12.4 Java与线程 12.4.1 线程的实现 12.4.2 Java线程调度 12.4.3 状态转换 12.5 本章小结 第13章 线程平安与锁优化 13.1 概述 13.2 线程平安 13.2.1 Java语言中的线程平安 13.2.2 线程平安的实现办法 13.3 锁优化 13.3.1 自旋锁与自适应自旋 13.3.2 锁打消 13.3.3 锁粗化 13.3.4 轻量级锁 13.3.5 偏差锁 13.4 本章小结 附 录 附录A 编译Windows版的OpenJDK 附录B 虚拟机字节码指令表 附录C HotSpot虚拟机次要参数表 附录D 对象查询语言(OQL)简介 附录E JDK历史版本轨迹 ...
关于jvm:基础篇JVM运行时内存布局
1 JVM的内存区域布局java代码的执行步骤有三点 java源码文件->编译器->字节码文件字节码文件->JVM->机器码机器码->零碎CPU执行JVM执行的字节码须要用类加载来载入;字节码文件能够来自本地文件,能够在网络上获取,也能够实时生成。就是说你能够跳过写java代码阶段,间接生成字节码交由JVM执行其中Java虚拟机栈、程序计数器、Heap、本地办法栈、Metaspace属于JVM运行时的内存;按是否线程共享则能够分两类 JAVA堆和MetasSpace元空间属于线程共享的;虚拟机栈和本地办法栈、程序计数器是线程公有的2 JVM五大数据区域介绍2.1 程序计数器(Progarm Counter Register) 一块较小的内存空间, 是以后线程所执行的字节码的行号指示器。线程有一个独属的程序计数器,字节码解析工作时须要程序计数器来选取下一指令,分支、循环、跳转等依赖它正在执行java办法线程的计数器记录的是虚拟机字节码指令的地址;如果还是Native办法,则为空程序计数器内存区域是惟一一个在虚拟机中没有规定任何OutOfMemoryError谬误的区域2.2 虚拟机栈(Virtual Machine Stack) Java办法执行的内存模型:每个办法在执行的同时都会创立一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动静链接、办法进口等信息每一个办法从调用直至执行实现的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程栈帧是用来存储数据和局部过程后果的数据结构,同时也被用来解决动静链接(Dynamic Linking)、 办法返回值和异样分派(Dispatch Exception)。栈帧随着办法调用而创立,随着办法完结而销毁(无论办法是失常实现还是异样实现)如果线程申请的栈深度大于虚拟机容许深度,则抛出StackOverflowError;扩大时无奈申请到足够内存,则抛出OutOfMemeryError2.3 本地办法栈(Native Method Stack) 本地办法栈和虚拟机栈作用相似,区别是虚拟机栈为执行Java办法服务,而本地办法栈则为Native办法服务。(HopShot的实现 间接把本地办法栈和虚拟机栈合二为一)上述3类区域,生命周期与Thread雷同,即:线程创立时,相应的内存区创立,线程销毁时,开释相应内存2.4 堆(Heap) 线程共享的一块内存区域,简直所有的对象实例在这里分配内存,也是垃圾收集器进行垃圾收集的最重要的内存区域。因而很多时候也叫GC堆线程公有的调配缓存区(Thread Local Alloaction Buffer)也是在堆划分进去的JDK8的版本,因应用元空间代替永恒代,字符串常量池和类的动态变量也放入java堆中 2.5 元空间(MetaSpace) 次要存储类的元数据,比方类的各种形容信息,类名、办法、字段、拜访限度等,既编译器编译后的代码等数据运行时常量池:Class文件中除了有类的版本、字段、办法等形容等信息外;还有一项信息是常量池,用于寄存编译期生成的各种字面量和符号援用,这部分将在类加载后寄存到元空间的运行时常量池中应用元空间代替永恒代起因 永恒代的大小是在启动时固定好的,很难进行调优;太大则容易导致永恒代溢出;太小在运行时,容易抛出OutOfMemeryError字符串存在永恒代中,应用时易出问题,因为永恒代内存常常不够用,爆出异样OutOfMemoryError: PermGenCodeCache JVM生成的native code寄存的内存空间称之为Code Cache;JIT编译、JNI等都会编译代码到native code,其中JIT生成的native code占用了Code Cache的绝大部分空间间接内存 它并不是虚拟机运行时数据区的个别分,也不在标准定义。JDK1.4,引入了Channel(通道)与Buffer(缓存区)的I/O形式,它能够应用Native函数调配堆外内存,可通过DirectByteBuffer操作。3 JVM运行时内存布局和JMM内存模型区别 JVM内存区域是指JVM运行时将内存数据分区域存储,强调对内存空间的划分JAVA内存模型是Java语言在多线程并发状况下对于共享变量内存操作的标准:解决变量在多线程的可见性、原子性的问题4 JMM内存模型交互操作内存交互操作有八种,虚拟机的实现保障每一个操作都是原子性的 lock(锁定):作用于主内存的变量,标识变量为线程独占状态unlock(解锁):作用于主内存的变量,开释一个处于锁定状态的变量,开释后的变量才能够被其余线程锁定read(读取):作用于主内存变量,从主内存中读取出前面load操作要用到的变量load(载入):作用于主内存中的变量,把方才read的值放入工作内存的正本中use(应用):作用于工作内存中的变量,当线程执行某个字节码指令须要用到相应的变量时,把工作内存中的变量正本传给执行引擎assign(赋值):作用于工作内存中的变量,把一个从执行引擎中承受到的值放入工作内存的变量正本中store(存储):作用于工作内存中的变量,把工作内存中的变量送到主内存,给后续的write应用write(写入):作用于主内存中的变量,把store的工作内存中的变量值,写入主内存中read和load 如同是雷同的操作?各位有何高见,请指教下JMM对这八种指令的应用,制订了如下规定 read和load、store和write必须程序执行,而且两个指令绑定呈现;就是说呈现read就要有load不容许一个线程抛弃最近的assign操作,工作内存中的变量扭转后,必须write同步到主内存不容许一个线程把没有产生assign操作的变量同步到主内存新的变量必须诞生于主内存,不容许工作内存应用一个没有初始化的变量;use、store操作变量之前,必须通过load和assign操作变量同一时刻只容许一个线程对其lock,该线程能够对该变量加锁屡次,开释锁须要执行雷同次数的unlock,lock和unlock要成对呈现一个变量没有lock,不能unlock;并且一个线程不能unlock被其余线程锁住的变量执行unlock前,必须把工作内存中的变量同步到主内存中执行lock操作,须要清空工作内存(所有),并且须要应用该变量之前,要从新执行load和assign操作欢送指注释中谬误关注公众号,一起交换 参考文章深刻了解Java虚拟机JVM之内存布局超具体整顿Metaspace 之一:Metaspace整体介绍
关于jvm:ParallelScavenge-+-Parallel-Old
ParalletScavenge:复制算法,多线程,关注吞吐率Copying GC(GC复制算法): 最早是Robert R.Fenichel和Jerome C.Yochelson提出,简略说将空间分为From和To,当From空间齐全占满时,gc将流动对象全副复制到To空间,复制实现后,From和To的使命调换。 为了保障From中所有活着的对象都能复制到To里,要求From和To空间大小统一。 coping(){ free = to_start; for(r : roots){ *r = copy(*r) } swap(from_start,to_start)}/***free设在To空间的结尾*第四行copy函数,复制能从根援用到的对象及其子对象,返回*r所在的新空间。*而后替换From和To,对From空间进行gc*/ copy(obj){ if(obj.tag != COPIED){ copy_data(free,obj,obj.size); obj.tag = COPIED; obj.forwarding = free; free = obj.size for(child : children(obj.forwarding){ *child = copy(*child) } } return obj.forwarding}/***obj.tag 并不是独自的一个域,只是占用了obj的field1,一旦obj被复制结束,其在From空间的域值更改没有关系*obj.forwarding也不是一个独自的域,占用了obj.field2,用来标识新空间的地位* 由更上可知,须要每个对象至多有两个域* 深度遍历*/ //调配new_obj(size){ if(free + size > from_start + HEAP_SIZE /2){ copying(); //空间有余,gc if(free + size > from_start + HEAP /2){ allocation_fail();//还是不够,调配失败 } } obj = free; obj.size = size; free += size; return obj;}copying算法的总结: ...
关于jvm:ParallelScavenge-+-Parallel-Old
ParalletScavenge:复制算法,多线程,关注吞吐率Copying GC(GC复制算法): 最早是Robert R.Fenichel和Jerome C.Yochelson提出,简略说将空间分为From和To,当From空间齐全占满时,gc将流动对象全副复制到To空间,复制实现后,From和To的使命调换。 为了保障From中所有活着的对象都能复制到To里,要求From和To空间大小统一。 coping(){ free = to_start; for(r : roots){ *r = copy(*r) } swap(from_start,to_start)}/***free设在To空间的结尾*第四行copy函数,复制能从根援用到的对象及其子对象,返回*r所在的新空间。*而后替换From和To,对From空间进行gc*/ copy(obj){ if(obj.tag != COPIED){ copy_data(free,obj,obj.size); obj.tag = COPIED; obj.forwarding = free; free = obj.size for(child : children(obj.forwarding){ *child = copy(*child) } } return obj.forwarding}/***obj.tag 并不是独自的一个域,只是占用了obj的field1,一旦obj被复制结束,其在From空间的域值更改没有关系*obj.forwarding也不是一个独自的域,占用了obj.field2,用来标识新空间的地位* 由更上可知,须要每个对象至多有两个域* 深度遍历*/ //调配new_obj(size){ if(free + size > from_start + HEAP_SIZE /2){ copying(); //空间有余,gc if(free + size > from_start + HEAP /2){ allocation_fail();//还是不够,调配失败 } } obj = free; obj.size = size; free += size; return obj;}copying算法的总结: ...
关于jvm:对象的创建
基于HotSpot 根本类型对象的创立 1 当虚拟机遇到一条new指令时,尝试在常量池中定位到相干类的符号援用 2 查看这个符号援用代表的类是否曾经加载,解析,初始化,如果没有先执行类加载 3 实现2后可确定对象所需的内存大小 4 从堆中划分出相应大小的内存。 5 初始化对象:对象的哈希码,gc分代年龄,对应类的信息。 6 依据代码设置对象初始值。 问题一: 步骤3是如何确定对象所需内存的大小的? 首先看一下对象在内存中的存储布局: hotspot规定对象的起始地址必须是8字节的整倍数,即对象的大小必须是8字节的整数倍。对象头个别格局较固定,能够保障这一点,当实例数据不满足的时候,须要对齐填充局部来一起保障。 问题二: 步骤4中是如何从堆中划分内存的? 堆中划分内存有两种办法:指针 和 闲暇列表。应用哪种形式取决于堆是否是规整的(堆是否能规整取决于应用的垃圾回收算法)。 规整的意思是说堆中应用过的内存和闲暇内存有显著的划分界线,而不是混在一起的。 如果是规整的,那么就能够应用指针法,在应用过的内存和闲暇内存的分界点用一个指针示意,分配内存时只有将指针向闲暇内存方向挪动一段与对象内存大小相等的间隔。 如果不是规整的,那只能应用闲暇列表,闲暇列表是指虚拟机保护一个列表,记录哪些内存块是应用过的哪些是闲暇的,分配内存时从列表中抉择一块满足对象大小的内存块。这里如何抉择内存块,预计就是学生时代学的操作系统讲的那些内容了,如何保障内存碎片较少之类的(后续整顿) hotspot应用的应该是指针 分配内存的时候还波及到并发的问题,两种解决形式: 1 对分配内存这种操作进行同步解决,CAS保障操作的原子性 2 本地线程调配缓冲TLAB,事后为每个线程调配一小块内存TLAB,须要内存时各线程先在本人的TLAB上调配,当TLAB不够用的时候才会须要同步。 CAS: 简略形容下,大体采纳的是乐观锁的思维,不应用锁,多个线程去改同一个变量时,都能够尝试去改,只会有一个线程胜利,失败的线程也会立即失去反馈,不会阻塞。 CAS中有三个变量,内存中的值V,预期原值A,新值B,每个线程尝试去扭转量的时候,先判断A和V是否相等,如果雷同,则将B更新到内存,否则更新失败。
关于jvm:对象的实例化以及对象的内存布局
创建对象的形式new常见的形式变形1: xxx静态方法变形2: xxxBuilde/xxxFactory 的静态方法Class的 newinstance() :反射形式,只能调用空参结构器,权限必须是publicConstructor 的newInstance(Xxx): 反射形式,能够调用空参,带参的结构器,权限没要求应用clone():不调用任何结构器,以后的类须要实现Cloneable接口,实现clone()办法适应反序列化:从文件或文本中获取一个对象的二进制流应用三方库Objenesis创建对象的步骤1.判断对象对应的类是否加载、链接、初始化虚拟机遇到一条new指令,首先查看这个指令的参数是否在Metaspace的常量池中定位到一个类的符号援用,并查看这个符号援用代表的类是否被加载、解析、初始化(即判断类元信息是否存在)。如果没有,那么在双亲委派机制下,应用以后类加载器以ClassLoader+ 包名+ 类名为key进行查找对应的.class文件。如果没有找到文件,则抛出 ClassNotFoundException异样,如果找到,则进行类加载,并生成对应的Class类对象。 2.为对象分配内存首先计算对象占用空间大小,截止在堆中划分一块内存给新对象。如果实例成员变量是援用变量,仅调配援用变量空间即可,即4个字节大小。 如果内存工整 指针碰撞如果内存是规整的,那么虚拟机将采纳的是指针碰撞法(Bump the pointer )来为对象分配内存。 意思是所有用过的内寄存一边,闲暇的内寄存另一边,两头放着一个指针作为分界点的指示器,分配内存就仅仅把指针指向闲暇的那边移动一段与对象大小相等的间隔罢了。如果垃圾收集器抉择的是Serial、 ParNew这种基于压缩算法的,虚拟机采纳这种调配形式。个别应用带有compact(整顿)过程的收集器时,应用指针碰撞。 如果内存不工整 虚拟机须要保护一个列表闲暇列表调配如果内存是不工整的,已应用的内存和未应用的内存互相交织,那么虚拟机采纳的是闲暇散列表法来为对象分配内存。 意思是虚拟机保护了一个列表,记录了哪些内存是可用的,再调配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。这种调配形式成为“闲暇列表(Free List)” 阐明抉择哪种调配形式有Java堆是否规整决定,而java堆是否规整又由所采纳的垃圾收集器是否带有压缩整顿性能决定。 3.解决并发平安问题采纳CAS配上失败重试保障更新的原子性每个线程事后调配一块TLAB 通过-XX:+/-UseTLAB参数来设定4.初始化调配到空间所有属性设置默认值,保障对象实例字段在不赋值时能够间接应用5.设置对象的对象头将对象的所属类(即类的元数据信息)、对象HashCode和对象的GC信息、锁信息等数据存储在对象的对象头中。这个过程的具体设置取决于JVM的实现。 6.执行init办法进行初始化在Java程序的视角来看,初始化才正式开始。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给援用变量。 因而一般来说(由字节码中是否追随有invokespecial指令所决定),new指令之后会接着就是执行办法,把对象依照程序员的志愿进行初始化,这样一个真正可用的对象才算齐全创立进去了。 对象内存布局对象头 (Header)蕴含两局部 运行时元数据(MarkWorld) 哈希值 (hash code)GC 分代年龄锁状态标记线程持有的锁偏差锁ID偏差工夫戳类型指针 指向类元数据InstanceKlass,确定该对象所属的类型。阐明: 如果是数组,还要记录数组长度实例数据(Instance Data)阐明 它是对象真正存储的无效信息,包含程序代码中定义的各种类型的字段(包含从父类继承下来的和自身领有的字段)规定 如果CompactFiled参数为true(默认为true);子类的窄变量可能插入代父类变量的空隙对齐填充(Padding)不是必须的,也没特地含意,仅仅起到占位符作用。对象拜访定位创建对象的目标是为了应用它JVM是如果通过栈帧中的对象援用拜访到其外部的对象实例 的呢?定位,通过栈上reference援用对象拜访形式次要有两种句柄拜访 益处reference中存储稳固句柄地址,对象被挪动(垃圾收集时挪动对象很广泛)时只会扭转句柄中实例数据指针即可,reference自身不须要被批改。 间接指针(HotSpot采纳) 益处间接指向对象 思维导图如下:
关于jvm:对象的实例化以及对象的内存布局
创建对象的形式new常见的形式变形1: xxx静态方法变形2: xxxBuilde/xxxFactory 的静态方法Class的 newinstance() :反射形式,只能调用空参结构器,权限必须是publicConstructor 的newInstance(Xxx): 反射形式,能够调用空参,带参的结构器,权限没要求应用clone():不调用任何结构器,以后的类须要实现Cloneable接口,实现clone()办法适应反序列化:从文件或文本中获取一个对象的二进制流应用三方库Objenesis创建对象的步骤1.判断对象对应的类是否加载、链接、初始化虚拟机遇到一条new指令,首先查看这个指令的参数是否在Metaspace的常量池中定位到一个类的符号援用,并查看这个符号援用代表的类是否被加载、解析、初始化(即判断类元信息是否存在)。如果没有,那么在双亲委派机制下,应用以后类加载器以ClassLoader+ 包名+ 类名为key进行查找对应的.class文件。如果没有找到文件,则抛出 ClassNotFoundException异样,如果找到,则进行类加载,并生成对应的Class类对象。 2.为对象分配内存首先计算对象占用空间大小,截止在堆中划分一块内存给新对象。如果实例成员变量是援用变量,仅调配援用变量空间即可,即4个字节大小。 如果内存工整 指针碰撞如果内存是规整的,那么虚拟机将采纳的是指针碰撞法(Bump the pointer )来为对象分配内存。 意思是所有用过的内寄存一边,闲暇的内寄存另一边,两头放着一个指针作为分界点的指示器,分配内存就仅仅把指针指向闲暇的那边移动一段与对象大小相等的间隔罢了。如果垃圾收集器抉择的是Serial、 ParNew这种基于压缩算法的,虚拟机采纳这种调配形式。个别应用带有compact(整顿)过程的收集器时,应用指针碰撞。 如果内存不工整 虚拟机须要保护一个列表闲暇列表调配如果内存是不工整的,已应用的内存和未应用的内存互相交织,那么虚拟机采纳的是闲暇散列表法来为对象分配内存。 意思是虚拟机保护了一个列表,记录了哪些内存是可用的,再调配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。这种调配形式成为“闲暇列表(Free List)” 阐明抉择哪种调配形式有Java堆是否规整决定,而java堆是否规整又由所采纳的垃圾收集器是否带有压缩整顿性能决定。 3.解决并发平安问题采纳CAS配上失败重试保障更新的原子性每个线程事后调配一块TLAB 通过-XX:+/-UseTLAB参数来设定4.初始化调配到空间所有属性设置默认值,保障对象实例字段在不赋值时能够间接应用5.设置对象的对象头将对象的所属类(即类的元数据信息)、对象HashCode和对象的GC信息、锁信息等数据存储在对象的对象头中。这个过程的具体设置取决于JVM的实现。 6.执行init办法进行初始化在Java程序的视角来看,初始化才正式开始。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给援用变量。 因而一般来说(由字节码中是否追随有invokespecial指令所决定),new指令之后会接着就是执行办法,把对象依照程序员的志愿进行初始化,这样一个真正可用的对象才算齐全创立进去了。 对象内存布局对象头 (Header)蕴含两局部 运行时元数据(MarkWorld) 哈希值 (hash code)GC 分代年龄锁状态标记线程持有的锁偏差锁ID偏差工夫戳类型指针 指向类元数据InstanceKlass,确定该对象所属的类型。阐明: 如果是数组,还要记录数组长度实例数据(Instance Data)阐明 它是对象真正存储的无效信息,包含程序代码中定义的各种类型的字段(包含从父类继承下来的和自身领有的字段)规定 如果CompactFiled参数为true(默认为true);子类的窄变量可能插入代父类变量的空隙对齐填充(Padding)不是必须的,也没特地含意,仅仅起到占位符作用。对象拜访定位创建对象的目标是为了应用它JVM是如果通过栈帧中的对象援用拜访到其外部的对象实例 的呢?定位,通过栈上reference援用对象拜访形式次要有两种句柄拜访 益处reference中存储稳固句柄地址,对象被挪动(垃圾收集时挪动对象很广泛)时只会扭转句柄中实例数据指针即可,reference自身不须要被批改。 间接指针(HotSpot采纳) 益处间接指向对象 思维导图如下:
JVM中栈的frames详解
简介咱们晓得JVM运行时数据区域专门有一个叫做Stack Area的区域,专门用来负责线程的执行调用。那么JVM中的栈到底是怎么工作的呢?快来一起看看吧。 JVM中的栈小师妹:F师兄,JVM为每个线程的运行都调配了一个栈,这个栈到底是怎么工作的呢? 小师妹,咱们先看下JVM的整体运行架构图: 咱们能够看到运行时数据区域分为5大部分。 堆区是存储共享对象的中央,而栈区是存储线程公有对象的中央。 因为是栈的构造,所以这个区域总是LIFO(Last in first out)。咱们思考一个办法的执行,当办法执行的时候,就会在Stack Area中创立一个block,这个block中持有对本地对象和其余对象的援用。一旦办法执行结束,则这个block就会出栈,供其余办法拜访。 FrameJVM中的stack area是由一个个的Frame组成的。 Frame次要用来存储数据和局部后果,以及执行动静链接,办法的返回值和调度异样。 每次调用办法时都会创立一个新Frame。当Frame的办法调用实现时,无论该办法是失常完结还是异样完结(它引发未捕捉的异样),这个frame都会被销毁。 Frame是从JVM中的stack area中调配的。 每个frame都由三局部组成,别离是本人的local variables数组,本人的operand stack,以及对以后办法的run-time constant pool的援用。 在线程的执行过程中,任何一个时刻都只有一个frame处于活动状态。这个frame被称为current frame,它的办法被称为current 办法,定义以后办法的类是以后类。 如果frame中的办法调用另一个办法或该frame的办法完结,那么这个frame将不再是current frame。 每次调用新的办法,都会创立一个新的frame,并将控制权转移到调用新的办法生成的框架。 在办法返回时,以后frame将其办法调用的后果(如果有的话)传回上一个frame,并完结以后frame。 请留神,由线程创立的frame只能有该线程拜访,并且不能被任何其余线程援用。 Local Variables本地变量每个frame都蕴含一个称为其本地局部变量的变量数组。frame的局部变量数组的长度是在编译的时候确定的。 单个局部变量能够保留以下类型的值:boolean, byte, char, short, int, float, reference, 或者 returnAddress。 如果对于long或double类型的值须要应用一对局部变量来存储。 局部变量因为存储在数组中,所以间接通过数字的索引来定位和拜访。 留神,这个数组的索引值是从0开始,到数组长度-1完结。单个局部变量间接通过索引来拜访就够了,那么对于占用两个间断局部变量的long或者double类型来说,怎么拜访呢? 比如说一个long类型占用数组中的n和n+1两个变量,那么咱们能够通过索引n值来拜访这个long类型,而不是通过n+1来拜访。 留神,在JVM中,并不一定要求这个n是偶数。那么这些局部变量有什么用呢? Java虚拟机应用局部变量在办法调用时传递参数。 咱们晓得在java中有两种办法,一种是类办法,一种是实例办法。 在类办法调用中,所有参数都从局部变量0开始在间断的局部变量中传递。 在实例办法调用中,局部变量0始终指向的是该实例对象,也就是this。也就是说实在的参数是从局部变量1开始存储的。 Operand Stacks在每个frame外部,又蕴含了一个LIFO的栈,这个栈叫做Operand Stack。 刚开始创立的时候,这个Operand Stack是空的。而后JVM将local variables中的常量或者值加载到Operand Stack中去。 而后Java虚拟机指令从操作数堆栈中获取操作数,对其进行操作,而后将后果压回操作数堆栈。 比如说,当初的Operand Stack中曾经有两个值,1和2。 这个时候JVM要执行一个iadd指令,将1和2相加。那么就会先将stack中的1和2两个数取出,相加后,将后果3再压入stack。 最终stack中保留的是iadd的后果3。 留神,在Local Variables本地变量中咱们提到,如果是long或者double类型的话,须要两个本地变量来存储。而在Operand Stack中,一个值能够示意任何Java虚拟机类型的值。也就是说long和double在Operand Stack中,应用一个值就能够示意了。Operand Stack中的任何操作都必须要确保其类型匹配。像之前提到的iadd指令是对两个int进行相加,如果这个时候你的Operand Stacks中存储的是long值,那么iadd指令是会失败的。 ...
干货分享丨jvm系列dump文件深度分析
摘要:java内存dump是jvm运行时内存的一份快照,利用它能够剖析是否存在内存节约,能够查看内存治理是否正当,当产生OOM的时候,能够找出问题的起因。那么dump文件的内容是什么样的呢?JVM dumpjava内存dump是jvm运行时内存的一份快照,利用它能够剖析是否存在内存节约,能够查看内存治理是否正当,当产生OOM的时候,能够找出问题的起因。那么dump文件的内容是什么样的呢?咱们一步一步来 获取JVM dump文件获取dump文件的形式分为被动和被动 i.被动形式: 1.利用jmap,也是最罕用的形式:jmap -dump:[live],format=b,file= 2.利用jcmd,jcmd GC.heap_dump 3.应用VisualVM,能够界面操作进行dump内存 4.通过JMX的形式 MBeanServer server = ManagementFactory.getPlatformMBeanServer();HotSpotDiagnosticMXBean mxBean = ManagementFactory.newPlatformMXBeanProxy(server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class);mxBean.dumpHeap(filePath, live);参考(https://www.baeldung.com/java... ii.被动形式: 被动形式就是咱们通常的OOM事件了,通过设置参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath= dump文件剖析构造示意图 构造详解 dump文件是堆内存的映射,由文件头和一系列内容块组成 文件头 由musk, 版本,identifierSize, 工夫4局部组成 1、musk:4个byte,内容为'J', 'A', 'V', 'A'即JAVA 2、version:若干byte,值有以下三种 " PROFILE 1.0\0"," PROFILE 1.0.1\0"," PROFILE 1.0.2\0"3、identifierSize:4个byte数字,值为4或者8,示意一个援用所占用的byte数 4、time:8个byte,dump文件生成工夫 阐明:java一个类的成员变量有两种类型 根本类型(8种根本类型),它们占用byte数固定不变,每生成一个对象它们就须要给它们赋初始值,调配空间是援用类型,示意一个对象,在类中只有一个援用,援用只是一个数值,所占用的空间大小为identifierSize,被援用对象行将在堆中的另一个中央 例如定义一个类public class Person { private int age;//4个byte private String name;//identifierSize个byte private double weight;//8个byte}当咱们在new Person()的时候 它就须要申请一个空间,空间大小为 对象头大小+4+identifierSize+8个byte 对象大小的测量: jdk提供一个测试对象占用内存大小的工具Instrumentation,然而Instrumentation没法间接援用到,须要通过agent来援用到 定义一个Premain类, javac Premain.java ...
JvisualVM查找前N个最大的对象
下图是应用Guava的布隆过滤器,预估量是1亿,误判率是1百万分之1。的堆dump查看。下图是查问后果
AlarmClock-slow-alarmAlarm-问题排查-cms-perm-gen内存使用满
项目启动成功 但是无法正常访问控制台有报错信息AlarmClock slow alarm Alarm项目端口为8083使用netstat -ano| findstr "8083" 查找对应的pid使用jamp -heap 12576 查看内存使用情况可以看到maxpermsize最大为82M 目前已经使用81M多 永久代使用完思考项目启动时加载的类较多 解决方案:项目启动时设置 maxpermsize 增大后无报错
为什么需要-JVM它处在什么位置
JVM 和操作系统的关系JVM 全称 Java Virtual Machine,也就是我们耳熟能详的 Java 虚拟机。它能识别 .class后缀的文件,并且能够解析它的指令,最终调用操作系统上的函数,完成我们想要的操作。 一般情况下,使用 C++ 开发的程序,编译成二进制文件后,就可以直接执行了,操作系统能够识别它;但是 Java 程序不一样,使用 javac 编译成 .class 文件之后,还需要使用 Java 命令去主动执行它,操作系统并不认识这些 .class 文件。 你可能会想,我们为什么不能像 C++ 一样,直接在操作系统上运行编译后的二进制文件呢?而非要搞一个处于程序与操作系统中间层的虚拟机呢? 这就是 JVM 的过人之处了。大家都知道,Java 是一门抽象程度特别高的语言,提供了自动内存管理等一系列的特性。这些特性直接在操作系统上实现是不太可能的,所以就需要 JVM 进行一番转换。 有了上面的介绍,我们就可以做如下的类比。JVM:等同于操作系统;Java 字节码:等同于汇编语言。 如果你还是对上面的介绍有点模糊,可以参考下图:从图中可以看到,有了 JVM 这个抽象层之后,Java 就可以实现跨平台了。JVM 只需要保证能够正确执行 .class 文件,就可以运行在诸如 Linux、Windows、MacOS 等平台上了。 而 Java 跨平台的意义在于一次编译,处处运行,能够做到这一点 JVM 功不可没。比如我们在 Maven 仓库下载同一版本的 jar 包就可以到处运行,不需要在每个平台上再编译一次。 现在的一些 JVM 的扩展语言,比如 Clojure、JRuby、Groovy 等,编译到最后都是 .class 文件,Java 语言的维护者,只需要控制好 JVM 这个解析器,就可以将这些扩展语言无缝的运行在 JVM 之上了。 我们用一句话概括 JVM 与操作系统之间的关系:JVM 上承开发语言,下接操作系统,它的中间接口就是字节码。 ...
你不得不掌握的-JVM-内存管理
Java 引以为豪的就是它的自动内存管理机制。相比于 C++的手动内存管理、复杂难以理解的指针等,Java 程序写起来就方便的多。然而这种呼之即来挥之即去的内存申请和释放方式,自然也有它的代价。为了管理这些快速的内存申请释放操作,就必须引入一个池子来延迟这些内存区域的回收操作。我们常说的内存回收,就是针对这个池子的操作。我们把上面说的这个池子,叫作堆,可以暂时把它看成一个整体。 JVM 内存布局Java 程序的数据结构是非常丰富的。其中的内容,举一些例子:静态成员变动态成员变量区域变量短小紧凑的对象声明庞大复杂的内存申请 我们先看一下 JVM 的内存布局。随着 Java 的发展,内存布局一直在调整之中。比如,Java 8 及之后的版本,彻底移除了持久代,而使用 Metaspace 来进行替代。这也表示着 -XX:PermSize 和 -XX:MaxPermSize 等参数调优,已经没有了意义。但大体上,比较重要的内存区域是固定的。JVM 内存区域划分如图所示,从图中我们可以看出: JVM 堆中的数据是共享的,是占用内存最大的一块区域。可以执行字节码的模块叫作执行引擎。执行引擎在线程切换时怎么恢复?依靠的就是程序计数器。JVM 的内存划分与多线程是息息相关的。像我们程序中运行时用到的栈,以及本地方法栈,它们的维度都是线程。本地内存包含元数据区和一些直接内存。虚拟机栈Java 虚拟机栈是基于线程的。哪怕你只有一个 main() 方法,也是以线程的方式运行的。在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生命周期是和线程一样的。 栈里的每条数据,就是栈帧。在每个 Java 方法被调用的时候,都会创建一个栈帧,并入栈。一旦完成相应的调用,则出栈。所有的栈帧都出栈后,线程也就结束了。每个栈帧,都包含四个区域: 局部变量表操作数栈动态连接返回地址我们的应用程序,就是在不断操作这些内存空间中完成的。 本地方法栈是和虚拟机栈非常相似的一个区域,它服务的对象是 native 方法。你甚至可以认为虚拟机栈和本地方法栈是同一个区域,这并不影响我们对 JVM 的了解。 这里有一个比较特殊的数据类型叫作 returnAdress。因为这种类型只存在于字节码层面,所以我们平常打交道的比较少。对于 JVM 来说,程序就是存储在方法区的字节码指令,而 returnAddress 类型的值就是指向特定指令内存地址的指针。 这里有一个两层的栈。第一层是栈帧,对应着方法;第二层是方法的执行,对应着操作数。注意千万不要搞混了。你可以看到,所有的字节码指令,其实都会抽象成对栈的入栈出栈操作。执行引擎只需要傻瓜式的按顺序执行,就可以保证它的正确性。程序计数器既然是线程,就代表它在获取 CPU 时间片上,是不可预知的,需要有一个地方,对线程正在运行的点位进行缓冲记录,以便在获取 CPU 时间片时能够快速恢复。 程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。这里面存的,就是当前线程执行的进度。下面这张图,能够加深大家对这个过程的理解。可以看到,程序计数器也是因为线程而产生的,与虚拟机栈配合完成计算操作。程序计数器还存储了当前正在运行的流程,包括正在执行的指令、跳转、分支、循环、异常处理等。 我们可以看一下程序计数器里面的具体内容。下面这张图,就是使用 javap 命令输出的字节码。大家可以看到在每个 opcode 前面,都有一个序号。就是图中红框中的偏移地址,你可以认为它们是程序计数器的内容。 堆 堆是 JVM 上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的。我们常说的垃圾回收,操作的对象就是堆。 堆空间一般是程序启动时,就申请了,但是并不一定会全部使用。 随着对象的频繁创建,堆空间占用的越来越多,就需要不定期的对不再使用的对象进行回收。这个在 Java 中,就叫作 GC(Garbage Collection)。 由于对象的大小不一,在长时间运行后,堆空间会被许多细小的碎片占满,造成空间浪费。所以,仅仅销毁对象是不够的,还需要堆空间整理。这个过程非常的复杂。 那一个对象创建的时候,到底是在堆上分配,还是在栈上分配呢?这和两个方面有关:对象的类型和在 Java 类中存在的位置。 ...
小师妹学JVM之JIT中的PrintAssembly
简介想不想了解JVM最最底层的运行机制?想不想从本质上理解java代码的执行过程?想不想对你的代码进行进一步的优化和性能提升? 如果你的回答是yes。那么这篇文章非常适合你,因为本文将会站在离机器码最近的地方来观看JVM的运行原理:Assembly。 使用PrintAssembly小师妹:F师兄,上次你给我介绍了java中的字节码,还有JIT中的LogCompilation和PrintCompilation的用法。虽然都非常有用,但是能不能更进一步,让我能以机器的眼光来看待JVM的执行? 小师妹,如果要探究JVM的运行本质,那就应该是机器码了。难道你要去读懂机器码?虽然我不是机器码的专家,但我猜那应该是个非常复杂的过程。 小师妹:F师兄,当然不是机器码,有没有比机器码更高级一点点的,我记得上大学的时候学过汇编语言,好像就是离机器码最近的语言了,JVM有没有相应的汇编语言呢? 必须有的,我们可以使用-XX:+PrintAssembly来将assembly打印出来。 但是打印assembly是有条件的,它就像一个高傲的姑娘,不是你想追求就能追求得上的。 我们使用下面的命令来查看系统对PrintAssembly的支持程度: java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -versionJava HotSpot(TM) 64-Bit Server VM warning: PrintAssembly is enabled; turning on DebugNonSafepoints to gain additional outputCould not load hsdis-amd64.dylib; library not loadable; PrintAssembly is disabledjava version "1.8.0_171"Java(TM) SE Runtime Environment (build 1.8.0_171-b11)Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)根据大家的运行环境的不同,得到的结果可能也是不同的,我是mac的系统,从上面的结果可以看到,在我的JDK8的环境中,显示缺少hsdis-amd64.dylib,所以PrintAssembly其实是禁用的。 小师妹:F师兄,那现在咋办呀?没有hsdis-amd64.dylib就用不了PrintAssembly了。 巴甫洛夫说过:问号是开启任何一门科学的钥匙。没有问题我们就创造问题,没有困难我们就制造困难,没有hsdis-amd64.dylib当然是安装咯。 具体怎么安装,大家自行探索吧,网上有很多安装的教程,这里就不一一介绍了。 这里想讨论一个很奇怪的事情,虽然在JDK8环境中,我们不能使用PrintAssembly,因为没有hsdis-amd64.dylib。但是当我切到最新的JDK14环境中,一切都很美好,PrintAssembly可以正常运行了。 如果我们在JDK14中同样运行上面的命令,我们会得到下面的结果: 上图说明JDK14中虽然可以正常运行但是结果却不是assembly code,说明在JDK14中还是需要安装hsdis-amd64.dylib才能够得到正确的assembly结果。 注意,JDK14也需要安装hsdis-amd64.dylib才能正确使用。输出过滤默认情况下,PrintAssembly输出的是所有的信息,但是JDK内部的代码我们不可能进行修改,一般来说并不关心他们的assembly输出,如果要指定我们自己编写的方法,可以使用CompileCommand: CompileCommand=print,*MyClass.myMethod prints assembly for just one methodCompileCommand=option,*MyClass.myMethod,PrintOptoAssembly (debug build only) produces the old print command outputCompileCommand=option,*MyClass.myMethod,PrintNMethods produces method dumps例如: ...
JVM系列分享4垃圾回收
JVM系列分享3内存机制
JVM系列分享2方法调用
Java内存区域
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有的区域则是依赖用户线程的启动和结束而建立和销毁。根据Java虚拟机规范的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器,在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的本地Native方法服务 Java堆是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里"几乎"所有的对象实例都在这里分配内存。 方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
JVM学习笔记双亲委派方式
简介本篇包括以下内容: 什么是双亲委派机制。双亲委派机制的处理流程。什么是双亲委派机制。运行原理一个类加载器收到类加载请求,不会自己立刻尝试加载类,而是把请求委托给父加载器去完成,每一层都是如此,所有的加载请求最终都传递到最顶层的引导类加载器进行处理。如果父加载器不存在了,那么尝试判断该类能不能被引导类加载器加载。如果父加载器无法加载,子加载器才会尝试自己加载。为什么要有如此复杂的双亲委派机制防止类的重复加载。保护程序安全,防止核心API被篡改。例:我们自己编写一个java.lang.Object用自己的类加载器进行加载,系统中就会存在多个Object类。双亲委派机制的处理流程。以下是java.lang.Classload.loadClass()方法的实现。`` protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; }}``转换成流程图: ...
JVM学习笔记类加载器ClassLoader
类加载器和类加载过程本篇主要内容:JVM工作结构简图。类加载过程。类加载器(ClassLoader)。自定义类加载器实现步骤。JVM工作结构简图 类加载过程上图中我们看到了字节码文件经过类加载子系统加载到JVM中,那么现在我们在看类加载子系统中发生了什么。先放一张类加载过程的简图。其中包括了三个大步骤,他们分别是: 加载加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象。 通过类的全限定名获取此类的二进制字节流。将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。在内存中生成一个Class对象,作为方法区这个类的各种数据的访问入口。加载通常由类加载器完成,加载类的方式具体有以下几项: 本地资源加载。网络加载。Web Applet。zip压缩包加载。jar,war。运行时计算生成。动态代理技术。其他文件生成。JSP应用。从加密文件中读取,主要是为了防止class文件被反编译。链接链接分为三个步骤: 验证 确保class文件的字节流中包含信息符合虚拟机要求。 主要包括,文件格式验证,元数据验证,字节码验证,符号引用验证。准备 为类变量分配内存,并为变量赋零值。 这里不包括用final修饰的static,因为final在变异的时候就会分配了,准备阶段会显式初始化。 不会为实例变量分配初始化。解析 将常量池内的符号引用变为直接引用。初始化初始化阶段就是执行类构造器方法<clinit>()方法。<clinit>()方法是由javac将类变量赋值动作和静态代码块中的语句合并自动产生的。当类不存在类变量和静态代码块时不会自动生成此方法。一个类只会被加载一次,<clinint》()方法是同步加锁的。(后付测试代码)注:如果想看此方法,可以使用JClassLib软件或IDEA中的JClassLib插件。附一张clinit<>()方法图和同步测试代码。 package cn.lele;public class ClinitTest02 { public static void main(String[] args) { Runnable r = ()->{ System.out.println(Thread.currentThread().getName() + "start"); DeadThread deadThread = new DeadThread(); System.out.println(Thread.currentThread().getName() + "finish"); }; Thread a = new Thread(r,"A"); Thread b = new Thread(r,"B"); a.start(); b.start(); }}class DeadThread { static { if (true) { System.out.println(Thread.currentThread().getName() + "初始化当前类"); while (true) { } } }}图中我们看到在number声明之前我们就可以在同步代码块中为他赋值,这是因为在链接的准备阶段,number变量已经分配了内存空间并且赋予了零值。 ...
面试准备-JVM面试准备
整理一下JVM的面试题,别让自己在面试官面签懵逼了就好。。。1. JVM虚拟机1.1 什么是java虚拟机Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。 1.2 平台无关性一次编译,到处执行(Write Once ,Run Anywhere)。Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。 1.3 JVM 的主要组成部分?及其作用?类加载器(ClassLoader)运行时数据区(Runtime Data Area)执行引擎(Execution Engine)本地库接口(Native Interface)组件的作用:首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。 1.4 引用的分类强引用:GC时不会被回收软引用:描述有用但不是必须的对象,在发生内存溢出异常之前被回收弱引用:描述有用但不是必须的对象,在下一次GC时被回收虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用PhantomReference实现虚引用,虚引用用来在GC时返回一个通知。1.5 垃圾回收机制算法标记 -清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。1.6 JVM性能调优方法设定堆内存大小-Xmx:堆内存最大限制。 设定新生代大小。 新生代不宜太小,否则会有大量对象涌入老年代-XX:NewSize:新生代大小-XX:NewRatio 新生代和老生代占比-XX:SurvivorRatio:伊甸园空间和幸存者空间的占比设定垃圾回收器 年轻代用 -XX:+UseParNewGC 老生代用-XX:+UseConcMarkSweepGC1.7 JVM调优工具常用调优工具分为两类:jdk自带监控工具:jconsole和jvisualvm第三方有:MAT(Memory Analyzer Tool)、GChisto。详细描述为:jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗GChisto,一款专业分析gc日志的工具1.8 么判断对象是否可以被回收引用计数器:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;可达性分析:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。1.9 什么是双亲委派机制当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。 1.10 双亲委派机制的作用防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。1.11 堆栈功能方面:堆是用来存放对象的,栈是用来执行程序的。共享性:堆是线程共享的,栈是线程私有的。空间大小:堆大小远远大于栈。 个人网站http://www.zhouzhaodong.xyz
从main方法分析内存溢出
内存溢出OutOfMemoryError不常遇到,起码没有姨妈空指针异常(NullPointerException)来的那么频繁。现在就用最简单的main方法复现堆内存溢出并做分析。 概念先行JVM内存模型(JMM): 堆,方法区,本地方法栈,虚拟机栈,程序计数器 (后面三个线程共享)栈和堆:栈是运行空间,堆是存储空间,类似于我小米手机的运行内存(RAM)8G和存储空间(ROM)128G。java中基本类型和堆对象的引用存在栈中。堆:堆在JVM中占据了很大的空间,用来存放实例对象,等会儿我们就拿它下手!堆:我当时害怕急了。堆内存分为年轻代和老年代,java8之后没有了永久代。(往细了说年轻代还有伊甸园(eden)和两个幸存区(from、to))当java对象在年轻代存活一段时间经历过N次回收没有被回收掉后(N还可以自己设置),就会进入老年代,在老年代中又经历回收后积累的没有被回收的对象超负荷后就会抛出内存溢出的异常。 图做的有点粗糙,还请见谅! 我把堆内存设置小一点先。在idea的配置 (VM options)加上启动参数 -Xms10m -Xmx20m。运行以下代码,不断的生成People对象并放入集合中防止被回收。 public static void main(String[] args) { List<People> peoples = new ArrayList<>(); int i = 0; while (true) { People abc = new People(); i++; peoples.add(abc); System.out.println(abc.toString() + i); }}结果在生成540217个对象的时候抛出了内存溢出的异常。 ......People{name='null', sex='null'}540216People{name='null', sex='null'}540217Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:265) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231) at java.util.ArrayList.add(ArrayList.java:462) at com.example.demo.DemoMain.main(DemoMain.java:17)开启GC日志添加启动参数,不同的参数打印不同格式的日志。 参数说明-XX:-PrintGC开启GC日志-XX:-PrintGCDetails打印详细信息-XX:+PrintGCDateStamps打印时间-Xloggc:./gclogs.logGC日志的生成路径和日志名称加上后看到的GC日志如下,太多了,我截取了后半段,可以看到短期内GC之后都是Full GC,应该是老年代满了,触发Full GC但是还是有大量的对象不能被回收,最后抛出OOM的异常。年轻代满了会触发GC,也叫Minor GC,老年代满了会触发Full GC,CPU空闲时也会回收垃圾,JVM调优的目的就是减少GC的频率和Full GC的次数。 ...
深入理解Java虚拟机-线程的上下文类加载器
更多JVM内容,欢迎个人网站 https://www.zhoutao123.com 线程上下文类加载器 线程上下文类加载器( Thread Context ClassLoader) 是从JDK1.2 引入的,类Thread 的getContextClassLoader() 与 setContextClassLoader(Classloader var1) 分别用来设置线程的上下文类加载器。如果没有指定线程的上下文的加载器,那么线程将会继承父线程的上下文类加载器。Java 的初始化线程的上下文加载器,可以通过上下文类加载器加载类与资源。 基本的获取和使用方法: public class ContextClassLoader { /** * 每个类都会使用自己的类加载器尝试去加载所依赖的类 * * <p>如果ClassX 依赖了 ClassY ,那么在ClassX的加载器将会在主动引用ClassY 并且ClassY尚未被加载的时候加载ClassY 这个类 */ public static void main(String[] args) { System.out.println(Thread.currentThread().getContextClassLoader()); System.out.println(Thread.class.getClassLoader()); }} 从 JDBC 说起在以前学习JDBC 的时候我们最深刻的印象应该是 Class.forName("xxxxxx"); 简单的伪代码如下图: import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.Statement;public class DbUtil { public static final String URL = "jdbc:mysql://localhost:3306/db"; public static final String USER = "root"; public static final String PASSWORD = "123456"; public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT user_name, age FROM student"); } }}从上面的代码中,import导入的 jdbc 的相关类可以看到,他们均来自于 java.sql 包下,根据类的双亲委派机制可以非常清晰的知道,这些接口肯定是来自于 系统类加载器加载。那么具体的其实现类使用我们的应用类加载器加载,所以问题出现 其中的JDBC 标准是由启动器类加载器加载,而具体的实现的类是由系统类加载器加载,所以这就会导致启动类加载器加载的JDBC标准类无法找到子加载器加载的JDBC实现 ...
深入理解Java虚拟机Launcher-源码分析
更多JVM文章,欢迎访问个人网站 https://www.zhoutao123.com Launcher 源码分析拓展类加载器是Launcher 类的一个内部类,为了学习应用类加载器和拓展类加载器的实现细节,我们需要对Launcher 类的源码进行学习。 由于 Launcher 的代码属于 Oracle 闭源的代码部分,这一块代码的分析属于 IDEA 反编译出来的,所以部分变量的命名为var1, var2 这种奇怪的形式 1、获取系统类加载器想要获取系统类加载器,可以通过 ClassLoader.getSystemClassLoader() 获取到, 这个方法中实现了对系统类加载的创建,他的说明文档中也阐述了部分细节。 /** * Returns the system class loader for delegation. This is the default * delegation parent for new <tt>ClassLoader</tt> instances, and is * typically the class loader used to start the application. * * <p> This method is first invoked early in the runtime's startup * sequence, at which point it creates the system class loader and sets it * as the context class loader of the invoking <tt>Thread</tt>. * * <p> The default system class loader is an implementation-dependent * instance of this class. * * <p> If the system property "<tt>java.system.class.loader</tt>" is defined * when this method is first invoked then the value of that property is * taken to be the name of a class that will be returned as the system * class loader. The class is loaded using the default system class loader * and must define a public constructor that takes a single parameter of * type <tt>ClassLoader</tt> which is used as the delegation parent. An * instance is then created using this constructor with the default system * class loader as the parameter. The resulting class loader is defined * to be the system class loader.大致翻译一下: ...
模拟实战排查堆内存溢出javalangOutOfMemoryError-Java-heap-space问题
前言:模拟实战中排查堆内存溢出(java.lang.OutOfMemoryError: Java heap space)的问题。堆内存溢出的原因:一般都是创建了大量的对象,这些对象一直被引用着,无法被GC垃圾回收掉,最终导致堆内存被占满,没有足够的空间存放新创建的对象时,就会出现堆内存溢出问题。 在实际的业务场景中出现内存溢出的问题,排查起来一般是十分困难繁琐的,本文将通过结合一个简单的实例来阐述排查的具体思路和步骤。 准备:注意:在实际场景中,一般都是部署在Linux服务器中的项目报出内存溢出的问题;为了尽可能还原出实际场景,本文也是将提前编写好的可以触发内存溢出的代码并打包成可运行的Jar包,然后放到服务器中执行的。1、准备可导致内存溢出的代码:// 创建一个Java类public class OutOfMemory { private String test; public OutOfMemory(String test){ this.test = test; } }// 模拟内存溢出的发生public class TestOOM { public static void main(String[] args) { List<OutOfMemory> list = new ArrayList<OutOfMemory>(); while(true){ /** * 无限创建OutOfMemory对象,直至将堆空间占满,并且创建的OutOfMemory对象一直被list集合对象引用着, * 导致GC也无法回收,最终出现堆内存溢出问题 */ list.add(new OutOfMemory("5656")); System.out.println("5656"); } }}代码编写完成后,使用开发工具导出可运行的Jar包(TestOOM.jar)2、准备Linux服务器可以直接使用centos或者Red Hat等都可以;实战:1、将可运行的Jar包放到服务器中执行:①、可使用xshell、xftp工具将可运行的Jar包(Jar包叫:TestOOM.jar)放入到服务器中;②、使用命令执行Jar包;命令: java -Xms40m -Xmx70m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/tmp -jar TestOOM.jar 注意:为了尽快模拟发生堆内存溢出,所以在启动Jar包时,设置了一些参数;参数解析: 1)、 -Xms40m 初始堆大小设置为40m 2)、 -Xmx70m 最大堆大小设置为70m 3)、 -XX:+HeapDumpOnOutOfMemoryError 出现堆内存溢出时,自动导出堆内存 dump 快照 4)、 -XX:HeapDumpPath=/usr/tmp 设置导出的堆内存快照的存放地址为 /usr/tmp ...