当前位置 : 首页 » 文章分类 :  开发  »  Java面试准备-(04)JVM

Java面试准备-(04)JVM

Java面试准备笔记


jvm 虚拟机

《深入理解Java虚拟机》作者周志明的博客
http://icyfenix.iteye.com/

随笔分类 - Java虚拟机
http://www.cnblogs.com/xiaoxi/category/961347.html

专栏 - 深入java虚拟机
http://blog.csdn.net/column/details/java-vm.html

专栏 - java面试-深入理解JVM
https://blog.csdn.net/qq_34173549/article/category/7473988

JVM包括哪几部分?(哪几个子系统)

1、类加载子系统
2、内存管理子系统(内存划分、分配、垃圾回收)
3、执行子系统,包括解释执行和编译执行(即时编译JIT)


JVM内存管理

主内存和线程本地内存

JMM主要是为了规定了线程和内存之间的一些关系。

根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。

Java内存模型规定了所有的变量都存储在主内存中。每个线程还有自己的工作内存,线程的工作内存中保存了被该线程中使用到的变量的主内存拷贝副本,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。

线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。

线程若要对某变量进行操作,必须经过一系列步骤:首先从主存复制/刷新数据到工作内存,然后执行代码,进行引用/赋值操作,最后把变量内容写回Main Memory。Java语言规范(JLS)中对线程和主存互操作定义了6个行为,分别为load,save,read,write,assign和use,这些操作行为具有原子性,且相互依赖,有明确的调用先后顺序。

对于普通变量,如果一个线程中那份主内存变量值的拷贝更新了,并不能马上反应在其他变量中
A,B两条线程直接读or写的都是线程的工作内存!而A、B使用的数据从各自的工作内存传递到同一块主内存的这个过程是有时差的,或者说是有隔离的!通俗的说他们之间看不见!也就是之前说的一个线程中的变量被修改了,是无法立即让其他线程看见的!如果需要在其他线程中立即可见,需要使用 volatile 关键字。

哪些变量分配在堆上哪些变量分配在栈上

所有基本类型的local变量( boolean, byte, short, char, int, long, float, double)全都被存储在线程栈里,而且对其他线程是不可见的,一个线程可能会传递一份基本类型的变量值的一份拷贝给另一个线程,但是自己本身的变量是不能共享的,只能传递拷贝。

堆中存储着java程序中new出来的对象,不管是哪个线程new出来的对象,都存在一起,而且不区分是哪个线程的对象。这些对象里面也包括那些原始类型的对象版本(e.g. Byte, Integer, Long etc.). 不管这个对象是分配给本地变量还是成员变量,最终都是存在堆里。

一个原始数据类型的本地变量将完全被存储在线程栈中。
本地变量也可以是指向对象的引用,在这种情况下,本地变量存在线程栈上,但是对象本身是存在堆上。
一个对象可能包含方法这些方法同时也会包含本地变量,这些本地变量也是存储在线程栈上面,即使他们所属于的对象和方法是存在堆上的。
一个对象的成员变量是跟随着对象本身存储在堆上的,不管成员变量是原始数据类型还是指向对象的引用。
静态的类变量一般也存储在堆上,根据类的定义。
存储在堆上的对象可以被所有的线程通过引用来访问。当一个线程持有一个对象的引用时,他同时也就可以访问这个对象的成员变量了。如果两个线程同时调用同一个对象的一个方法,他们就会都拥有这个对象的成员变量,但是每一个线程会享有自己私有的本地变量。

深度解析Java多线程的内存模型(剖析的非常透彻深入)
https://www.jianshu.com/p/a3f9f2c3ecf8


JVM运行时内存划分(jdk8之前)

JVM内存管理——JAVA语言的内存管理概述 - zuoxiaolong(左潇龙)
http://www.cnblogs.com/zuoxiaolong/p/jvm1.html

JVM的内存区域划分 - 平凡希
http://www.cnblogs.com/xiaoxi/p/6421526.html

java面试-深入理解JVM(一)——JVM内存模型
https://blog.csdn.net/qq_34173549/article/details/79612540

Java虚拟机(JVM)内部定义了程序在运行时需要使用到的内存区域


JVM内存模型

之所以要划分这么多区域出来是因为这些区域都有自己的用途,以及创建和销毁的时间。有些区域随着虚拟机进程的启动而存在,有的区域则依赖用户线程的启动和结束而销毁和建立。图中绿色部分就是所有线程之间共享的内存区域,而其余部分则是线程运行时独有的数据区域

线程独有内存区域(随线程启动创建)

对于线程独有的这部分内存,都是随着线程的启动而创建,而当线程被销毁时,内存也就随之释放。这一部分内存,不需要垃圾搜集器的管理,而是JAVA虚拟机来主动管理,每当一个线程被创建的时候,JAVA虚拟机就会为其分配相应的PC寄存器和JAVA虚拟机栈,如果需要的话,还会有本地方法栈。相应的,当一个线程被销毁的时候,JAVA虚拟机也会将这个线程所占有的内存全部释放。

PC程序计数器(线程独有)

这块内存区域很小,它是当前线程所执行的字节码的行号指示器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。

它的作用就是用来支持多线程,线程的阻塞、恢复、挂起等一系列操作,直观的想象一下,要是没有记住每个线程当前运行的位置,又如何恢复呢。依据这一点,每一个线程都有一个PC寄存器,也就是说PC寄存器是线程独有的。

VM Stack虚拟机栈(线程独有)

Java栈也称作虚拟机栈(Java Vitual Machine Stack),也就是我们常常所说的栈

Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。

讲到这里,大家就应该会明白为什么在使用递归方法的时候容易导致栈内存溢出的现象了以及为什么栈区的空间不用程序员去管理了

栈帧中包括下列内容:

  • 局部变量表(Local Variables):用来存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。
  • 操作数栈(Operand Stack)
  • 指向当前方法所属的类的运行时常量池的引用(Reference to runtime constant pool):因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。
  • 方法返回地址(Return Address):当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。
  • 一些额外的附加信息。

NATIVE METHOD STACK本地方法栈(线程独有)

用来支持native方法的执行

在JVM规范中,并没有对本地方发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。


线程间共享内存区域(随虚拟机启动创建)

相对于线程独有的那部分内存,全局共享的这部分内存更加难以处理,不过这只是针对于虚拟机的实现来说,因为这一部分内存是要实现自动内存管理系统(GC)的。

全局共享的这部分内存(以下简称堆),内存分配主要是由程序员显示的使用new关键字来触发的,至于new出来的这部分内存在哪分配,如何分配,则是JAVA虚拟机来决定。而这部分内存的释放,则是由自动内存管理系统(以下简称GC)来管理的。

HEAP堆(新生代+老年代)

这一部分是JAVA内存中最重要的一部分,之所以说是最重要的一部分,并不是因为它的重要性,而是指作为开发人员最应该关注的一部分。

它随着JAVA虚拟机的启动创建,储存着所有对象实例以及数组对象,而且内置了“自动内存管理系统”,也就是我们常说的垃圾搜集器(GC)。JAVA堆中的内存释放是不受开发人员控制的,完全由JAVA虚拟机一手操办。对于JAVA虚拟机如何实现垃圾搜集器,JAVA虚拟机规范没有明确的规定,也正因如此,我们平时使用的JAVA虚拟机中提供了许多种垃圾搜集器,它们采用不同的算法以及实现方式,已满足多方面的性能需求。

由于现在垃圾收集器采用的基本都是分代收集算法,所以堆还可以细分为新生代和老年代,再细致一点还有Eden区、From Survivior区、To Survivor区。

METHOD AREA方法区(持久代)

这块区域用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,虚拟机规范是把这块区域描述为堆的一个逻辑部分的。

它与JAVA堆的区别除了存储的信息与JAVA堆不一样之外,最大的区别就是这一部分JAVA虚拟机规范不强制要求实现自动内存管理系统(GC)。

从上面提到的分代收集算法的角度看,HotSpot中,方法区≈永久代。不过JDK 7之后,我们使用的HotSpot应该就没有永久代这个概念了,会采用Native Memory来实现方法区的规划了。

方法区中有一部分叫RUNTIME CONSTANT POOL,运行时常量池,用于存放编译期间生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中,另外翻译出来的直接引用也会存储在这个区域中。

方法区和永久代的区别?

方法区(Method Area)是java虚拟机规范中的概念,表示用于存储类信息、常量、静态变量、JIT即时编译后代码等数据的内存区域。具体放在哪里,不同的虚拟机实现可以放在不同的地方。

永久代(Permanent Generation)是Hotspot虚拟机中特有的概念,Hotspot用永久代来实现方法区。别的虚拟机如BEA JRockit、IBM J9等都没有永久代。

在Java 8中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——元空间(Metaspace),‑XX:MaxPermSize 参数失去了意义,取而代之的是-XX:MaxMetaspaceSize。

方法区的Class信息,又称为永久代,是否属于Java堆? - 毛海山的回答 - 知乎
https://www.zhihu.com/question/49044988/answer/113961406


jdk7将永久代的常量移到堆中

从JDK7开始永久代的移除工作,贮存在永久代的一部分数据已经转移到了Java Heap或者是Native Heap。但永久代仍然存在于JDK7,并没有完全的移除:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。

永久代在JDK8中被完全的移除了。所以永久代的参数-XX:PermSize和-XX:MaxPermSize也被移除了。

字符串常量溢出测试(如何写一个方法区溢出的代码?)

通过一段代码来比较 JDK 1.6 与 JDK 1.7及 JDK 1.8 的区别,以字符串常量为例:

public class StringOomMock {
    static String  base = "string";
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i=0;i< Integer.MAX_VALUE;i++){
            String str = base + base;
            base = str;
            list.add(str.intern());
        }
    }
}

JDK 1.6中,报错java.lang.OutOfMemoryError: PermGen space
JDK 1.7中,报错java.lang.OutOfMemoryError: Java heap space
JDK 1.8中,报错java.lang.OutOfMemoryError: Java heap space,此外还会提示PermSize和MaxPermSize参数已失效。

Java 8: 从永久代(PermGen)到元空间(Metaspace)
https://blog.csdn.net/zhushuai1221/article/details/52122880


jdk8中的内存划分

为什么废弃永久代?(与其他vm兼容/内存限制)

1、移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。
与 Oracle JRockit 和 IBM JVM类似,JDK 8.HotSpot JVM开始使用本地化的内存存放类的元数据,这个空间叫做元空间(Metaspace)。

2、由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen
一 个好的消息是意味着java.lang.OutOfMemoryError: PermGen的空间问题将不复存在,并且不再需要调整和监控这个内存空间,虽然还没有那么快。当这个变化被默认执行的时候,我们会发现你任然需要担心类的元数据的内存占用率的问题,所以请记住这个新的特性并不会奇迹般的消除类和类加载器的内存泄漏。而是你需要使用一些不同的方式和学习新名词来追查这些问题。

元空间和永久代的区别?

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过参数来指定元空间的大小。

元空间的容量

默认情况下,类元数据分配受到可用的本机内存容量的限制(容量依然取决于你使用32位JVM还是64位操作系统的虚拟内存的可用性)。

一个新的参数 (MaxMetaspaceSize)可以使用。允许你来限制用于类元数据的本地内存。如果没有特别指定,元空间将会根据应用程序在运行时的需求动态设置大小。

元空间配置参数

jdk8中,-XX:PermSize, -XX:MaxPermSize参数已移除,代替他的是元空间的配置参数。

-XX:MetaspaceSize
初始化的Metaspace大小,控制元空间发生GC的阈值。GC后,动态增加或降低MetaspaceSize。在默认情况下,这个值大小根据不同的平台在12M到20M浮动。使用Java -XX:+PrintFlagsInitial命令查看本机的初始化参数

-XX:MaxMetaspaceSize
限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。

元空间的垃圾回收

如果类元数据的空间占用达到参数“MaxMetaspaceSize”设置的值,将会触发对死亡对象和类加载器的垃圾回收。

