对于JVM的理解

几大模块
  1. 关于方法区

Java7和Java8内存结构的不同主要体现在方法区的实现

方法区是java虚拟机规范中定义的一种概念上的区域,不同的厂商可以对虚拟机进行不同的实现。 我们通常使用的Java SE都是由Sun JDK和OpenJDK所提供,这也是应用最广泛的版本。而该版本使用的VM就是HotSpot VM。通常情况下,我们所讲的java虚拟机指的就是HotSpot的版本。

JDK 1.7中,绝大部分Java程序员应该都见过java.lang.OutOfMemoryError: PremGen space异常。这里的PermGen space其实指的就是方法区。不过方法区和PermGen space又有着本质的区别。前者是JVM的规范,而后者则是JVM规范的一种实现

JDK 1.7中,存储在永久代的部分数据就已经转移到Java Heap或者Native Heap。 但永久代仍存在于JDK 1.7中,并没有完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)、类的静态变量转移到了Java heap。

JDK1.8和JDK1.7的jvm内存最大的区别是, 在1.8中方法区是由元空间(元数据区)来实现的,常量池移到堆中。 1.8中方法区的实现换成了元空间,而是在本地内存中,新加入元数据区(元空间). 元空间「运行时常量池」: 存储.class 信息, 类的信息,方法的定义,静态变量等.

而字符串常量池放到堆里存储「调用intern()(jdk1.8): 如果字符串常量池里没有该变量,就创建引用指向堆里面的变量地址」

关于元空间:https://www.cnblogs.com/duanxz/p/3520829.html

  • 对于元数据区配置
-XX:MetaspaceSize,class metadata的初始空间配额,以bytes为单位,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当的降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize(如果设置了的话),适当的提高该值。
-XX:MaxMetaspaceSize,可以为class metadata分配的最大空间。默认是没有限制的。
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为class metadata分配空间导致的垃圾收集。
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为class metadata释放空间导致的垃圾收集。
class文件分析
类加载机制

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的加载机制。

类加载过程

类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示的对象组件。在Java中,类装载器把一个类装入JVM中,要经过以下步骤: load—>link(check,prepare-parse)—>initialization—>using—>unloading

装载:查找和导入Class文件;
链接:把类的二进制数据合并到JRE中;
  (a)校验:检查载入Class文件数据的正确性;
  (b)准备:给类的静态变量分配存储空间;
  (c)解析:将符号引用转成直接引用;
 初始化:对类的静态变量,静态代码块执行初始化操作
  • 加载阶段

    1. 通过一个类的全限定名称来获取定义此类的二进制字节流。

    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

    3. 在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口

  • 验证阶段

    确保Class文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能会有所不同,但大致都会完成以下四个阶段的验证:文件格式的验证、元数据的验证、字节码验证和符号引用验证。

  1. 文件格式的验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理,该验证的主要目的是保证输入的字节流能正确地解析并存储于方法区之内。经过该阶段的验证后,字节流才会进入内存的方法区中进行存储,后面的三个验证都是基于方法区的存储结构进行的。
  2. 元数据验证:对类的元数据信息进行语义校验(其实就是对类中的各数据类型进行语法校验),保证不存在不符合Java语法规范的元数据信息。
  3. 字节码验证:该阶段验证的主要工作是进行数据流和控制流分析,对类的方法体进行校验分析,以保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为。
  4. 符号引用验证:这是最后一个阶段的验证,它发生在虚拟机将符号引用转化为直接引用的时候(解析阶段中发生该转化,后面会有讲解),主要是对类自身以外的信息(常量池中的各种符号引用)进行匹配性的校验。
  • 准备阶段

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。

  1. 这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
  2. 这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
  • 解析阶段

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

分派指的是确定方法调用版本,又分为静态分派和动态分派。 静态分派是在编译期即根据变量的静态类型即可确定方法调用版本,静态分派的典型应用是方法重载; 动态分派是需要在运行时根据实际类型才能确定方法调用版本,静态分派的典型应用是方法重写;

  • 初始化阶段

  • 卸载阶段

