如下图所示:
黄色部分为线程共有,蓝色部分为线程私有。
整体流程如下图所示:
第1步:虚拟机遇到一个 new 指令,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用的类是否已经被 加载 - 解析 - 初始化。
第2步:如果类已经被加载那么进行第3步;如果没有进行加载,那么就需要先进行类的加载。
第3步:类加载检查通过之后,接下来进行新生对象的内存分配。
第4步:对象生成需要的内存大小在类加载完成后便可完全确定,为对象分配空间等同于把一块确定大小的内存从 Java堆 中划分出来。
第5步:内存大小的划分分为两种情况:
第一种情况:JVM的内存是规整的,所有的使用的内存都放到一边,空闲的内存在另外一边,中间放一个指针作为分界点的指示器。那么这时候分配内存就比较简单,只要讲指针向空闲空间那边挪动一段与对象大小相同的距离。这种就是“指针碰撞”。
第二种情況:JVM 的内存不是规整的,也就是说已使用的内存与未使用的内存相互交错。这时候就没办法利用指针碰撞了。这时候我们就需要维护一张表,用于记录那些内存可用,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新到记录表上。
第6步:空间申请完成之后,JVM需要将内存的空间都初始化为 0 值。如果使用 TLAB,就可以在 TLAB 分配的时候就可以进行该工作。
第7步:JVM对对象进行必要的设置。例如,这个对象是哪个类的实例、对象的哈希码、GC年代等信息。
第8步:完成了上面的步骤之后 从 JVM 来看一个对象基本上完成了,但从 Java 程序代码绝对来看,对象创建才刚刚开始,需要执行 < init > 方法,按照程序中设定的初始化操作初始化,这时候一个真正的程序对象生成了。
类加载的过程包括了:
第一步:加载
加载是类加载过程的第一个阶段,虚拟机在这一阶段需要完成以下三件事情:
通过类的全限定名来获取其定义的二进制字节流
将字节流所代表的静态存储结构转化为方法区的运行时数据结构
在 Java 堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口
第二步:验证
确保被加载的类的正确性。
这一阶段是确保 Class 文件的字节流中包含的信息符合当前虚拟机的规范,并且不会损害虚拟机自身的安全。
包含了四个验证动作:文件格式验证,元数据验证,字节码验证,符号引用验证。
第三步:准备
为类的静态变量分配内存,并将其初始化为默认值。
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。
第四步:解析
把类中的符号引用转换为直接引用。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行。
第五步:初始化
类变量进行初始化。
为类的静态变量赋予正确的初始值,JVM 负责对类进行初始化,主要对类变量进行初始化。
从图中可以看出,CMS 收集器的工作过程可以分为 4 个阶段:
初始标记(CMS initial mark)阶段
并发标记(CMS concurrent mark)阶段
重新标记(CMS remark)阶段
并发清除((CMS concurrent sweep)阶段
使用算法:复制+标记清除
G1 在压缩空间方面有优势。
G1 通过将内存空问分成区域 (Region)的方式避免内存碎片问题,
Eden , Survivor , old 区不再固定、在内存使用效率上区说更灵活。
G1 可以通过设置预期停顿时间 (Pause Time) 区控制垃圾收集时间避免应用雪崩现象。
G1 在回收内存后会马上同时做合并空闲内存的工作、而 CMS 默认是在 STW (stopthe world)的时候做
G1 会在Young GC 中使用、而 CMS 只能在 o 区使用。
吞吐量优先:G1
响应优先:CMS
CMS 的缺点是对 cpu 的要求比较高。
G1是将内存化成了多块,所有对内段的大小有很大的要求。
CMS 是清除,所以会存在很多的内存碎片。
G1 是整理,所以碎片空间较小。
Xms:初始堆大小
Xmx:最大堆大小
Xss: Java 每个线程的Stack大小
XX:NewSize=n:设置年轻代大小。
XX:NewRatio=n:设置年轻代和年老代的比代。如:为3,表示年轻代与年老代比代为 1:3,年轻代占整个年轻代年老代和的 1/4。
XX:SurvivorRatio=n:年轻代中 Eden 区与两个 Survivor 区的比代。注意 Survivor区有两个。如:3,表示 Eden : Survivor=3: 2,一个 survivor 区占整个年轻代的1 / 5。
XX: MaxPermSize=n:设置持久代大小。
XX:+UseSesialGC:设置串行收集器
XX:+UsePasallelGC:设置并行收集器
XX:+UsePasalledlOld GC:设置并行年老代收集器
XX:+UseConcMask SweepGC:设置并发收集器
XX:+PsintGC:打印GC 的简要信息
XX:+PsintGCDetails:打印GC详细信息
XX:+PsintGCTimeStamps:输出 GC 的时间戳
jps:用来显示本地的Java 进程,可以查看本地运行着几个 Java程序,并显示他们的进程号
命令格式:ips
jinfo:运行环境参数:Java System 属性和 JVM 命令行参数,Java class path 等信息
命令格式:info 进程 pid
jstat:监视虛拟机各种运行状态信息的命令行工具
命令格式:jstat -gc 123 250 20
jstack:可以观察到 JVM 中当前所有线程的运行情况和线程当前状态。
命令格式:istack 进程 pid
jmap:观察运行中的 JVM 物理内存的占用情况(如:产生哪些对象,及其数量)
命令格式:jmap [loption] pid
调用 System.gc 时,系统建议执行 Full GC, 但是不必然执行。
老年代空间不足。
方法区空间不足。
超过 Minor GC 后进入老年代的平均大小大于老年代的可用内存。
由 Eden 区、survivor spacel (From Space)区向 survivor space2 (To Space)区复制时,对象大小大于 To Space 可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
方法创建了个很大的对象,如 List , Array。
是否产生了循环调用、死循环。
是否引用了较大的全局变量。
强引用:new 出的对象之类的引用,只要强引用还在,永远不会回收。
软引用:引用但非必须的对象,内存溢出异常之前,回收。
弱引用:非必须的对象,对象能生存到下一次垃圾收集发生之前。
虚引用:对生存时间无影响,在垃圾回收时得到通知。
top 查看当前 CPU 情况,找到占用 CPU 过高的进程 PID=123。
top -H -p123 找出两个 CPU 占用较高的线程,记录下来 PID=2345, 3456转换为十六进制。
jstack -l 123 > temp.txt 打印出当前进程的线程栈。
查找到对应于第二步的两个线程运行栈,分析代码。
使用top指令查询服务器系统状态。
ps -aux|grep java 找出当前 Java 进程的 PID。
jmap -histo:dve pid 可用统计存活对象的分布情况,从高到低查看占据内存最多的对象。
jmap -dump:format=b,fice=文件名 [pid] 利用 Jmap dump。
使用性能分析工具对上一步dump出来的文件进行分析,工具有 MAT 等。