为了限制垃圾回收的频率和延迟,适当的监控和调优元空间是非常有必要的。元空间过多的垃圾收集可能表示类,类加载器内存泄漏或对你的应用程序来说空间太小了。

JDK8-废弃永久代(PermGen)迎来元空间(Metaspace)
https://www.cnblogs.com/yulei126/p/6777323.html

JAVA8 JVM的变化: 元空间(Metaspace)
https://blog.csdn.net/bigtree_3721/article/details/51248377


堆内存分配策略

TLAB(内存分配的线程安全考虑)

首先讲讲什么是TLAB。内存分配的动作,可以按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。哪个线程需要分配内存,就在哪个线程的TLAB上分配。虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。这么做的目的之一,也是为了并发创建一个对象时,保证创建对象的线程安全性。TLAB比较小,直接在TLAB上分配内存的方式称为快速分配方式,而TLAB大小不够,导致内存被分配在Eden区的内存分配方式称为慢速分配方式。

一、对象优先在Eden区分配

对象通常在新生代的Eden区进行分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC,与Minor GC对应的是Major GC、Full GC。

Minor GC:指发生在新生代的垃圾收集动作,非常频繁,速度较快。
Major GC:指发生在老年代的GC,出现Major GC,经常会伴随一次Minor GC,同时Minor GC也会引起Major GC,一般在GC日志中统称为GC,不频繁。
Full GC:指发生在老年代和新生代的GC,速度很慢,需要Stop The World。

二、大对象直接进入老年代

需要大量连续内存空间的Java对象称为大对象,大对象的出现会导致提前触发垃圾收集以获取更大的连续的空间来进行大对象的分配。虚拟机提供了-XX:PretenureSizeThreadshold参数来设置大对象的阈值,超过阈值的对象直接分配到老年代。

三、长期存活的对象进入老年代

每个对象有一个对象年龄计数器,与前面的对象的存储布局中的GC分代年龄对应。对象出生在Eden区、经过一次Minor GC后仍然存活,并能够被Survivor容纳,设置年龄为1,对象在Survivor区每次经过一次Minor GC,年龄就加1,当年龄达到一定程度(默认15),就晋升到老年代,虚拟机提供了-XX:MaxTenuringThreshold来进行设置。

四、动态对象年龄判断

对象的年龄到达了MaxTenuringThreshold可以进入老年代,同时,如果在survivor区中相同年龄所有对象大小的总和大于survivor区的一半,年龄大于等于该年龄的对象就可以直接进入老年代。无需等到MaxTenuringThreshold中要求的年龄。

五、老年代空间分配担保

冒险是指经过一次Minor GC后有大量对象存活,而新生代的survivor区很小,放不下这些大量存活的对象,所以需要老年代进行分配担保,把survivor区无法容纳的对象直接进入老年代。

JVM之内存分配与回收策略 - 平凡希
http://www.cnblogs.com/xiaoxi/p/6557473.html


Java内存溢出与内存泄露

内存溢出:简单地说内存溢出就是指程序运行过程中申请的内存大于系统能够提供的内存,导致无法申请到足够的内存,于是就发生了内存溢出。

内存泄漏:内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。

Java内存溢出

堆溢出(OutOfMemory)

java.lang.OutOfMemoryError: Java heap space (堆溢出)

发生这种溢出的原因一般是创建的对象太多,在进行垃圾回收之前对象数量达到了最大堆的容量限制。

解决这个区域异常的方法一般是通过内存映像分析工具对Dump出来的堆转储快照进行分析,看到底是内存溢出还是内存泄漏。如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,定位出泄漏代码的位置,修改程序或算法;如果不存在泄漏,就是说内存中的对象确实都还必须存活,那就应该检查虚拟机的堆参数-Xmx(最大堆大小)和-Xms(初始堆大小),与机器物理内存对比看是否可以调大。

深递归导致栈内存溢出(StackOverflow)

写个方法,不断递归调用自己,会导致栈溢出。因为不断向虚拟机栈中压入栈帧,最终导致虚拟机栈(jvm内存划分中的虚拟机栈)内存溢出。

如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError。
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError。

java内存溢出示例(堆溢出、栈溢出)
https://www.cnblogs.com/panxuejun/p/5882424.html

永久代溢出(循环申请intern字符串常量)

我们知道jvm通过持久带实现了java虚拟机规范中的方法区,而运行时常量池就是保存在方法区中的,因此发生这种溢出可能是运行时常量池溢出,或是由于程序中使用了大量的jar或class,使得方法区中保存的class对象没有被及时回收或者class信息占用的内存超过了配置的大小。

内存溢出与内存泄漏 - 平凡希
http://www.cnblogs.com/xiaoxi/p/7354857.html


Java内存泄露

Java中的内存泄露,广义并通俗的说,就是:不再会被使用的对象的内存不能被回收,就是内存泄露。

在Java中,我们不用(也没办法)自己释放内存,无用的对象由GC自动清理,这也极大的简化了我们的编程工作。但,实际有时候一些不再会被使用的对象,在GC看来不能被释放,就会造成内存泄露。

内存泄漏的根本原因是长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象已经不再需要,但由于长生命周期对象持有它的引用而导致不能被回收。

下面总结几种常见的内存泄漏:

集合类中引用不需要的对象

1、静态集合类引起的内存泄漏:
像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,从而造成内存泄漏,因为他们也将一直被Vector等引用着。

Vector<Object> v=new Vector<Object>(100);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}

在这个例子中,代码栈中存在Vector 对象的引用 v 和 Object 对象的引用 o 。在 For 循环中,我们不断的生成新的对象,然后将其添加到 Vector 对象中,之后将 o 引用置空。问题是当 o 引用被置空后,如果发生 GC ,我们创建的 Object 对象是否能够被 GC 回收呢?答案是否定的。因为, GC 在跟踪代码栈中的引用时,会发现 v 引用,而继续往下跟踪,就会发现 v 引用指向的内存空间中又存在指向 Object 对象的引用。也就是说尽管 o 引用已经被置空,但是 Object 对象仍然存在其他的引用,是可以被访问到的,所以 GC 无法将其释放掉。如果在此循环之后, Object 对象对程序已经没有任何作用,那么我们就认为此 Java 程序发生了内存泄漏。

放入HashMap/HashSet中的对象取不出

2、修改HashSet中对象的参数值,且参数是计算哈希值的字段
当一个对象被存储到HashSet集合中以后,修改了这个对象中那些参与计算哈希值的字段后,这个对象的哈希值与最初存储在集合中的就不同了,这种情况下,用contains方法在集合中检索对象是找不到的,这将会导致无法从HashSet中删除当前对象,造成内存泄漏

监听器

3、监听器
在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。

Connect/File/Session用完不关闭

4、各种连接
比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close() 方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去连接,在finally里面释放连接。

单例对象引用外部对象

5、单例模式
如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露。
不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露

缓存

缓存通常都是以动态方式实现的,如果缓存设置不正确而大量使用缓存的话则会出现内存溢出的后果,因此需要将所使用的内存容量与检索数据的速度加以平衡。
常用的解决途径是使用java.lang.ref.SoftReference类坚持将对象放入缓存。这个方法可以保证当虚拟机用完内存或者需要更多堆的时候,可以释放这些对象的引用。

内存溢出与内存泄漏 - 平凡希
http://www.cnblogs.com/xiaoxi/p/7354857.html

java内存溢出示例(堆溢出、栈溢出)
https://www.cnblogs.com/panxuejun/p/5882424.html

Java内存泄漏分析与解决方案
https://www.cnblogs.com/guozhenqiang/p/5433202.html


堆外内存(直接内存)

使用堆外内存的优点与缺点

堆外内存的好处是:
1、可以扩展至更大的内存空间。比如超过1TB甚至比主存还大的空间(基于操作系统的动态内存换入换出)。
2、理论上能减少GC暂停时间。
3、可以在进程间共享,减少JVM间的对象复制,使得JVM的分割部署更容易实现。
4、它的持久化存储可以支持快速重启,同时还能够在测试环境中重现生产数据。

缺点是:
1、堆外内存难以控制,如果内存泄漏,那么很难排查
2、堆外内存相对来说,不适合存储很复杂的对象。一般简单的对象或者扁平化的比较适合。

Java堆外内存的使用
http://www.importnew.com/14292.html

-XX:MaxDirectMemorySize=40M 参数设置最大堆外内存

通过sun.misc.Unsafe类使用堆外内存

Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库(JDK类库下的NIO和concurrent包下的很多类都使用到了Unsafe类,如AtomicInteger和AbstractQueuedSynchronizer等。),包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。

如何获取Unsafe类实例(反射)

Unsafe类使用了单例模式,需要通过一个静态方法getUnsafe()来获取。但Unsafe类做了限制,如果是普通的调用的话,它会抛出一个SecurityException异常;只有由主类加载器加载的类才能调用这个方法。
但是我们可以通过反射,在我们的应用代码中获取Unsafe类的实例:

public static Unsafe getUnsafeInstance() throws Exception
{
        // 通过反射获取rt.jar下的Unsafe类
        Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafeInstance.setAccessible(true);
        // return (Unsafe) theUnsafeInstance.get(null);是等价的
        return (Unsafe) theUnsafeInstance.get(Unsafe.class);
}

在eclipse编写完这个函数之后,会出现错误或者警告提示:Access restriction: The type Unsafe is not accessible due to restriction on required library C:\Program Files\Java\jdk1.6.0_32\jre\lib\rt.jar。
虽然这段代码在eclipse里面会报经过或者报错,但它的却是可以运行的。我们可以在eclipse进行如下设置,来取消警告或错误:Window–>Preferences–>Java–>Compiler–>Errors/Warnings,将里面的Deprecated and restricted API中的Forbidden references(access rules)设置成Ignore,这样eclipse就不会再报警告或者错误了。

java获取Unsafe类的实例和取消eclipse编译的错误和警告
https://blog.csdn.net/aitangyong/article/details/38276681

Unsafe类分配堆外内存实例

import sun.misc.Unsafe;

public class TestUnsafeMemo
{
    // -XX:MaxDirectMemorySize=40M
    public static void main(String[] args) throws Exception
    {
        Unsafe unsafe = GetUsafeInstance.getUnsafeInstance();

        while (true)
        {
            long pointer = unsafe.allocateMemory(1024 * 1024 * 20);
            System.out.println(unsafe.getByte(pointer + 1));

            // 如果不释放内存,运行一段时间会报错java.lang.OutOfMemoryError
            // unsafe.freeMemory(pointer);
        }
    }

}

这段程序会报OutOfMemoryError错误,也就是说allocateMemory和freeMemory,相当于C语音中的malloc和free,必须手动释放分配的内存。

java中使用堆外内存,关于内存回收需要注意的事和没有解决的遗留问题(等大神解答)
https://blog.csdn.net/aitangyong/article/details/39323125

Unsafe类提供的功能

Unsafe类提供了以下这些功能:
一、内存管理。包括分配内存、释放内存等。
该部分包括了allocateMemory(分配内存)、reallocateMemory(重新分配内存)、copyMemory(拷贝内存)、freeMemory(释放内存 )、getAddress(获取内存地址)、addressSize、pageSize、getInt(获取内存地址指向的整数)、getIntVolatile(获取内存地址指向的整数,并支持volatile语义)、putInt(将整数写入指定内存地址)、putIntVolatile(将整数写入指定内存地址,并支持volatile语义)、putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法)等方法。getXXX和putXXX包含了各种基本类型的操作。

利用copyMemory方法,我们可以实现一个通用的对象拷贝方法,无需再对每一个对象都实现clone方法,当然这通用的方法只能做到对象浅拷贝。

二、非常规的对象实例化。
allocateInstance()方法提供了另一种创建实例的途径。通常我们可以用new或者反射来实例化对象,使用allocateInstance()方法可以直接生成对象实例,且无需调用构造方法和其它初始化方法。

这在对象反序列化的时候会很有用,能够重建和设置final字段,而不需要调用构造方法。

三、操作类、对象、变量。
这部分包括了staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移)等方法。