一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期

加载器和Class对象:在类加载器的内部实现中,用一个Java集合来存放所加载类的引用。 另一方面,一个Class对象总是会引用它的类加载器。调用Class对象的getClassLoader()方法,就能获得它的类加载器。由此可见,Class实例和加载它的加载器之间为双向关联关系。

类、类的Class对象、类的实例对象:一个类的实例总是引用代表这个类的Class对象。在Object类中定义了getClass()方法,这个方法返回代表对象所属类的Class对象的引用。此外,所有的Java类都有一个静态属性class,它引用代表这个类的Class对象。

由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。 Java虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。 Java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的。 由用户自定义的类加载器加载的类是可以被卸载的。

符号引用和直接引用
  1. 符号引用:以一组符号来描述所引用的目标,符号引用可以是任何形式的字面量,符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经在内存中。
  2. 直接引用:直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般都不相同,如果有了直接引用,那引用的目标必定已经在内存中存在。
双亲委派模型

如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。

  1. 子类先委托父类加载
  2. 父类加载器有自己的加载范围,范围内没有找到,则不加载,并返回给子类
  3. 子类在收到父类无法加载的时候,才会自己去加载

双亲委派模型的破坏

当高层提供了统一接口让低层去实现,同时又要是在高层加载(或实例化)低层的类时,必须通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。

上下文类加载器默认情况下就是Application ClassLoader。

服务加载器在Java SE 6后才开始作为公共API出现,作为一个简单的服务提供者加载设施。ServiceLoader也像ClassLoader一样,能装载类文件,但是使用时有区别,具体区别如下:

  1. ServiceLoader装载的是一系列有某种共同特征的实现类(例如实现同一个接口),而ClassLoader是个万能加载器;
  2. ServiceLoader装载时需要特殊的配置(配置文件中标明要加载的类URL),使用时也与ClassLoader有所区别;
  3. ServiceLoader实现了Iterator接口;

ServiceLoader的缺点:1.ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。2.获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。3.不是单例。

tomcat 类加载器

  • commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
  • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
  • webappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;
垃圾回收
垃圾回收算法
  1. 标记-清除算法,对根集合进行扫描,对存活的对象进行标记。标记完成后,再对整个空间内未被标记的对象扫描,进行回收。
  2. 复制算法,将内存区域划分成相同的两个内存块。每次仅使用一半的空间,JVM生成的新对象放在一半空间中。当一半空间用完时进行GC,把可到达对象复制到另一半空间,然后把使用过的内存空间一次清理掉。
  3. 标记-整理算法,进行对象的标记,但后续不直接对可回收对象进行清理,而是将所有的存活对象往一端空闲空间移动,然后清理掉端边界以外的内存空间,解决了标记-清理算法存在的内存碎片问题。
  4. 分代收集算法,根据对象的存活周期将内存划分为几块。一般包括年轻代、老年代,对于新生代,每次GC时都有大量的对象死亡,只有少量对象存活。考虑到复制成本低,适合采用复制算法。因此有了From Survivor和To Survivor区域。对于老年代,因为对象存活率高,没有额外的内存空间对它进行担保。因而适合采用标记-清理算法和标记-整理算法进行回收。
垃圾回收器

参考:https://crowhawk.github.io/2017/08/15/jvm_3/

新生代收集器

  1. 垃圾回收器 (Serial Garbage Collector),冻结所有应用程序线程,使用单个垃圾回收线程来进行垃圾回收工作。 使用方法:-XX:+UseSerialGC 串行收集

  2. ParNew收集器,Serial收集器的多线程版本,新生代收集器,串行收集,除了Serial收集器外,只有它能和CMS收集器(Concurrent Mark Sweep)配合工作,CMS收集器是JDK 1.5推出的一个具有划时代意义的收集器。 使用方法:-XX:+UseParNewGC ParNew收集器

