目录
目录
1.JVM 简介
1.1. 如何理解 JVM 呢?
1.2. 市场主流 JVM 分析?
1.3. 为什么要学习 JVM?
1.4. 字节码底层是如何执行呢?
如何理解 JIT 呢?
为什么 JVM 中解释执行与编译执行的并存(混合模式)?
1.5. 如何理解 JVM 的运行模式?
如何查看现在 JVM 的工作模式?
2.JVM 体系结构
2.1. JVM 的产品架构是怎样的?
2.2. JVM 运行时内存结构是怎样的?
2.2.1. JVM 线程共享区应用分析
1. Heap (堆内存):
2. Method Area (方法区)
2.2.2. JVM 线程私有区应用分析
1. Program Counter Register (程序计数器)
2. Stack Area (虚拟机栈区)
3. Native Stack Area (本地方法栈)
3.JVM 应用参数分析
3.1. 如何理解 JVM 中的内存溢出?
代码演示:堆内存溢出
代码演示:元数据内存溢出(JDK8)
3.2. JVM 工具应用分析篇
3.2.1. 命令行工具篇
1. Jps [options] [hostid] (hostid 为 ip 或域名地址)
2. jmap -heap pid
3. jstack 用于生成 java 虚拟机当前时刻的线程快照
1.JVM 简介
1.1. 如何理解 JVM 呢?
1.2. 市场主流 JVM 分析?
1.3. 为什么要学习 JVM?
1.4. 字节码底层是如何执行呢?
JAVA 源程序编译,执行过程如下图所示:
如何理解 JIT 呢?
为什么 JVM 中解释执行与编译执行的并存(混合模式)?
1.5. 如何理解 JVM 的运行模式?
JVM 有两种运行模式 Server 与 Client。两种模式的区别在于,Client 模
如何查看现在 JVM 的工作模式?
2.JVM 体系结构
2.1. JVM 的产品架构是怎样的?
2.2. JVM 运行时内存结构是怎样的?
JVM 启动运行 Class 文件时会对 JVM 内存进行切分,我们可以将其分为线程共
2.永久代
在自定义类加载器还不是很常见的时候,类大多是static的,很少被卸载或收集,因此被成为“永久的(Permanent)”。
同时,由于类class是JVM实现的一部分,并不是由应用创建的,所以又被认为是“非堆(Non-Heap)”内存。
在JDK8之前的HotSpot JVM,存放这些“永久的”区域叫做“永久代(permanent generation)”。
2.2.1. JVM 线程共享区应用分析
1. Heap (堆内存):
2. Method Area (方法区)
2.2.2. JVM 线程私有区应用分析
1. Program Counter Register (程序计数器)
2. Stack Area (虚拟机栈区)
3. Native Stack Area (本地方法栈)
3.JVM 应用参数分析
3.1. 如何理解 JVM 中的内存溢出?
代码演示:堆内存溢出
public class TestOOM01 { public static void main(String[] args) { long t1=System.currentTimeMillis(); try { List<byte[]> list=new ArrayList<>(); for(int i=0;i<100;i++) { list.add(new byte[1024*1024]); } }finally { long t2=System.currentTimeMillis(); //System.out.println("oom:"+(t2-t1)); } } }
运行时可设置虚拟机参数-Xmx50m -Xms10m -XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=e:/a.dump
代码演示:元数据内存溢出(JDK8)
public class MetaSpaceTest {
public static void main(String[] args) {
int i = 0;
try {
for (i = 0; i < 100000; i++) {
new CglibBean(new HashMap<>());
}
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
System.out.println("total create count:" + i);
}
}
public static class CglibBean {
public CglibBean(Object object) {
Enhancer enhancer = new Enhancer();
enhancer.setUseCache(false);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> obj);
enhancer.setSuperclass(object.getClass());
enhancer.create();
}
}
}
上述代码通过Cglib生成大量的HashMap代理,下面我们在运行这段代码的时候指定下列参数
-XX:MaxMetaspaceSize=100M -XX:+PrintGCDetails
当我们程序循环至3660次,也就是说我们大约在生成了约3660个代理类以后元数据区发生了内存溢出,下面将MaxMetaspaceSize改为50M执行,
从上图可以看出当我们生成了1710个代理类以后元数据区发生了内存溢出,可见一个元数据区的大小决定了Java虚拟机可以装载的类的多少。
3.2. JVM 工具应用分析篇
3.2.1. 命令行工具篇
1. Jps [options] [hostid] (hostid 为 ip 或域名地址)
2. jmap -heap pid
public class TestCommand {
public static void main(String[] args) {
while (true) {
byte[] b1 = new byte[1024 * 1024];
}
}
}
1) MaxHeapFreeRatio: 最大空闲堆内存比例最大空闲对内存比例, GC 后如果发现空闲堆内存大于整个预估堆内存的 N%(百分比 ) , JVM 则会收缩堆内存,但不能小于 -Xms 指定的最小堆的限制。2) MinHeapFreeRatio: 最小空闲堆内存比例GC 后如果发现空闲堆内存小于整个预估堆内存的 N%( 百分比 ), 则 JVM 会增大 堆内存,但不能超过 -Xmx 指定的最大堆内存限制。3) MaxHeapSize: 即 -Xmx, 堆内存大小的上限4) InitialHeapSize: 即 -Xms, 堆内存大小的初始值5) NewSize: 新生代预估堆内存占用的默认值6) MaxNewSize: 新生代占整个堆内存的最大值7) OldSize: 老年代的默认大小 ,8) NewRatio:老年代对比新生代的空间大小 , 比如 2 代表老年代空间是新生代的两倍大小 .9) SurvivorRatio:Eden/Survivor 的值 . 例如 8 表示 Survivor:Eden=1:8, 因为 survivor 区 有 2 个 , 所以 新生代 Eden 的占比为 8/1010)MetaspaceSize:分配给类元数据空间的初始大小 (Oracle 逻辑存储上的初始高水位,the initial high-water-mark ). 此值为估计值 . MetaspaceSize 设置得过大,会延长垃圾回收时间 . 垃圾回收过后 , 引起下一次垃圾回收的类元数据空间的 大小可能会变大11)MaxMetaspaceSize:是分配给类元数据空间的最大值 , 超过此值就会触发 Full GC. 此值仅受限系统内存的大小 , JVM 会动态地改变此值12)CompressedClassSpaceSize:类指针压缩空间大小 , 默认为 1G.13)G1HeapRegionSize:G1 区块的大小 , 取值为 1M 至 32M. 其取值是要根据最小 Heap 大小划分出 2048 个区块 .说明 :jmap 在系统调优时通常会结合 jhat 来分析 jmap 生成的 dump 文件。
3. jstack 用于生成 java 虚拟机当前时刻的线程快照
class SyncTask02 implements Runnable {
private List<Integer> from;
private List<Integer> to;
private Integer target;
public SyncTask02(List<Integer> from, List<Integer> to, Integer target) {
this.from = from;
this.to = to;
this.target = target;
}
@Override
public void run() {
moveListItem(from, to, target);
}
private static void moveListItem(List<Integer> from,
List<Integer> to, Integer item) {
log("attempting lock for list", from);
synchronized (from) {
log("lock acquired for list", from);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log("attempting lock for list ", to);
synchronized (to) {
log("lock acquired for list", to);
if (from.remove(item)) {
to.add(item);
}
log("moved item to list ", to);
}
}
}
private static void log(String msg, Object target) {
System.out.println(Thread.currentThread().getName() +
": " + msg + " " +
System.identityHashCode(target));
}
}
public class TestDeadLock02 {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>(Arrays.asList(2, 4, 6, 8,
10));
List<Integer> list2 = new ArrayList<>(Arrays.asList(1, 3, 7, 9,
11));
Thread thread1 = new Thread(new SyncTask02(list1, list2, 2));
Thread thread2 = new Thread(new SyncTask02(list2, list1, 9));
thread1.start();
thread2.start();
}
}