通过这些方法我们可以获取对象的指针,通过对指针进行偏移,我们不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。

四、数组操作。
这部分包括了arrayBaseOffset(获取数组第一个元素的偏移地址)、arrayIndexScale(获取数组中元素的增量地址)等方法。arrayBaseOffset与arrayIndexScale配合起来使用,就可以定位数组中每个元素在内存中的位置。

由于Java的数组最大值为Integer.MAX_VALUE,使用Unsafe类的内存分配方法可以实现超大数组。实际上这样的数据就可以认为是C数组,因此需要注意在合适的时间释放内存。

五、多线程同步。包括锁机制、CAS操作等。
这部分包括了monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap等方法。

其中monitorEnter、tryMonitorEnter、monitorExit已经被标记为deprecated,不建议使用。

Unsafe类的CAS操作可能是用的最多的,它为Java的锁机制提供了一种新的解决办法,比如AtomicInteger等类都是通过该方法来实现的。compareAndSwap方法是原子的,可以避免繁重的锁机制,提高代码效率。这是一种乐观锁,通常认为在大部分情况下不出现竞态条件,如果操作失败,会不断重试直到成功。

六、挂起与恢复。
这部分包括了park、unpark等方法。

将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。

七、内存屏障。
这部分包括了loadFence、storeFence、fullFence等方法。这是在Java 8新引入的,用于定义内存屏障,避免代码重排序。

loadFence() 表示该方法之前的所有load操作在内存屏障之前完成。同理storeFence()表示该方法之前的所有store操作在内存屏障之前完成。fullFence()表示该方法之前的所有load、store操作在内存屏障之前完成。

说一说Java的Unsafe类
https://www.cnblogs.com/pkufork/p/java_unsafe.html


通过NIO中的ByteBuffer使用堆外内存

import java.nio.ByteBuffer;

public class TestDirectByteBuffer
{
    // -verbose:gc -XX:+PrintGCDetails -XX:MaxDirectMemorySize=40M
    public static void main(String[] args) throws Exception
    {
        while (true)
        {
            ByteBuffer buffer = ByteBuffer.allocateDirect(10 * 1024 * 1024);
        }
    }
}

我们将最大堆外内存设置成40M,运行这段代码会发现:程序可以一直运行下去,不会报OutOfMemoryError。如果使用了-verbose:gc -XX:+PrintGCDetails,会发现程序频繁的进行垃圾回收活动。于是我们可以得出结论:ByteBuffer.allocateDirect分配的堆外内存不需要我们手动释放,而且ByteBuffer中也没有提供手动释放的API。也即是说,使用ByteBuffer不用担心堆外内存的释放问题,除非堆内存中的ByteBuffer对象由于错误编码而出现内存泄露。

ByteBuffer.allocateDirect()内部也是调用sun.misc.Unsafe类实现直接在堆外分配内存。

java中使用堆外内存,关于内存回收需要注意的事和没有解决的遗留问题(等大神解答)
https://blog.csdn.net/aitangyong/article/details/39323125


内存分代与垃圾回收

为什么要分代?(生命周期不同)

分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。

在Java程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比如Http请求中的Session对象、线程、Socket连接,这类对象跟业务直接挂钩,因此生命周期比较长。但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:String对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。

试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,同时,因为每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果的,因为可能进行了很多次遍历,但是他们依旧存在。因此,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。

如何分代?


JVM内存分代

虚拟机中的共划分为三个代:年轻代(Young Generation)、年老代(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。

新生代

新生代又划分为Eden、From Survivor和To Survivor三个部分,他们对应的内存空间的大小比例为8:1:1,也就是,为对象分配内存的时候,首先使用Eden空间,经过GC后,没有被回收的会首先进入From Survivor区域,任何时候,都会保持一个Survivor区域(From Survivor或To Survivor)完全空闲,也就是说新生代的内存利用率最大为90%。From Survivor和To Survivor两个区域会根据GC的实际情况,进行互换,将From Survivor区域中的对象全部复制到To Survivor区域中,或者反过来,将To Survivor区域中的对象全部复制到From Survivor区域中。

为什么需要survivor区?(避免快速填满老年代)

如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)。老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多。

为什么新生代内存需要有两个Survivor区
https://blog.csdn.net/antony9118/article/details/51425581

为什么需要两个survivor区?(避免垃圾碎片)

设置两个Survivor区最大的好处就是解决了碎片化

假设现在只有一个survivor区,我们来模拟一下流程:
刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。

两个survivor区大小一定相等吗?(scavenge动态调节)

两个survivor区大小一定相等吗?不一定,可动态调节

Parallel Scavenge收集器有个参数-XX:+UseAdaptiveSizePolicy,设置此选项后,并行收集器会自动选择年轻代大小(-Xmn)、eden和Survivor区比例(-XX:SurvivorRatio)、晋升老年代年龄(-XX:PretenureSizeThreshold)等参数,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。

Java 垃圾回收的log,为什么 from和to大小不等?
https://www.zhihu.com/question/65601024

如何配置新生代大小?(-XX:NewRatio=4)

-XX:NewRatio
年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)
-XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。

-XX:NewRatio=n,表示老年代是年轻代的n倍,即老年代:年轻代 = n:1,即年轻代占堆大小的1/(n+1)

一般情况下,不允许-XX:Newratio值小于1,即Old要比Yong大。

如何配置survivor区大小?(-XX:SurvivorRatio=8)

-XX:SurvivorRatio
Eden区与Survivor区的大小比值
设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10

-XX:SurvivorRatio=n,表示eden和两个survivor区的比值为n:2,即2个survivor占年轻代总大小的2/(n+2)

-XX:+UseAdaptiveSizePolicy,JVM默认开启survivor区大小自动变化的参数

年老代

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

GC过程中,当某些对象经过多次GC都没有被回收,可能会进入到年老代。或者,当新生代没有足够的空间来为对象分配内存时,可能会直接在年老代进行分配。

持久代(方法区)

用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize进行设置。

永久代实际上对应着虚拟机运行时数据区的“方法区”,这里主要存放类信息、静态变量、常量等数据。一般情况下,永久代中对应的对象的GC效率非常低,因为这里的的大部分对象在运行都不要进行GC,它们会一直被利用,直到JVM退出。

JVM分代垃圾回收策略的基础概念 - 平凡希
http://www.cnblogs.com/xiaoxi/p/6602166.html

方法区gc(废弃常量,无用的类)

永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。

回收废弃常量与回收Java堆中的对象非常类似。以常量池中字面量的回收为例,假如一个字符串“abc”已经进入了常量池中,但是当前系统没有任何一个String对象是叫做“abc”的,换句话说是没有任何String对象引用常量池中的“abc”常量,也没有其他地方引用了这个字面量,如果在这时候发生内存回收,而且必要的话,这个“abc”常量就会被系统“请”出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面3个条件才能算是“无用的类”:

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader已经被回收。
  • 该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是“可以”,而不是和对象一样,不使用了就必然会回收。是否对类进行回收,HotSpot虚拟机提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class及-XX:+TraceClassLoading、 -XX:+TraceClassUnLoading查看类的加载和卸载信息。

什么情况下要关注方法区gc?(反射,代理等动态生成类时)

在大量使用反射、动态代理、CGLib等bytecode框架的场景,以及动态生成JSP和OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

jvm回收方法区
https://www.cnblogs.com/vinozly/p/5076920.html

什么时候会触发Full GC?

Full gc是对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个块进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:

  • 年老代(Tenured)被写满
  • 持久代(Perm)被写满
  • System.gc()被显示调用
  • 上一次GC之后Heap的各域分配策略动态变化

哪些内存(对象)需要被回收?

哪些内存需要回收是垃圾回收机制第一个要考虑的问题,所谓“要回收的垃圾”无非就是那些不可能再被任何途径使用的对象。那么如何找到这些对象?

引用计数法

这个算法的实现是,给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的。这种算法使用场景很多,但是,Java中却没有使用这种算法,因为这种算法很难解决对象之间相互引用的情况。

可达性分析法(根搜索算法)

这个算法的基本思想是通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。

那么问题又来了,如何选取GC Roots对象呢?
在Java语言中,可以作为GC Roots的对象包括下面几种:
(1) 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
(2) 本地方法栈中JNI(Native方法)引用的对象。
(3) 方法区中的类静态属性引用的对象。
(4) 方法区中常量引用的对象。

不可达对象一定被回收吗?(两次标记,finalize()方法)

对于可达性分析算法而言,未到达的对象并非是“非死不可”的,若要宣判一个对象死亡,至少需要经历两次标记阶段。

  1. 如果对象在进行可达性分析后发现没有与GCRoots相连的引用链,则该对象被第一次标记并进行一次筛选,筛选条件为是否有必要执行该对象的finalize方法,若对象没有覆盖finalize方法或者该finalize方法已经被虚拟机执行过了,则均视作不必要执行该对象的finalize方法,即该对象将会被回收。反之,若对象覆盖了finalize方法并且该finalize方法并没有被执行过,那么,这个对象会被放置在一个叫F-Queue的队列中,之后会由虚拟机自动建立的、优先级低的Finalizer线程去执行,而虚拟机不必要等待该线程执行结束,即虚拟机只负责建立线程,其他的事情交给此线程去处理。

2.对F-Queue中对象进行第二次标记,如果对象在finalize方法中拯救了自己,即关联上了GCRoots引用链,如把this关键字赋值给其他变量,那么在第二次标记的时候该对象将从“即将回收”的集合中移除,如果对象还是没有拯救自己,那就会被回收。如下代码演示了一个对象如何在finalize方法中拯救了自己,然而,它只能拯救自己一次,第二次就被回收了。

Java垃圾回收(GC)机制详解 - 平凡希
http://www.cnblogs.com/xiaoxi/p/6486852.html


java中的四种引用方式

java对象的引用包括:强引用,软引用,弱引用,虚引用

Java 如何有效地避免OOM:善于利用软引用和弱引用 - 海子
http://www.cnblogs.com/dolphin0520/p/3784171.html

强引用(StrongReference)

强引用就是指在程序代码之中普遍存在的,比如下面这段代码中的object和str都是强引用:
Object object = new Object();
String str = “hello”;

只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象

如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象

强引用在实际中有非常重要的用处,举个ArrayList的实现源代码:

private transient Object[] elementData;
public void clear() {
        modCount++;
        // Let gc do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
}

在ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null。不同于elementData=null,强引用仍然存在,避免在后续调用 add()等方法添加元素时进行重新的内存分配。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。

软引用(SoftReference)

软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。下面是一个使用示例:

import java.lang.ref.SoftReference;

public class Main {
    public static void main(String[] args) {

        SoftReference<String> sr = new SoftReference<String>(new String("hello"));
        System.out.println(sr.get());
    }
}
弱引用(WeakReference)

弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。下面是使用示例:

import java.lang.ref.WeakReference;

public class Main {
    public static void main(String[] args) {

        WeakReference<String> sr = new WeakReference<String>(new String("hello"));

        System.out.println(sr.get());
        System.gc();                //通知JVM的gc进行垃圾回收
        System.out.println(sr.get());
    }
}

输出结果为:
hello
null
第二个输出结果是null,这说明只要JVM进行垃圾回收,被弱引用关联的对象必定会被回收掉。不过要注意的是,这里所说的被弱引用关联的对象是指只有弱引用与之关联,如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象(软引用也是如此)。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被JVM回收,这个弱引用就会被加入到与之关联的引用队列中。

虚引用(PhantomReference)

虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收

要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;


public class Main {
    public static void main(String[] args) {
        ReferenceQueue<String> queue = new ReferenceQueue<String>();
        PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue);
        System.out.println(pr.get());
    }
}
为什么需要软引用和弱引用?(干预gc和生命周期)

java内存管理分为内存分配和内存回收,都不需要程序员负责,垃圾回收的机制主要是看对象是否有引用指向该对象。

在Java中,虽然不需要程序员手动去管理对象的生命周期,但是如果希望某些对象具备一定的生命周期的话(比如内存不足时JVM就会自动回收某些对象从而避免OutOfMemory的错误)就需要用到软引用和弱引用了。