多CPU环境下,随着CPU的数量增加,它对于GC时系统资源的有效利用是很有好处的。它默认开启的收集线程数与CPU的数量相同,在CPU非常多的情况下可使用**-XX:ParallerGCThreads**参数设置

  1. Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数XX:+UseAdaptiveSizePolicy来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法。并行收集。停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。Parallel Scavenge收集器无法与CMS收集器配合使用,所以在JDK 1.6推出Parallel Old之前,如果新生代选择Parallel Scavenge收集器,老年代只有Serial Old收集器能与之配合使用。

老年代收集器

  1. Serial Old 是 Serial收集器的老年代版本,它同样是一个单线程收集器,使用**“标记-整理”(Mark-Compact)**算法。串行收集。
  2. Parallel Old收集器,是Parallel Scavenge收集器的老年代版本,使用多线程和**“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供的,在此之前,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old以外别无选择,所以在Parallel Old诞生以后,“吞吐量优先”收集器终于有了比较名副其实的应用组合,在注重吞吐量以及CPU资源敏感**的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。并行收集。
  3. CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。 过程分为:初始标记(CMS initial mark),并发标记(CMS concurrent mark),重新标记(CMS remark),并发清除(CMS concurrent sweep), 其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。 由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。 -XX:+UseConcMarkSweepGC 使用CMS收集器。

G1收集器,G1垃圾回收器是针对于大heap的垃圾回收器,-XX:+UseG1GC

在G1之前的其他收集器进行收集的范围都是整个新生代或者老生代,而G1不再是这样。G1在使用时,Java堆的内存布局与其他收集器有很大区别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,而都是一部分Region(不需要连续)的集合

  • 并行与并发 G1 能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短“Stop The World”停顿时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。

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

  • 空间整合 G1从整体来看是基于**“标记-整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”**算法实现的。这意味着G1运行期间不会产生内存空间碎片,收集后能提供规整的可用内存。此特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。

  • 可预测的停顿 这是G1相对CMS的一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了降低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在GC上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。

G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

G1把Java堆分为多个Region,就是“化整为零”。但是Region不可能是孤立的,一个对象分配在某个Region中,可以与整个Java堆任意的对象发生引用关系。在做可达性分析确定对象是否存活的时候,需要扫描整个Java堆才能保证准确性,这显然是对GC效率的极大伤害。为了避免全堆扫描的发生,虚拟机为G1中每个Region维护了一个与之对应的Remembered Set。虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中(在分代的例子中就是检查是否老年代中的对象引用了新生代中的对象),如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。

