jvm体系结构
黄色:所有线程共享、占用空间较大,存在垃圾回收
灰色:各个线程独享数据区域、占用空间较小,不存在垃圾回收
类装载器ClassLoader
是什么
负责加载class文件,class文件在文件开头有特定的文件标示,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构并且ClassLoader只负责class文件的加载,至于它是否可以运行,则有Execution Engine决定。
类装载器类型
虚拟机自带的加载器
- 启动类加载器
(Bootstrap)C++ - 扩展类加载器
(Extension)Java - 应用程序类加载器
(AppClassLoader),Java也叫系统类加载器,加载当前应用的classpath的所有类
用户自定义加载器
Java.lang.ClassLoader的子类,用户可以定制类的加载方式
演示一
双亲委派
双亲委派模型的式作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完全这个加载请求时,子加载器才会尝试自己去加载。
沙箱安全
沙箱机制是由基于双亲委派机制上 采取的一种JVM的自我保护机制,假设你要写一个java.lang.String的类,由于双亲委派机制的原理,此请求会先交给Bootstrap试图进行加载,但是Bootstrap在加载类时首先通过包和类名查找rt.jar中有没有该类,有则优先加载rt.jar包中的类,因此就保证了java的运行机制不会被破坏
演示二
本地接口
本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候是C/C++横行的时候,要想立足,必须有调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraties。
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或着Java系统管理生产设备,在企业级应用中已经比较少见。应为现在的异构领域间的哦通信很发达,比如可以使用Socket通信,也可以使用Web Service等等,不多做介绍。
本地方法栈
它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载本地方法库。
演示三
线程的start方法调用了start0方法就是一个本地方法,只有声明没有实现
程序计数器
类似排班值日表
方法区
- 所有线程共享,存在垃圾回收
- 存类模板信息
(Car Class) - 方法区是一种规范
- Java7是永久代
- Java8是元空间
- 实例变量存在堆内存中,和方法区无关
Java栈
栈管运行,堆管存储
队列(FIFO)先进先出
栈(FILO)先进后出
Java栈保存:8种基本数据类型、对象的引用变量、实例方法
Java中层面main方法是程序的入口,main方法会被保存到Java栈中,Java栈中层面叫做栈帧
演示四
方法递归调用,栈溢出
栈、堆、方法区的交互关系
创建对象的过程Object obj = new Object(),obj存储在**Java栈中,实例对象数据存储在堆中,实例对象数据是由类模板创建出来的,类模板存储在方法区**中
堆
堆结构
逻辑上分
- 新生代
- 伊甸区
- 幸存0区
- 幸存1区
- 老年代
- 永久代(Java7)、元空间(Java8),方法区是一种规范,永久代或者是元空间是方法区的不同落地实现
对于HotSpot虚拟机,很多开发者习惯将方法区称之为“永久代(Parmanent Gen)” ,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于是一个接口interface)的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。
物理上则排除了永久代或者是元空间
证明堆结构
package com.zbiti.jvm;
public class HeapStructureDemo {
public static void main(String[] args) {
//返回 Java 虚拟机试图使用的最大内存量。
long maxMemory = Runtime.getRuntime().maxMemory();
//返回 Java 虚拟机中的内存总量。
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("MAX_MEMORY = " + maxMemory + "(字节)、" + (maxMemory / (double) 1024 / 1024) + "MB");
System.out.println("TOTAL_MEMORY = " + totalMemory + "(字节)、" + (totalMemory / (double) 1024 / 1024) + "MB");
}
}
添加VM options :-XX:+PrintGCDetails
控制台输出
可以看到输出了堆结构:新生代PSYoungGen、老年代ParOldGen、元空间Metaspace,那如何知道物理上堆结构只包含新生代和老年代呢
新生代加上老年代的和38400K+87552K=125952K
堆内存的默认初始化大小128974848字节,128974848/1024=125952K,因此可知物理上堆结构只包含新生代和老年代
堆内存调优
Java7
Java8
| 参数 | 说明 |
|---|---|
-Xms |
设置初始分配大小,默认为物理内存的1/64 |
-Xmx |
最大分配内存,默认为物理内存的1/4 |
-XX:+PrintGCDetails |
输出详细的GC处理日志 |
修改堆参数,模拟堆溢出
-Xms1024m -Xmx1024m -XX:+PrintGCDetails,设置堆初始值和最大值一样,避免峰值的低、高抖动
package com.zbiti.jvm;
import java.util.Random;
public class HeapSpaceDemo {
public static void main(String[] args) {
String str = "com.zbiti.com";
while(true){
str+=str+new Random().nextInt(88888888)+new Random().nextInt(999999999);
}
}
}
控制台
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
4大垃圾回收算法
JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代。因此GC按照回收的区域又分了两种类型,一种是普通GC(minor GC),一种是全局GC(major GC or Full GC)
- 普通
GC(minor GC):只针对新生代区域的GC,指发生在新生代的垃圾收集动作,因为大多数Java对象存活率都不高,所以Minor GC非常频繁,一般回收速度也比较快。 - 全局
GC(major GC or Full GC):指发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次的Minor GC(但并不是绝对的)。Major GC的速度一般要比Minor GC慢上10倍以上
引用计数法
复制算法(Copying)
使用在新生代
绿色:空闲的堆内存空间(新生代)
红色:可回收的垃圾对象
黄色:对象已经占用的堆内存空间
蓝色:堆内存空间(老年代)
标记清除(Mark-Sweep)
使用在老年代
标记压缩(Mark-Compact)
使用在老年代,比标记清除多一步压缩
本文由博客一文多发平台 OpenWrite 发布!