Java中提供这四种引用类型主要有两个目的:
第一是可以让程序员通过代码的方式决定某些对象的生命周期;
第二是有利于JVM进行垃圾回收。

什么情况下使用软/弱/虚引用?

使用得最多的就是软引用和弱引用,这2种既有相似之处又有区别。它们都是用来描述非必需对象的,但是被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收。

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

什么时候使用软引用?
想让对象保留尽量长的时间,但又不会因此导致内存溢出OOM,此时应使用虚引用
软引用非常适合于创建缓存。当系统内存不足的时候,缓存中的内容是可以被释放的。比如考虑一个图像编辑器的程序。该程序会把图像文件的全部内容都读取到内存中,以方便进行处理。而用户也可以同时打开多个文件。当同时打开的文件过多的时候,就可能造成内存不足。如果使用软引用来指向图像文件内容的话,垃圾回收器就可以在必要的时候回收掉这些内存。

什么时候使用弱引用?
如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集(或者说不想介入这个对象的生命周期),那么你应该用 Weak Reference 来记住此对象。

虚引用有什么用?
虚引用允许你知道具体何时其引用的对象从内存中移除。
而实际上这是Java中唯一的方式。这一点尤其表现在处理类似图片的大文件的情况。当你确定一个图片数据对象应该被回收,你可以利用虚引用来判断这个对象回收之后在继续加载下一张图片。这样可以尽可能地避免可怕的内存溢出错误。

Java 7之基础 - 强引用、弱引用、软引用、虚引用
http://blog.csdn.net/mazhimazh/article/details/19752475

Java的四种引用方式
https://www.cnblogs.com/huajiezh/p/5835618.html


垃圾收集算法

标记-清除(Mark-Sweep)算法

这是最基础的算法,标记-清除算法就如同它的名字样,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。

这种算法的不足主要体现在效率和空间,从效率的角度讲,标记和清除两个过程的效率都不高;从空间的角度讲,标记清除后会产生大量不连续的内存碎片, 内存碎片太多可能会导致以后程序运行过程中在需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾收集动作。

复制(Copying)算法(新生代)

复制算法是为了解决效率问题而出现的,它将可用的内存分为两块,每次只用其中一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次性清理掉。这样每次只需要对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等复杂情况,只需要移动指针,按照顺序分配即可。

为什么新生代分为eden和survivor(基于复制算法)

不过这种算法有个缺点,内存缩小为了原来的一半,这样代价太高了。现在的商用虚拟机都采用这种算法来回收新生代,不过研究表明1:1的比例非常不科学,因此新生代的内存被划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。每次回收时,将Eden和Survivor中还存活着的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden区和Survivor区的比例为8:1:1,意思是每次新生代中可用内存空间为整个新生代容量的90%。当然,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖老年代进行分配担保(Handle Promotion)。

标记-整理(Mark-Compact)算法(老年代)

复制算法在对象存活率较高的场景下要进行大量的复制操作,效率很低。万一对象100%存活,那么需要有额外的空间进行分配担保。老年代都是不易被回收的对象,对象存活率高,因此一般不能直接选用复制算法。根据老年代的特点,有人提出了另外一种标记-整理算法,过程与标记-清除算法一样,不过不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存

分代应用不同回收算法

现代商用虚拟机基本都采用分代收集算法来进行垃圾回收。这种算法没什么特别的,无非是上面内容的结合罢了,根据对象的生命周期的不同将内存划分为几块,然后根据各块的特点采用最适当的收集算法。

大批对象死去、少量对象存活的(新生代),使用复制算法,复制成本低;
对象存活率高、没有额外空间进行分配担保的(老年代),采用标记-清理算法或者标记-整理算法。

Java垃圾回收(GC)机制详解 - 平凡希
http://www.cnblogs.com/xiaoxi/p/6486852.html


垃圾收集器(collector种类)

jvm中GC执行的三种方式,即串行收集、并行收集、并发收集;
默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。

Serial收集器

串行收集(SerialGC),是jvm的默认GC方式,一般适用于小型应用和单处理器,算法比较简单,GC效率也较高,但可能会给应用带来停顿。

最基本、发展历史最久的收集器,这个收集器是一个采用复制算法的单线程的收集器,单线程一方面意味着它只会使用一个CPU或一条线程去完成垃圾收集工作,另一方面也意味着它进行垃圾收集时必须暂停其他线程的所有工作,直到它收集结束为止。

Serial收集器依然是虚拟机运行在Client模式下的默认新生代收集器,因为它简单而高效。

ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集外,其余行为和Serial收集器完全一样,包括使用的也是复制算法。

ParNew收集器除了多线程以外和Serial收集器并没有太多创新的地方,但是它却是Server模式下的虚拟机首选的新生代收集器,其中有一个很重要的和性能无关的原因是,除了Serial收集器外,目前只有它能与CMS收集器配合工作。

CMS收集器

CMS(Conrrurent Mark Sweep)收集器是以获取最短回收停顿时间为目标的收集器。使用标记 - 清除算法

串行收集(SerialGC),是jvm的默认GC方式,一般适用于小型应用和单处理器,算法比较简单,GC效率也较高,但可能会给应用带来停顿。
并行收集(ParallelGC),是指GC运行时,对应用程序运行没有影响,GC和app两者的线程在并发执行,这样可以最大限度不影响app的运行。并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。
并发收集(ConcMarkSweepGC),是指多个线程并发执行GC,一般适用于多处理器系统中,可以提高GC的效率,但算法复杂,系统消耗较大。并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。

Java垃圾回收(GC)机制详解 - 平凡希
http://www.cnblogs.com/xiaoxi/p/6486852.html


jvm参数

如何配置堆大小(-Xms,-Xmx)

-Xms
指定jvm堆的初始大小,默认为物理内存的1/64,最小为1M;可以指定单位,比如k、m、g,若不指定,则默认为单位字节。例如-Xms5120m,设置初始堆内存为5120M;-Xms2g,设置初始堆大小为2G。
-Xmx
指定jvm堆的最大值,默认为物理内存的1/4或者1G,最小为2M;单位与-Xms一致。服务器一般设置-Xms、-Xmx相等以避免在每次GC后调整堆内存的大小。
-Xss
指定每个线程的堆大小。JDK5.0以后每个线程堆栈大小为1M。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成。一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。

如何配置新生代大小(-Xmn)

-Xmn
指定年轻代大小,此处的大小是(eden + 2 survivor space)。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小,增大年轻代后,将会减小年老代大小。

如何配置永久代大小(-XX:PermSize)

-XX:PermSize
指定初始分配的非堆内存大小,即持久代(perm gen)初始大小,默认是物理内存的1/64
-XX:MaxPermSize
指定最大非堆内存大小,即持久代最大值,默认是物理内存的1/4。例如-XX:MaxPermSize=256m,设置持久代大小为256M。

如何生成dump文件

1、配置jvm参数,自动在OOM时产生dump文件

-XX:+HeapDumpOnOutOfMemoryError
当首次遭遇OOM时导出此时堆中相关信息

-XX:HeapDumpPath
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:/usr/aaa/dump/heap_trace.txt

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/aaa/dump -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/usr/aaa/dump/heap_trace.txt

2、在命令行中使用jmap工具手动导出dump文件

3、在图形界面jvisualvm中远程产生dump文件,内部时通过jmx实现的

JVM性能调优之生成堆的dump文件
http://blog.csdn.net/lifuxiangcaohui/article/details/37992725


jvm调优(合理配置堆内存各部分大小)

一步步优化JVM四:决定Java堆的大小以及内存占用
https://blog.csdn.net/zhoutao198712/article/details/7783070


JVM类加载子系统

Java的类加载机制
http://www.cnblogs.com/xiaoxi/p/6959615.html

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

类加载过程

类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示的对象组件。在Java中,类装载器把一个类装入JVM中,到卸载出内存为止,它的整个生命周期包括:

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

Java程序可以动态扩展是由运行期动态加载和动态链接实现的;比如:如果编写一个使用接口的应用程序,可以等到运行时再指定其实际的实现(多态),解析过程有时候还可以在初始化之后执行;比如:动态绑定(多态);

类加载器

深入探讨 Java 类加载器
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/

类加载器负责读取 Java 字节代码(.class文件),并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。

java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:

  • 引导类加载器(bootstrap class loader)
    它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
    将存放于JAVA_HOME\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar 。名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用

  • 扩展类加载器(extensions class loader)
    它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
    将JAVA_HOME\lib\ext目录下的,或者被java.ext.dirs系统变量所指定的路径中的所有类库加载。开发者可以直接使用扩展类加载器。

  • 系统类加载器(system class loader)
    它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
    负责加载用户类路径(ClassPath)上所指定的类库,开发者可直接使用。

除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

每个 Java 类都维护着一个指向定义它的类加载器的引用,通过 Class类的getClassLoader()方法就可以获取到此引用。

双亲委派模型

双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

双亲委派机制:
1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

Class.forName做了什么

Class.forName(xxx.xx.xx)的作用就是要求JVM查找并加载指定的类,如果在类中有静态初始化器的话,JVM必然会执行该类的静态代码段。
java里面任何class都要装载在虚拟机上才能运行,而静态代码是和class绑定的,class装载成功就表示执行了你的静态代码了,而且以后不会再走这段静态代码了。

Class.forName()做了什么?
假设一个类以前从来没有被装进内存过,Class.forName(String className)这个方法会做以下几件事情:
1、装载。将字节码读入内存,并产生一个与之对应的java.lang.Class类对象
2、连接。这一步会验证字节码,为static变量分配内存,并赋默认值(0或null),并可选的解析符号引用(这里不理解没关系)
3、初始化。为类的static变量赋初始值,假如有static int a = 1;这个将a赋值为1的操作就是这个时候做的。除此之外,还要调用类的static块。(这一步是要点)

Class.forName()和ClassLoader.loadClass()区别

Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;

ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。

注:Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。

Java的类加载机制 - 平凡希
http://www.cnblogs.com/xiaoxi/p/6959615.html


对象的创建过程

类加载检查

1、虚拟机遇到一条new指令,首先去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化。如果没有,那么必须先执行类的初始化过程。

在堆上为对象分配内存

2、类加载检查通过后,虚拟机为新生对象分配内存。对象所需内存大小在类加载完成后便可以完全确定,为对象分配空间无非就是从Java堆中划分出一块确定大小的内存而已。这个地方会有两个问题:
(1)如果内存是规整的,那么虚拟机将采用的是指针碰撞法来为对象分配内存。意思是所有用过的内存在一边,空闲的内存在另外一边,中间放着一个指针作为分界点的指示器,分配内存就仅仅是把指针向空闲那边挪动一段与对象大小相等的距离罢了。如果垃圾收集器选择的是Serial、ParNew这种基于压缩算法的,虚拟机采用这种分配方式。
(2)如果内存不是规整的,已使用的内存和未使用的内存相互交错,那么虚拟机将采用的是空闲列表法来为对象分配内存。意思是虚拟机维护了一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。如果垃圾收集器选择的是CMS这种基于标记-清除算法的,虚拟机采用这种分配方式。

另外一个问题及时保证new对象时候的线程安全性。因为可能出现虚拟机正在给对象A分配内存,指针还没有来得及修改,对象B又同时使用了原来的指针来分配内存的情况。虚拟机采用了CAS配上失败重试的方式保证更新更新操作的原子性和TLAB两种方式来解决这个问题。

对象内存初始化

3、内存分配结束,虚拟机将分配到的内存空间都初始化为零值(不包括对象头)。这一步保证了对象的实例字段在Java代码中可以不用赋初始值就可以直接使用,程序能访问到这些字段的数据类型所对应的零值。

对象头设置

4、对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息存放在对象的对象头中。

对象初始化

5、执行init方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

JVM的内存区域划分 - 平凡希
http://www.cnblogs.com/xiaoxi/p/6421526.html


Java安全

Java安全沙箱的构成