G1收集器的运作大致可划分为以下几个步骤:

  • 初始标记(Initial Marking) 仅仅只是标记一下GC Roots 能直接关联到的对象,并且修改TAMS(Nest Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可以的Region中创建对象,此阶段需要停顿线程,但耗时很短。
  • 并发标记(Concurrent Marking) 从GC Root 开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时较长,但可与用户程序并发执行。
  • 最终标记(Final Marking) 为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。
  • 筛选回收(Live Data Counting and Evacuation) 首先对各个Region中的回收价值和成本进行排序,根据用户所期望的GC 停顿是时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。
java的几种引用类型

当内存空间还足够的时候,这些对象能够保留在内存空间中;如果当内存空间在进行了垃圾收集之后还是非常紧张,则可以抛弃这些对象。基于这种特性,可以满足很多系统的缓存功能的使用场景。

  1. StrongReference,如果一个对象具备强引用,垃圾回收器绝不会回收它。当内存空间不足,JVM宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会出现回收具有强引用的对象来解决内存不足的情况。
  2. SoftReference,对于软引用关联着的对象,在JVM应用即将发生内存溢出异常之前,将会把这些软引用关联的对象列进去回收对象范围之中进行第二次回收。如果这次回收之后还是没有足够的内存,才会抛出内存溢出异常。
  3. WeakReference,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,简言之就是:一旦发生GC必定回收被弱引用关联的对象,不管当前的内存是否足够。也就是弱引用只能活到下次GC之时。
  4. PhantomReference,一个对象是否关联到虚引用,完全不会影响该对象的生命周期,也无法通过虚引用来获取一个对象的实例(PhantomReference覆盖了Reference#get()并且总是返回null)。为对象设置一个虚引用的唯一目的是:能在此对象被垃圾收集器回收的时候收到一个系统通知。在NIO中,就运用了虚引用管理堆外内存。
关于finalize

在GC算法中,有标记-清除、标记-整理算法,他们的标记过程,标记分为两个阶段:

第一次标记:如果对象被判定为不可达对象,也就是从GC ROOTS开始搜索,没有一条引用链可达,这个对象就会被第一次标记,等待被回收。

第二次标记:将第一次标记的对象再进行判定,分析这个对象是否有必要执行**finalize()**方法,如果有必要执行,就从待回收的集合中剔除,放置在一个叫F-Queue的队列之中,并且稍后由一个优先级低的Finalizer线程去取该队列的元素,“尝试执行"元素的finalize()方法。 是否有必要执行,有两点:

对象没有覆盖继承自Object类的finalize()方法,如果没有重写这个方法,没必要执行。

对象的finalize()方法已经被JVM调用过,已经调用过了就不会重复执行。

jvm怎么判断哪些对象应该回收呢

Java虚拟机没有采用引用计数法。它采用的是可达性分析算法。

  1. 引用计数算法,在对象中添加一个引用计数器,每当一个地方引用它时,计数器就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。不能解决循环依赖的问题。
  2. 从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。所以此对象就是可以被回收的对象。
  3. 哪些对象可以作为根对象?
虚拟机栈(栈桢中的本地变量表)中的引用的对象。
方法区中的类静态属性引用的对象。
方法区中的常量引用的对象。
本地方法栈中JNI的引用的对象。
什么情况下触发full gc

https://blog.csdn.net/Hollake/article/details/90484027

多说一句:当Eden区满时,触发Minor GC。

  1. System.gc()方法的调用,建议JVM进行Full GC,但是注意这只是建议,JVM执行不执行是另外一回事儿,不过在大多数情况下会增加Full GC的次数,导致系统性能下降,一般建议不要手动进行此方法的调用,可以通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。
  2. 在Survivor区域的对象满足晋升到老年代的条件时,晋升进入老年代的对象大小大于老年代的可用内存,这个时候会触发Full GC。
  3. 统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间。
  4. 堆中产生大对象超过阈值,这个参数可以通过-XX:PretenureSizeThreshold进行设定,大对象或者长期存活的对象进入老年代,典型的大对象就是很长的字符串或者数组,它们在被创建后会直接进入老年代,虽然可能新生代中的Eden区域可以放置这个对象,在要放置的时候JVM如果发现老年代的空间不足时,会触发GC。
  5. JVM如果判断老年代没有做足够的连续空间来放置大对象,那么就会引起Full GC,例如老年代可用空间大小为200K,但不是连续的,连续内存只要100K,而晋升到老年代的对象大小为120K,由于120>100的连续空间,所以就会触发Full GC。

Survivor区域对象晋升到老年代有两种情况:

一种是给每个对象定义一个对象计数器,如果对象在Eden区域出生,并且经过了第一次GC,那么就将他的年龄设置为1,在Survivor区域的对象每熬过一次GC,年龄计数器加一,等到到达默认值15时,就会被移动到老年代中,默认值可以通过-XX:MaxTenuringThreshold来设置。 另外一种情况是如果JVM发现Survivor区域中的相同年龄的对象占到所有对象的一半以上时,就会将大于这个年龄的对象移动到老年代,在这批对象在统计后发现可以晋升到老年代,但是发现老年代没有足够的空间来放置这些对象,这就会引起Full GC。

常用的CLI
  • 看busy thread 步骤:top -Hp 8 ;printf 0x%x 121; jstack pid | grep 0x88(也就是nid)

  • jps 显示指定系统内所有的HotSpot虚拟机进程,jps -lmv

  • jstat 用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。jstat -gc 25446

  • jmap 用于生成heap dump文件,如果不使用这个命令,可以使用-XX:+HeapDumpOnOutOfMemoryError参数来让虚拟机出现OOM的时候,自动生成dump文件。jmap不仅能生成dump文件,还可以查询finalize执行队列、Java堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。

    jmap -dump:format=b,file=dump.dprof 25446,输出.dprof文件后,使用MAT(Memory Analyzer Tool)分析工具进行分析

    jmap -heap 25446,打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况,可以用此来判断内存目前的使用情况以及垃圾回收情况

  • jhat 用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看。在此要注意,一般不会直接在服务器上进行分析,因为jhat是一个耗时并且耗费硬件资源的过程,一般把服务器生成的dump文件复制到本地或其他机器上进行分析。常用分析方法是用各平台通用的MAT进行分析。

  • jstack 生成java虚拟机当前时刻的线程快照。 线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。 jstack -l 25446 | more

  • jinfo 实时查看和调整虚拟机运行参数 jinfo -flags 25446

  • jcmd 一个多功能的根据,汇集了jps、jmap、jstack、jinfo、jstat等多个命令的功能,也就是我们之前要使用多个JVM命令才能达到的目的,现在只需要使用一个jcmd命令即可

  • jmc Java Mission Control 一体化的可视化工具,而且还可以在程序运行期会不断实时的呈现变更的数据

内存模型

不同计算机操作系统对内存模型操作不一样,这时候就要有统一的规范来完成操作。所以就要通过JAVA内存模型(Java Memory Model,JMM)。

是一种JAVA虚拟机规范。

屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。

  1. 定义程序中各种变量的访问规则,关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节

  2. 变量根据是否可以共享划分为:线程私有和线程公有。

    线程私有:局部变量与方法参数

    线程公有:实例字段、静态字段和构成数组对象的元素

关于volatile
Java内存模型中定义的8种工作内存与主内存之间的原子操作

lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占的状态。
unlock(解锁):作用于主内存的变量,把一个处于锁定的变量释放出来,释放变量才可以被其他线程锁定。
read(读取):作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
load(载入):作用于***工作内存***的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用):作用于***工作内***存种的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
assign(赋值):作用于***工作内存***中的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储):作用于***工作内存***的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用
write(写入):作用于主内存的变量,它把store操作从工作内存中得到的值放入主内存的变量中。
  1. 可见性 核心:volatile变量对对象的操作更严格: read-load-use 和 assign-store-write 两个不可分割的原子操作
  2. 禁止指令重排序
  3. Java语言中有一个“先行发生”(Happens-Before)的原则
