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 发布!