组成Java沙箱的基本组件如下:
1、类加载体系结构
2、class文件校验器
3、内置于Java虚拟机(及语言)的安全特性
4、安全管理器及Java API


Jar包签名和验证

首先要明白以下概念:非对称加密、信息摘要、数字签名、数字证书,以及他们之间的关系,如何工作的。

对Jar包的数字签名和验证过程和网络安全中的数字签名原理和过程一致。Jar包是待发送的消息,经过签名后,Jar包内置入了数字签名和public key,验证者可以使用这两项数据进行验证。

实际上,经签名的Jar包内包含了以下内容:
1、原Jar包内的class文件和资源文件
2、签名文件 META-INF/.SF:这是一个文本文件,包含原Jar包内的class文件和资源文件的Hash
3、签名block文件 META-INF/
.DSA:这是一个数据文件,包含签名者的 certificate 和数字签名。其中 certificate 包含了签名者的有关信息和 public key;数字签名是对 *.SF 文件内的 Hash 值使用 private key 加密得来

用keytool生成keystore密钥对

JDK 提供了 keytool 和 jarsigner 两个工具用来进行 Jar 包签名和验证。

keytool 用来生成和管理 keystore。keystore 是一个数据文件,存储了 key pair 有关的2种数据:private key 和 certificate,而 certificate 包含了 public key。整个 keystore 用一个密码进行保护,keystore 里面的每一对 key pair 单独用一个密码进行保护。每对 key pair 用一个 alias 进行指定,alias 不区分大小写。

keytool 支持的算法是:
如果公钥算法为 DSA,则摘要算法使用 SHA-1。这是默认的
如果公钥算法为 RSA,则摘要算法采用 MD5

用jarsigner对jar包签名和验证

jarsigner 读取 keystore,为 Jar 包进行数字签名。jarsigner 也可以对签名的 Jar 包进行验证。

1、创建密钥库并生成密钥myKey
keytool -genkey -keystore myKeyStore.store -alias myKey

2、用密钥对jar包进行签名
jarsigner -keystore myKeyStore xxx.jar myKey

3、对签名后的jar包进行验证
jarsigner -keystore myKeyStore -verify xxx.jar -verbose -certs

Jar 包签名
https://www.cnblogs.com/jackofhearts/archive/2013/07/17/jar_signing.html

java之jvm学习笔记八(实践对jar包的代码签名)
https://blog.csdn.net/yfqnihao/article/details/8267669


class文件校验器(4趟扫描)

class文件校验器,通过四趟扫描,保证了class文件正确
第一趟是,检查class文件的结构是否正确。比较典型的就是,检查class文件是否以魔数OxCAFEBABE打头。通过这趟检查,可以过滤掉大部分可能损坏的,或者压根就不是class的文件,来冒充装载。
第二趟是,检查它是否符合java语言特性里的编译规则。比如发现一个类的超类不是Object,就抛出异常。
第三趟是,字节码验证,检查字节码是否能被JVM安全的执行,而不会导致JVM崩溃。
第四趟是,符号引用验证。

总结:
第一趟扫描,在类被装载时进行,校验class文件的内部结构,保证能够被正常安全的编译
第二趟和第三趟在连接的过程中进行,这两趟基本上是语法校验,词法校验
第四趟是解析符号引用和直接引用时进行的,这次校验确认被引用的类,字段以及方法确实存在

java之jvm学习笔记三(Class文件检验器)
https://blog.csdn.net/yfqnihao/article/details/8258228

java安全沙箱机制介绍
https://blog.csdn.net/chdhust/article/details/42343473


安全管理器SecurityManager

Java从应用层给我们提供了安全管理机制——安全管理器,每个Java应用都可以拥有自己的安全管理器,它会在运行阶段检查需要保护的资源的访问权限及其它规定的操作权限,保护系统免受恶意操作攻击,以达到系统的安全策略。

安全管理器的工作过程

当运行Java程序时,安全管理器会根据policy文件所描述的策略给程序不同模块分配权限,假设把应用程序分成了三块,每块都有不同的权限,第一块有读取某文件的权限,第二块同时拥有读取某文件跟内存的权限,第三块有监听socket的权限。通过这个机制就能很好地控制程序各个部分的各种操作权限,从应用层上为我们提供了安全管理策略。

以安全管理器对文件操作进行管理的工作过程为例:
当应用程序要读取本地文件时,securitymanager就会在读取前进行拦截,判断是否有读取此文件的权限,如果有则顺利读取,否则将抛出访问异常。

如何启动安全管理器?

一般而言,Java程序启动时并不会自动启动安全管理器,可以通过以下两种方法启动安全管理器:

添加java启动参数-Djava.security.manager

启动默认的安全管理器最简单的方法就是:直接在启动命令中添加-Djava.security.manager参数即可。
若要同时指定配置文件的位置那么示例如下:
-Djava.security.manager -Djava.security.policy=”E:/java.policy”

指定安全策略文件java.policy

在启动安全管理器时可以通过-Djava.security.policy选项来指定安全策略文件。例如:-Djava.security.policy=”E:/java.policy”
需要说明一下的是,=表示这个策略文件将和默认的策略文件一同发挥作用;==表示只使用这个策略文件。

如果没有指定策略文件的路径,那么安全管理器将使用默认的安全策略文件,它位于%JAVA_HOME%/jre/lib/security目录下面的java.policy。

policy文件包含了多个grant语句,每一个grant描述某些代码拥有某些操作的权限。在启动安全管理器时会根据policy文件生成一个Policy对象,任何时候一个应用程序只能有一个Policy对象。

在启用安全管理器的时候,配置遵循以下基本原则:
没有配置的权限表示没有。
只能配置有什么权限,不能配置禁止做什么。
同一种权限可多次配置,取并集。
统一资源的多种权限可用逗号分割。

策略和保护域

java之jvm学习笔记十(策略和保护域)
https://blog.csdn.net/yfqnihao/article/details/8271415

编码方式启动System.setSecurityManager

也可以通过编码方式启动,不过不建议。
实例化一个java.lang.SecurityManager或继承它的子类的对象,然后通过System.setSecurityManager()来设置并启动一个安全管理器。
System.setSecurityManager(new SecurityManager());

其实通过配置java参数启动,本质上也是通过编码启动,不过参数启动更加灵活。
源码如下,在sun.misc.Launcher中,也是通过System.setSecurityManager()设置一个默认的SecurityManager

String s = System.getProperty("java.security.manager");
if (s != null) {
    SecurityManager sm = null;
    if ("".equals(s) || "default".equals(s)) {
        sm = new java.lang.SecurityManager();
    } else {
        try {
            sm = (SecurityManager)loader.loadClass(s).newInstance();
        } catch (IllegalAccessException e) {
        } catch (InstantiationException e) {
        } catch (ClassNotFoundException e) {
        } catch (ClassCastException e) {
        }
    }
    if (sm != null) {
        System.setSecurityManager(sm);
    } else {
        throw new InternalError(
            "Could not create SecurityManager: " + s);
    }
}

Java安全管理器——SecurityManager
https://blog.csdn.net/hjh200507609/article/details/50330773

java安全管理器SecurityManager入门
https://www.cnblogs.com/yiwangzhibujian/p/6207212.html


实现一个安全管理器

安全管理器SecurityManager的核心方法是checkPerssiom,这个方法里又调用AccessController的checkPerssiom方法,访问控制器AccessController的栈检查机制又遍历整个PerssiomCollection来判断具体拥有什么权限,一旦发现栈中一个权限不允许的时候就抛出异常,否则简单的返回。

自定义安全管理器,实现对指定文件名的读权限进行限制:
第一步,定义一个类继承自SecurityManger重写它的checkRead方法,

package com.yfq.test;

public class MySecurityManager extends SecurityManager {

    @Override
    public void checkRead(String file) {
        //super.checkRead(file, context);
        if (file.endsWith("test"))
        throw new SecurityException("你没有读取的本文件的权限");
    }

}

第二步,定义一个有main函数的public类来验证自己的安全管理器是不是器作用了。

package com.yfq.test;

import java.io.FileInputStream;
import java.io.IOException;

public class TestMySecurityManager {
    public static void main(String[] args) {
        System.setSecurityManager(new MySecurityManager());
        try {
            FileInputStream fis = new FileInputStream("test");
            System.out.println(fis.read());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

运行代码看到控制台输出:Exception in thread “main” java.lang.SecurityException: 你没有读取的本文件的权限

原因是FileInputStream的构造函数里会先获取安全管理器,并调用checkRead(name)方法检查是否有此文件的读权限。

public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
    security.checkRead(name);
}
if (name == null) {
    throw new NullPointerException();
}
fd = new FileDescriptor();
open(name);
}

java之jvm学习笔记六(实践写自己的安全管理器)
https://blog.csdn.net/yfqnihao/article/details/8263358


访问控制器

java之jvm学习笔记十一(访问控制器)
https://blog.csdn.net/yfqnihao/article/details/8271665


JIT即时编译

HotSpot和IBM J9中既有解释执行又有编译执行。
JRocket内部没有解释器,完全靠编译执行,所以存在启动时间较长的特点。

解释运行与编译运行

HotSpot虚拟机中内置了两个即时编译器,分别称为Clien tCompiler和Server Compiler,或者简称为C1编译器和C2编译器(也叫Opto编译器)。目前主流的HotSpot虚拟机(Sun系列JDK1.7及之前版本的虚拟机)中,默认采用解释器与其中一个编译器直接配合的方式工作,程序使用哪个编译器,取决于虚拟机运行的模式,HotSpot虚拟机会根据自身版本与宿主机器的硬件性能自动选择运行模式,用户也可以使用”-client”或”-server”参数去强制指定虚拟机运行在Client模式或Server模式。

无论采用的编译器是Client Compiler还是Server Compiler,解释器与编译器搭配使用的方式在虚拟机中称为“混合模式”(Mixed Mode),用户可以使用参数”-Xint”强制虚拟机运行于“解释模式”(Interpreted Mode),这时编译器完全不介入工作,全部代码都使用解释方式执行。另外,也可以使用参数”-Xcomp”强制虚拟机运行于“编译模式”(Compiled Mode),这时将优先采用编译方式执行程序,但是解释器仍然要在编译无法进行的情况下介入执行过程。


热点代码探测

什么是热点代码?

在运行过程中会被即时编译器编译的“热点代码”有两类,即:
被多次调用的方法。
被多次执行的循环体。

前者很好理解,一个方法被调用得多了,方法体内代码执行的次数自然就多,它成为“热点代码”是理所当然的。而后者则是为了解决一个方法只被调用过一次或少量的几次,但是方法体内部存在循环次数较多的循环体的问题,这样循环体的代码也被重复执行多次,因此这些代码也应该认为是“热点代码”。

对于第一种情况,由于是由方法调用触发的编译,因此编译器理所当然地会以整个方法作为编译对象,这种编译也是虚拟机中标准的JIT编译方式。而对于后一种情况,尽管编译动作是由循环体所触发的,但编译器依然会以整个方法(而不是单独的循环体)作为编译对象。这种编译方式因为编译发生在方法执行过程之中,因此形象地称之为栈上替换(On Stack Replacement,简称为OSR编译,即方法栈帧还在栈上,方法就被替换了)。

如何确定热点代码?(计数器)

基于计数器的热点探测(Counter Based Hot Spot Detection):采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”。这种统计方法实现起来麻烦一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系,但是它的统计结果相对来说更加精确和严谨。

在HotSpot虚拟机中使用的是第二种——基于计数器的热点探测方法,因此它为每个方法准备了两类计数器:方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter)。

在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译。

我们首先来看看方法调用计数器。顾名思义,这个计数器就用于统计方法被调用的次数,它的默认阈值在Client模式下是1500次,在Server模式下是10000次,这个阈值可以通过虚拟机参数-XX:CompileThreshold来人为设定。当一个方法被调用时,会先检查该方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的阈值。如果已超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。

如果不做任何设置,执行引擎并不会同步等待编译请求完成,而是继续进入解释器按照解释方式执行字节码,直到提交的请求被编译器编译完成。当编译工作完成之后,这个方法的调用入口地址就会被系统自动改写成新的,下一次调用该方法时就会使用已编译的版本。