Java内存模型下一些天然的先行发生关系,这些先行发生关系无须任何同步器协助就已经存在,可以在编码中直接使用。存在8中规则:

程序次序规则(Program Order Rule):在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。注意,这里说的是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。
管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是“同一个锁”,而“后面”是指时间上的先后。
volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是指时间上的先后。
线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。
线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread::join()方法是否结束、Thread::isAlive()的返回值等手段检测线程是否已经终止执行。
线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread::interrupted()方法检测到是否有中断发生。
对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。
堆外内存
  1. 在使用堆内内存(on-heap memory)的时候,完全遵守JVM虚拟机的内存管理机制,采用垃圾回收器(GC)统一进行内存管理,GC会在某些特定的时间点进行一次彻底回收,也就是Full GC,GC会对所有分配的堆内内存进行扫描,在这个过程中会对JAVA应用程序的性能造成一定影响,还可能会产生Stop The World。
常用参数配置
  1. -verbose:class 跟踪类的加载和卸载
  2. -XX:+TraceClassLoading 跟踪类的加载
  3. -XX:+TraceClassUnloading 跟踪类的卸载
好玩的
-- 配合各种Escape Sequence
string.rep('hahaha\t', 10)
相关介绍
参考

> 可在下面留言(需要有 GitHub 账号)