如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的次数。当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减(Counter Decay),而这段时间就称为此方法统计的半衰周期(Counter Half Life Time)。进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数-XX:-UseCounterDecay来关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样,只要系统运行时间足够长,绝大部分方法都会被编译成本地代码。另外,可以使用-XX:CounterHalfLifeTime参数设置半衰周期的时间,单位是秒。


逃逸分析(Escape Analysis)

逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,称为方法逃逸。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。

例如:

public static StringBuffer craeteStringBuffer(String s1, String s2) {
  StringBuffer sb = new StringBuffer();
  sb.append(s1);
  sb.append(s2);
  return sb;
}

StringBuffer sb是一个方法内部变量,上述代码中直接将sb返回,这样这个StringBuffer有可能被其他方法所改变,这样它的作用域就不只是在方法内部,虽然它是一个局部变量,称其逃逸到了方法外部。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。

上述代码如果想要StringBuffer sb不逃出方法,可以这样写:

public static String createStringBuffer(String s1, String s2) {
  StringBuffer sb = new StringBuffer();
  sb.append(s1);
  sb.append(s2);
  return sb.toString();
}

不直接返回 StringBuffer,那么StringBuffer将不会逃逸出方法。

如果能证明一个对象不会逃逸到方法或线程之外,也就是别的方法或线程无法通过任何途径访问到这个对象,则可能为这个变量进行一些高效的优化,如下所示。

栈上分配

栈上分配(StackAllocation):Java虚拟机中,在Java堆上分配创建对象的内存空间几乎是Java程序员都清楚的常识了,Java堆中的对象对于各个线程都是共享和可见的,只要持有这个对象的引用,就可以访问堆中存储的对象数据。虚拟机的垃圾收集系统可以回收堆中不再使用的对象,但回收动作无论是筛选可回收对象,还是回收和整理内存都需要耗费时间。如果确定一个对象不会逃逸出方法之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,垃圾收集系统的压力将会小很多。

是否所有对象都是在堆中分配内存?

所以,如果以后再有人问你:是不是所有的对象和数组都会在堆内存分配空间?
那么你可以告诉他:不一定,随着JIT编译器的发展,在编译期间,如果JIT经过逃逸分析,发现有些对象没有逃逸出方法,那么有可能堆内存分配会被优化成栈内存分配。但是这也并不是绝对的。就像我们前面看到的一样,在开启逃逸分析之后,也并不是所有User对象都没有在堆上分配

对象并不一定都是在堆上分配内存的。
https://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650121307&idx=1&sn=5526473d0248cca8385d2a18ba6b25af

同步消除

同步消除(SynchronizationElimination):线程同步本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那这个变量的读写肯定就不会有竞争,对这个变量实施的同步措施也就可以消除掉。

标量替换

标量替换(ScalarReplacement):标量(Scalar)是指一个数据已经无法再分解成更小的数据来表示了,Java虚拟机中的原始数据类型(int、long等数值类型以及reference类型等)都不能再进一步分解,它们就可以称为标量。相对的,如果一个数据可以继续分解,那它就称作聚合量(Aggregate),Java中的对象就是最典型的聚合量。如果把一个Java对象拆散,根据程序访问的情况,将其使用到的成员变量恢复原始类型来访问就叫做标量替换。如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。将对象拆分后,除了可以让对象的成员变量在栈上(栈上存储的数据,有很大的概率会被虚拟机分配至物理机器的高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件。

开启逃逸分析

在Java代码运行时,通过JVM参数可指定是否开启逃逸分析,
-XX:+DoEscapeAnalysis : 表示开启逃逸分析
-XX:-DoEscapeAnalysis : 表示关闭逃逸分析

开启逃逸分析之后可以通过参数-XX:+PrintEscapeAnalysis来查看分析结果。
有了逃逸分析支持之后,用户可以使用参数-XX:+EliminateAllocations来开启标量替换,使用+XX:+EliminateLocks来开启同步消除,使用参数-XX:+PrintEliminateAllocations查看标量的替换情况。

从jdk 1.7开始已经默认开始逃逸分析,如需关闭,需要指定-XX:-DoEscapeAnalysis


jdk工具

jvisualvm和jconsole

JVM监测、故障排除、分析工具,主要以图形化界面的方式提供运行于指定虚拟机的Java应用程序的详细信息。

wsimport

XML Web Service 2.0的Java API,主要用于根据服务端发布的wsdl文件生成客户端存根及框架

javap

Java反编译工具,主要用于根据Java字节码文件反汇编为Java源代码文件。


jps

JVM Process Status Tool,显示指定系统内所有具有访问权限的HotSpot虚拟机进程。

jps只能显示当前用户的java进程,如果服务器上的java进程是其他用户启动的,需要su到其他账户下查看。

命令格式:
jps [options] [hostid]
options参数
-l : 输出主类全名或jar路径
-q : 只显示pid,不显示class名称,jar文件名和传递给main 方法的参数
-m : 输出JVM启动时传递给main()的参数
-v : 输出JVM启动时显示指定的JVM参数
其中[options]、[hostid]参数也可以不写

jps命令原理

jdk中的jps命令可以显示当前运行的java进程以及相关参数,它的实现机制如下:
java程序在启动以后,会在java.io.tmpdir指定的目录下,就是临时文件夹里,生成一个类似于hsperfdata_User的文件夹,这个文件夹里(在Linux中为/tmp/hsperfdata_{userName}/),有几个文件,名字就是java进程的pid,因此列出当前运行的java进程,只是把这个目录里的文件名列一下而已。 至于系统的参数什么,就可以解析这几个文件获得。

Java命令学习系列(一)——Jps
https://www.hollischuang.com/archives/105

JVM调优命令-jps
http://www.cnblogs.com/myna/p/7567710.html


jstack

jstack主要用来查看某个Java进程内的线程堆栈信息。
jstack用于生成java虚拟机当前时刻的线程快照。
jstack可以定位到线程堆栈,根据堆栈信息我们可以定位到具体代码,所以它在JVM性能调优中使用得非常多。

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

命令语法:

jstack [-l] <pid> 连接到正在运行的进程
jstack -F [-m] [-l] <pid> 连接到挂起的进程
jstack [-m] [-l] <executable> <core> 连接到core文件
jstack [-m] [-l] [server_id@]<remote server IP or hostname> 连接到远程主机

参数:
-F:当正常输出请求不被响应时,强制输出线程栈堆。
-l:除线程栈堆外,显示关于锁的附加信息。-l long listings,会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况
-m:如果调用本地方法的话,可以显示c/c++的栈堆

jstack -l pid 定位死锁

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007f0134003ae8 (object 0x00000007d6aa2c98, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007f0134006168 (object 0x00000007d6aa2ca8, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
    at javaCommand.DeadLockclass.run(JStackDemo.java:40)
    - waiting to lock <0x00000007d6aa2c98> (a java.lang.Object)
    - locked <0x00000007d6aa2ca8> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:745)
"Thread-0":
    at javaCommand.DeadLockclass.run(JStackDemo.java:27)
    - waiting to lock <0x00000007d6aa2ca8> (a java.lang.Object)
    - locked <0x00000007d6aa2c98> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.

告诉我们 Found one Java-level deadlock,然后指出造成死锁的两个线程的内容。然后,又通过 Java stack information for the threads listed above来显示更详细的死锁的信息:
Thread-1在想要执行第40行的时候,当前锁住了资源0x00000007d6aa2ca8,但是他在等待资源0x00000007d6aa2c98
Thread-0在想要执行第27行的时候,当前锁住了资源0x00000007d6aa2c98,但是他在等待资源0x00000007d6aa2ca8
由于这两个线程都持有资源,并且都需要对方的资源,所以造成了死锁。 原因我们找到了,就可以具体问题具体分析,解决这个死锁了。

JVM调优命令-jstack
http://www.cnblogs.com/myna/p/7595414.html

JVM性能调优监控工具专题一:JVM自带性能调优工具(jps,jstack,jmap,jhat,jstat,hprof)
http://josh-persistence.iteye.com/blog/2161848

使用jstack精确找到异常代码的
https://blog.csdn.net/mr__fang/article/details/68496248

jstack简单使用,定位死循环、线程阻塞、死锁等问题
http://www.cnblogs.com/chenpi/p/5377445.html

java命令–jstack 工具
https://www.cnblogs.com/kongzhongqijing/articles/3630264.html


jstat

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

命令格式
jstat [options] VMID [interval] [count]

参数:
[options] : 操作参数,一般使用 -gcutil 查看gc情况
VMID : 本地虚拟机进程ID,即当前运行的java进程号
[-hlines]:每lines行显示一次标题,比如-h3表示每三行显示一下标题
[interval] : 连续输出的时间间隔,默认单位为毫秒(ms),可选单位有秒(s)或者毫秒(ms),比如5s,5ms
[count] : 连续输出的次数,如果缺省打印无数次

例如:
jstat -printcompilation 3024 250 6
每250毫秒打印一次,一共打印6次,还可以加上-h3每三行显示一下标题。

option参数:

  • -class,类加载的行为统计。显示加载class的数量,及所占空间等信息。
  • -compiler,HotSpot JIT编译器行为统计。
  • -gc,垃圾回收堆的行为统计。
  • -gccapacity,各个垃圾回收代容量(young,old,perm)和他们相应的空间统计。
  • -gcutil,垃圾回收统计概述(百分比)。
  • -gccause,垃圾收集统计概述(同-gcutil),附加最近两次垃圾回收事件的原因。
  • -gcnew,新生代行为统计。
  • -gcnewcapacity,新生代与其相应的内存空间的统计。
  • -gcold,老年代和永久代行为统计。
  • -gcoldcapacity,老年代大小统计。
  • -gcpermcapacity,永久代大小统计。
  • -printcompilation,HotSpot编译方法统计。

使用示例:

jstat -gc pid 查看gc次数

垃圾回收堆的行为统计,常用命令
其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。

[prouser@vm-vmw96712-app bin]$ ./jstat -gc 105970 2s
S0C    S1C    S0U    S1U      EC      EU        OC        OU      PC    PU    YGC    YGCT    FGC    FGCT    GCT
5568.0 5312.0  0.0    0.0  1387200.0 24118.9  2796224.0  282462.9  127552.0 127450.6    684  23.660  509  485.376  509.037
5568.0 5312.0  0.0    0.0  1387200.0 24118.9  2796224.0  282462.9  127552.0 127450.6    684  23.660  509  485.376  509.037

C即Capacity 总容量,U即Used 已使用的容量
S0C : survivor 0区的总容量
S1C : survivor 1区的总容量
S0U : survivor 0区已使用的容量
S1U : survivor 1区已使用的容量
EC : Eden区的总容量
EU : Eden区已使用的容量
OC : Old区的总容量
OU : Old区已使用的容量
PC : 当前perm的容量 (KB)
PU : perm的使用 (KB)
YGC : 新生代垃圾回收次数
YGCT : 新生代垃圾回收时间
FGC : 老年代垃圾回收次数
FGCT : 老年代垃圾回收时间
GCT : 垃圾回收总消耗时间

jstat -gccapacity pid

同-gc,还会输出Java堆各区域使用到的最大、最小空间

[prouser@vm-vmw96712-app bin]$ ./jstat -gccapacity 105970 2s
NGCMN    NGCMX    NGC    S0C  S1C      EC      OGCMN      OGCMX      OGC        OC      PGCMN    PGCMX    PGC      PC    YGC    FGC
1398080.0 1398080.0 1398080.0 5568.0 5312.0 1387200.0  2796224.0  2796224.0  2796224.0  2796224.0  21248.0 262144.0 127552.0 127552.0    684  509
1398080.0 1398080.0 1398080.0 5568.0 5312.0 1387200.0  2796224.0  2796224.0  2796224.0  2796224.0  21248.0 262144.0 127552.0 127552.0    684  509

NGCMN : 新生代占用的最小空间
NGCMX : 新生代占用的最大空间
OGCMN : 老年代占用的最小空间
OGCMX : 老年代占用的最大空间
OGC:当前年老代的容量 (KB)
OC:当前年老代的空间 (KB)
PGCMN : perm占用的最小空间
PGCMX : perm占用的最大空间

jstat -gcutil pid 查看堆内存百分比

同-gc,输出的是已使用空间占总空间的百分比

[prouser@vm-vmw96692-app bin]$ jstat -gcutil 44398 2s
  S0    S1    E      O      P    YGC    YGCT    FGC    FGCT    GCT
20.54  0.00  8.71  61.33  99.58  10838 2231.090  387 1045.842 3276.933
20.54  0.00  8.74  61.33  99.58  10838 2231.090  387 1045.842 3276.933

jstat -gccause pid 查看gc原因

垃圾收集统计概述(同-gcutil),附加最近两次垃圾回收事件的原因

[prouser@vm-vmw96712-app bin]$ ./jstat -gccause 105970 2s
  S0    S1    E      O      P    YGC    YGCT    FGC    FGCT    GCT    LGCC                GCC
  0.00  0.00  2.64  10.10  99.92    684  23.660  509  485.376  509.037 System.gc()          No GC
  0.00  0.00  2.64  10.10  99.92    684  23.660  509  485.376  509.037 System.gc()          No GC

jstat -gcnew pid

统计新生代行为

[prouser@vm-vmw96692-app bin]$ jstat -gcnew 44398 2s
S0C    S1C    S0U    S1U  TT MTT  DSS      EC      EU    YGC    YGCT
648256.0 643008.0    0.0 26659.4  2  15 648256.0 801280.0 505418.1  10839 2231.210
648256.0 643008.0    0.0 26659.4  2  15 648256.0 801280.0 513432.0  10839 2231.210

TT:Tenuring threshold(提升阈值)
MTT:最大的tenuring threshold
DSS:survivor区域大小 (KB)

jstat -gcnewcapacity pid

新生代与其相应的内存空间的统计

[prouser@vm-vmw96692-app bin]$ jstat -gcnewcapacity 44398 2s
  NGCMN      NGCMX      NGC      S0CMX    S0C    S1CMX    S1C      ECMX        EC      YGC  FGC
2097152.0  2097152.0  2097152.0 648256.0 699008.0 699008.0 645696.0  2097024.0  803200.0 10840  387
2097152.0  2097152.0  2097152.0 648256.0 699008.0 699008.0 645696.0  2097024.0  803200.0 10840  387

NGC:当前年轻代的容量 (KB)
S0CMX:最大的S0空间 (KB)
S0C:当前S0空间 (KB)
ECMX:最大eden空间 (KB)
EC:当前eden空间 (KB)

jstat -gcold pid

统计老年代行为

[prouser@vm-vmw96692-app bin]$ jstat -gcold 44398 2s
  PC      PU        OC          OU      YGC    FGC    FGCT    GCT
184128.0 183386.4  4194304.0  2685339.4  10840  387 1045.842 3277.150
184128.0 183386.4  4194304.0  2685339.4  10840  387 1045.842 3277.150

jstat -gcoldcapacity pid

老年代与其相应的内存空间的统计

[prouser@vm-vmw96692-app bin]$ jstat -gcoldcapacity 44398 2s
  OGCMN      OGCMX        OGC        OC      YGC  FGC    FGCT    GCT
  4194304.0  4194304.0  4194304.0  4194304.0 10841  387 1045.842 3277.217
  4194304.0  4194304.0  4194304.0  4194304.0 10841  387 1045.842 3277.217

jstat -gcpermcapacity pid

永久代与其相应内存空间的统计

[prouser@vm-vmw96692-app bin]$ jstat -gcpermcapacity 44398 2s
  PGCMN      PGCMX      PGC        PC      YGC  FGC    FGCT    GCT
  21248.0  262144.0  184128.0  184128.0 10841  387 1045.842 3277.217
  21248.0  262144.0  184128.0  184128.0 10841  387 1045.842 3277.217

jstat -class pid

()监视类装载、卸载数量、总空间以及耗费的时间

[prouser@vm-vmw96692-app bin]$ jstat -class 44398 2s
Loaded  Bytes  Unloaded  Bytes    Time
26977 54555.7    2284  3469.6      75.63
26977 54555.7    2284  3469.6      75.63
26977 54555.7    2284  3469.6      75.63
26977 54555.7    2284  3469.6      75.63

Loaded : 加载class的数量
Bytes : class字节大小
Unloaded : 未加载class的数量
Bytes : 未加载class的字节大小
Time : 加载时间

jstat -compiler pid

()输出JIT编译过的方法数量耗时等

[prouser@vm-vmw96712-app bin]$ ./jstat -compiler 105970 4s
Compiled Failed Invalid  Time  FailedType FailedMethod
    5689      1      0  125.70          1 org/apache/xerces/impl/XMLNSDocumentScannerImpl scanStartElement
    5689      1      0  125.70          1 org/apache/xerces/impl/XMLNSDocumentScannerImpl scanStartElement

Compiled : 编译数量
Failed : 编译失败数量
Invalid : 无效数量
Time : 编译耗时
FailedType : 失败类型
FailedMethod : 失败方法的全限定名

jstat -printcompilation pid

hotspot编译方法统计

[prouser@vm-vmw96692-app bin]$ jstat -printcompilation 44398 2s
Compiled  Size  Type Method
    6966      5    1 com/travelsky/adap/re/ras/warning/entity/PLFDifWarningRequest getFlightDateStart
    6966      5    1 com/travelsky/adap/re/ras/warning/entity/PLFDifWarningRequest getFlightDateStart

Compiled:被执行的编译任务的数量
Size:方法字节码的字节数
Type:编译类型
Method:编译方法的类名和方法名。类名使用”/“ 代替 “.” 作为空间分隔符. 方法名是给出类的方法名. 格式是一致于HotSpot - XX:+PrintComplation 选项

JVM调优命令-jstat - 钰火 - 博客园
http://www.cnblogs.com/myna/p/7567769.html


jmap

jmap,Java内存映射工具(Java Memory Map),主要用于打印指定Java进程、核心文件或远程调试服务器的共享对象内存映射或堆内存细节。

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

jmap语法:

jmap [option] <pid>,连接到进程
jmap [option] <executable> <core>,连接到core文件
jmap [option] [server_id@]<remote server IP or hostname>,连接到远程主机

option参数

  • -heap : 显示Java堆详细信息
  • -histo[:live] : 显示堆中对象的统计信息,若指定live,只统计存活的对象
  • -permstat :显示永久代的统计信息
  • -finalizerinfo : 显示在F-Queue队列等待Finalizer线程执行finalize方法的对象
  • -dump:<dump-options>: 在hprof二进制文件中生成堆转储快照
    dump-options:
    • live,只转储存活对象,若不指定则转储堆中所有对象
    • format=b,文件格式为二进制
    • file=<file>,指定文件名
  • -F: 强制。结合jmap -dump或jmap -histo使用,如果指定的pid没有响应,强制生成dump快照。此模式下,不支持live子选项。

使用示例:

jmap -dump:live,format=b,file=a pid

(1)dump堆到文件,format指定输出格式,live指明是活着的对象,file指定文件名
jmap -dump:live,format=b,file=dump.hprof 24971

[prouser@vm-vmw96712-app bin]$ ./jmap -dump:live,format=b,file=tapi-server.hprof 105970
Dumping heap to /opt/app/appuser/jboss-eap-5.2/jdk1.6.0_43/bin/tapi-server.hprof ...
Heap dump file created

jmap -heap pid

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

[prouser@vm-vmw96712-app bin]$ ./jmap -heap 105970
Attaching to process ID 105970, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 20.14-b01

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
  MinHeapFreeRatio = 40
  MaxHeapFreeRatio = 70
  MaxHeapSize      = 4294967296 (4096.0MB)
  NewSize          = 1310720 (1.25MB)
  MaxNewSize      = 17592186044415 MB
  OldSize          = 5439488 (5.1875MB)
  NewRatio        = 2
  SurvivorRatio    = 8
  PermSize        = 21757952 (20.75MB)
  MaxPermSize      = 268435456 (256.0MB)

Heap Usage:
PS Young Generation
Eden Space:
  capacity = 1418461184 (1352.75MB)
  used    = 390125360 (372.0525360107422MB)
  free    = 1028335824 (980.6974639892578MB)
  27.503421623414688% used
From Space:
  capacity = 6750208 (6.4375MB)
  used    = 0 (0.0MB)
  free    = 6750208 (6.4375MB)
  0.0% used
To Space:
  capacity = 6422528 (6.125MB)
  used    = 0 (0.0MB)
  free    = 6422528 (6.125MB)
  0.0% used
PS Old Generation
  capacity = 2863333376 (2730.6875MB)
  used    = 290497352 (277.0398635864258MB)
  free    = 2572836024 (2453.647636413574MB)
  10.145425413432543% used
PS Perm Generation
  capacity = 130678784 (124.625MB)
  used    = 130595664 (124.54573059082031MB)
  free    = 83120 (0.0792694091796875MB)
  99.9363936536171% used

jmap -finalizerinfo pid 看待回收对象

(3)打印等待回收的对象信息
jmap -finalizerinfo 105970

[prouser@vm-vmw96712-app bin]$ ./jmap -finalizerinfo 105970
Attaching to process ID 105970, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 20.14-b01
Number of objects pending for finalization: 0

说明当前F-QUEUE队列中并没有等待Fializer线程执行finalize方法的对象

jmap -histo:live pid

(4)打印堆的对象统计,包括对象数、内存大小等等。jmap -histo:live 这个命令执行,JVM会先触发gc,然后再统计信息

[prouser@vm-vmw96712-app bin]$ ./jmap -histo:live 105970
num    #instances        #bytes  class name
----------------------------------------------
  1:        686127      85776304  [C
  2:      1288224      41223168  java.lang.String
  3:        179493      27413544  <constMethodKlass>
  4:        179493      24422408  <methodKlass>
  5:        18092      20337824  <constantPoolKlass>
  6:        449738      17989520  java.util.TreeMap$Entry
  7:        496918      15901376  java.util.HashMap$Entry
  8:        18092      14522336  <instanceKlassKlass>
  9:        249163      13915328  <symbolKlass>
  10:        14536      11228224  <constantPoolCacheKlass>
  11:        132926      10685480  [Ljava.util.HashMap$Entry;
  12:        68494        7546160  [Ljava.lang.Object;
  13:        41473        6531744  [B
  14:        77832        6226560  java.util.jar.JarFile$JarFileEntry
  15:        135428        5417120  java.util.concurrent.ConcurrentHashMap$Segment
  16:        111130        5334240  java.util.HashMap
  17:        159326        5098432  java.util.concurrent.ConcurrentHashMap$HashEntry
  18:        124476        4979040  java.util.LinkedHashMap$Entry
  19:        77791        4978624  org.jboss.virtual.plugins.context.zip.ZipEntryHandler
  20:        152424        4877568  java.util.concurrent.locks.ReentrantLock$NonfairSync
  21:        53005        4664440  java.lang.reflect.Method
  22:          8114        4543240  <methodDataKlass>
  23:        94630        4542240  java.util.TreeMap
  24:        135428        4338280  [Ljava.util.concurrent.ConcurrentHashMap$HashEntry;
  25:        29725        4042600  org.apache.camel.com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap$PaddedAtomicReference
  26:        21380        3617024  [I
  27:        51668        3306752  java.net.URL
  28:        103102        3299264  java.util.Hashtable$Entry
  29:        77793        2489376  org.jboss.virtual.plugins.context.zip.EntryInfo
  30:        43183        2072784  javax.management.modelmbean.ModelMBeanOperationInfo
  31:        19087        1985048  java.lang.Class
  32:        24062        1469224  [S
  33:        89576        1433216  javax.management.modelmbean.DescriptorSupport
  34:        29772        1431696  [[I
...

jmap -permstat pid

(5)打印Java堆内存的永久代(方法区)的类加载器的智能统计信息。对于每个类加载器而言,它的名称、活跃度、地址、父类加载器、它所加载的类的数量和大小都会被打印。此外,包含的字符串数量和大小也会被打印。

[root@localhost jdk1.7.0_79]# jmap -permstat 24971
Attaching to process ID 24971, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.79-b02
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing liveness....................................................liveness analysis may be inaccurate ...
class_loader    classes bytes  parent_loader  alive?  type

<bootstrap>  3034    18149440      null      live    <internal>
0x000000070a88fbb8  1  3048      null      dead    sun/reflect/DelegatingClassLoader@0x0000000703c50b58
0x000000070a914860  1  3064    0x0000000709035198  dead    sun/reflect/DelegatingClassLoader@0x0000000703c50b58
0x000000070a9fc320  1  3056    0x0000000709035198  dead    sun/reflect/DelegatingClassLoader@0x0000000703c50b58
0x000000070adcb4c8  1  3064    0x0000000709035198  dead    sun/reflect/DelegatingClassLoader@0x0000000703c50b58
0x000000070a913760  1  1888    0x0000000709035198  dead    sun/reflect/DelegatingClassLoader@0x0000000703c50b58
0x0000000709f3fd40  1  3032      null      dead    sun/reflect/DelegatingClassLoader@0x0000000703c50b58
0x000000070923ba78  1  3088    0x0000000709035260  dead    sun/reflect/DelegatingClassLoader@0x0000000703c50b58
0x000000070a88fff8  1  3048      null      dead    sun/reflect/DelegatingClassLoader@0x0000000703c50b58
0x000000070adcbc58  1  1888    0x0000000709035198  dead    sun/reflect/DelegatingClassLoader@0x0000000703c50b58

JVM调优命令-jmap - 钰火 - 博客园
https://www.cnblogs.com/myna/p/7573843.html

jmap查看内存使用情况与生成heapdump
http://www.cnblogs.com/yjd_hycf_space/p/7753847.html


jhat

JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看。在此要注意,一般不会直接在服务器上进行分析,因为jhat是一个耗时并且耗费硬件资源的过程,一般把服务器生成的dump文件复制到本地或其他机器上进行分析。

通常用法:jmap导出堆内存dump文件,然后使用jhat来分析。dump出来的文件还可以用MAT、VisualVM等工具查看。

命令格式:
jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file>

参数:

  • -J< flag >
    因为 jhat 命令实际上会启动一个JVM来执行, 通过 -J 可以在启动JVM时传入一些启动参数. 例如, -J-Xmx512m 则指定运行 jhat 的Java虚拟机使用的最大堆内存为 512 MB. 如果需要使用多个JVM启动参数,则传入多个 -Jxxxxxx.
  • -stack false|true
    关闭对象分配调用栈跟踪(tracking object allocation call stack)。 如果分配位置信息在堆转储中不可用. 则必须将此标志设置为 false. 默认值为 true.
  • -refs false|true
    关闭对象引用跟踪(tracking of references to objects)。 默认值为 true. 默认情况下, 返回的指针是指向其他特定对象的对象,如反向链接或输入引用(referrers or incoming references), 会统计/计算堆中的所有对象。
  • -port port-number
    设置 jhat HTTP server 的端口号. 默认值 7000。
  • -exclude exclude-file
    指定对象查询时需要排除的数据成员列表文件(a file that lists data members that should be excluded from the reachable objects query)。 例如, 如果文件列列出了 java.lang.String.value , 那么当从某个特定对象 Object o 计算可达的对象列表时, 引用路径涉及 java.lang.String.value 的都会被排除。
  • -baseline exclude-file
    指定一个基准堆转储(baseline heap dump)。 在两个 heap dumps 中有相同 object ID 的对象会被标记为不是新的(marked as not being new). 其他对象被标记为新的(new). 在比较两个不同的堆转储时很有用。
  • -debug int
    设置 debug 级别. 0 表示不输出调试信息。 值越大则表示输出更详细的 debug 信息。
  • -version
    启动后只显示版本信息就退出。

使用示例:

jhat -port 7000 dump.hprof

用jmap把进程内存使用情况dump到文件中:
jmap -dump:live,format=b,file=tapi-server.hprof 105970
用jhat分析并启动一个http服务器:

[prouser@vm-vmw96712-app bin]$ ./jhat -port 7000 tapi-server.hprof
Reading from tapi-server.hprof...
Dump file created Mon Apr 09 14:24:00 CST 2018
Snapshot read, resolving...
Resolving 6003870 objects...
Chasing references, expect 1200 dots............................................................
Eliminating duplicate references..........................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

在浏览器中查看 http://ip:7000/

JVM调优命令-jhat - 钰火 - 博客园
https://www.cnblogs.com/myna/p/7590620.html


jinfo

JVM Configuration info这个命令作用是实时查看虚拟机运行参数。

命令语法:

jinfo [option] <pid> 连接到正在运行的java进程
jinfo [option] <executable> <core> 连接到core文件
jinfo [option] [server_id@]<remote server IP or hostname> 连接到远程主机

参数:
-flag:输出指定args参数的值
-flags:输出所有JVM参数的值
-sysprops:输出系统属性,等同于System.getProperties()

JVM调优命令-jinfo
http://www.cnblogs.com/myna/p/7595084.html


jdb

Java调试工具(Java Debugger),主要用于对Java应用进行断点调试。


上一篇 2018年运动记录

下一篇 Java面试准备-(03)线程和并发

阅读
29,719
阅读预计112分钟
创建日期 2018-03-28
修改日期 2018-11-16
类别
目录
  1. jvm 虚拟机
    1. JVM包括哪几部分?(哪几个子系统)
  2. JVM内存管理
    1. 主内存和线程本地内存
      1. 哪些变量分配在堆上哪些变量分配在栈上
    2. JVM运行时内存划分(jdk8之前)
      1. 线程独有内存区域(随线程启动创建)
        1. PC程序计数器(线程独有)
        2. VM Stack虚拟机栈(线程独有)
        3. NATIVE METHOD STACK本地方法栈(线程独有)
      2. 线程间共享内存区域(随虚拟机启动创建)
        1. HEAP堆(新生代+老年代)
        2. METHOD AREA方法区(持久代)
          1. 方法区和永久代的区别?
    3. jdk7将永久代的常量移到堆中
      1. 字符串常量溢出测试(如何写一个方法区溢出的代码?)
    4. jdk8中的内存划分
      1. 为什么废弃永久代?(与其他vm兼容/内存限制)
      2. 元空间和永久代的区别?
      3. 元空间的容量
        1. 元空间配置参数
      4. 元空间的垃圾回收
    5. 堆内存分配策略
      1. TLAB(内存分配的线程安全考虑)
      2. 一、对象优先在Eden区分配
      3. 二、大对象直接进入老年代
      4. 三、长期存活的对象进入老年代
      5. 四、动态对象年龄判断
      6. 五、老年代空间分配担保
    6. Java内存溢出与内存泄露
      1. Java内存溢出
        1. 堆溢出(OutOfMemory)
        2. 深递归导致栈内存溢出(StackOverflow)
        3. 永久代溢出(循环申请intern字符串常量)
      2. Java内存泄露
        1. 集合类中引用不需要的对象
        2. 放入HashMap/HashSet中的对象取不出
        3. 监听器
        4. Connect/File/Session用完不关闭
        5. 单例对象引用外部对象
        6. 缓存
    7. 堆外内存(直接内存)
      1. 使用堆外内存的优点与缺点
      2. 通过sun.misc.Unsafe类使用堆外内存
        1. 如何获取Unsafe类实例(反射)
        2. Unsafe类分配堆外内存实例
        3. Unsafe类提供的功能
      3. 通过NIO中的ByteBuffer使用堆外内存
    8. 内存分代与垃圾回收
      1. 为什么要分代?(生命周期不同)
      2. 如何分代?
      3. 新生代
        1. 为什么需要survivor区?(避免快速填满老年代)
        2. 为什么需要两个survivor区?(避免垃圾碎片)
        3. 两个survivor区大小一定相等吗?(scavenge动态调节)
        4. 如何配置新生代大小?(-XX:NewRatio=4)
        5. 如何配置survivor区大小?(-XX:SurvivorRatio=8)
      4. 年老代
      5. 持久代(方法区)
        1. 方法区gc(废弃常量,无用的类)
        2. 什么情况下要关注方法区gc?(反射,代理等动态生成类时)
      6. 什么时候会触发Full GC?
      7. 哪些内存(对象)需要被回收?
        1. 引用计数法
        2. 可达性分析法(根搜索算法)
        3. 不可达对象一定被回收吗?(两次标记,finalize()方法)
        4. java中的四种引用方式
          1. 强引用(StrongReference)
          2. 软引用(SoftReference)
          3. 弱引用(WeakReference)
          4. 虚引用(PhantomReference)
          5. 为什么需要软引用和弱引用?(干预gc和生命周期)
          6. 什么情况下使用软/弱/虚引用?
      8. 垃圾收集算法
        1. 标记-清除(Mark-Sweep)算法
        2. 复制(Copying)算法(新生代)
          1. 为什么新生代分为eden和survivor(基于复制算法)
        3. 标记-整理(Mark-Compact)算法(老年代)
        4. 分代应用不同回收算法
      9. 垃圾收集器(collector种类)
        1. Serial收集器
        2. ParNew收集器
        3. CMS收集器
    9. jvm参数
      1. 如何配置堆大小(-Xms,-Xmx)
      2. 如何配置新生代大小(-Xmn)
      3. 如何配置永久代大小(-XX:PermSize)
      4. 如何生成dump文件
      5. jvm调优(合理配置堆内存各部分大小)
  3. JVM类加载子系统
    1. 类加载过程
    2. 类加载器
      1. 双亲委派模型
    3. Class.forName做了什么
      1. Class.forName()和ClassLoader.loadClass()区别
    4. 对象的创建过程
      1. 类加载检查
      2. 在堆上为对象分配内存
      3. 对象内存初始化
      4. 对象头设置
      5. 对象初始化
  4. Java安全
    1. Java安全沙箱的构成
    2. Jar包签名和验证
      1. 用keytool生成keystore密钥对
      2. 用jarsigner对jar包签名和验证
    3. class文件校验器(4趟扫描)
    4. 安全管理器SecurityManager
      1. 安全管理器的工作过程
      2. 如何启动安全管理器?
        1. 添加java启动参数-Djava.security.manager
          1. 指定安全策略文件java.policy
          2. 策略和保护域
        2. 编码方式启动System.setSecurityManager
      3. 实现一个安全管理器
    5. 访问控制器
  5. JIT即时编译
    1. 解释运行与编译运行
    2. 热点代码探测
      1. 什么是热点代码?
      2. 如何确定热点代码?(计数器)
    3. 逃逸分析(Escape Analysis)
      1. 栈上分配
        1. 是否所有对象都是在堆中分配内存?
      2. 同步消除
      3. 标量替换
      4. 开启逃逸分析
  6. jdk工具
    1. jvisualvm和jconsole
    2. wsimport
    3. javap
    4. jps
      1. jps命令原理
    5. jstack
      1. jstack -l pid 定位死锁
    6. jstat
      1. jstat -gc pid 查看gc次数
      2. jstat -gccapacity pid
      3. jstat -gcutil pid 查看堆内存百分比
      4. jstat -gccause pid 查看gc原因
      5. jstat -gcnew pid
      6. jstat -gcnewcapacity pid
      7. jstat -gcold pid
      8. jstat -gcoldcapacity pid
      9. jstat -gcpermcapacity pid
      10. jstat -class pid
      11. jstat -compiler pid
      12. jstat -printcompilation pid
    7. jmap
      1. jmap -dump:live,format=b,file=a pid
      2. jmap -heap pid
      3. jmap -finalizerinfo pid 看待回收对象
      4. jmap -histo:live pid
      5. jmap -permstat pid
    8. jhat
      1. jhat -port 7000 dump.hprof
    9. jinfo
    10. jdb
百度推荐