Java SE体系架构

虚拟机的发展

HotSpot VM

目前适用范围最广的Java虚拟机

JRocket VM

号称“世界上最快的Java虚拟机”

J9 VM

Dalvik VM

未来的Java技术

模块化

混合语言

多核并行

丰富语法

64位

更强的垃圾回收

运行时数据区域

定义

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域

类型

程序计数器 (线程私有)

虚拟机栈(线程私有)

本地方法堆(线程私有)

Java堆(线程共享)

方法区(运行时常量池,线程共享)

直接内存

程序计数器

是指向当前线程正在执行的字节码指令的地址(行号)(面试点)

为什么需要程序计数器(面试点)

Java是多线程的,意味着存在线程切换

确保多线程情况下的程序正常执行

运行时数据区:栈

栈结构

数据结构

入口和出口只有一个

入栈

出栈

特点(面试点)

先进后出(FILO)

为什么JVM要使用栈(面试点)

因为非常符合Java中方法间的相互调用

public class StackFilo {
public static void main(String[] args)
{
A(); //A()->B()->C()
}
public static void A(){
System.out.println("A开始");
//此处省略100行代码
B();//调用B方法
System.out.println("A结束");
}
public static void B(){
System.out.println("B开始");
//此处省略100行代码
C();//调用C方法
System.out.println("B结束");
}
public static void C(){
System.out.println("C开始");
//此处省略100行代码
System.out.println("C结束");
}
}

//log

A开始

B开始

C开始

C结束

B结束

A结束

虚拟机栈

定义

存储当前线程运行方法所需的数据、指令、返回地址

栈帧

每个方法在执行的同时都会创建一个栈帧

栈帧还可以划分

局部变量表

操作数栈

动态连接

返回地址

大小设置 (面试点)

-Xss JDK 1.8默认1M)

只入栈不出栈,会栈溢出(常见于循环调用、递归调用处理不当)

public class JavaStack {
/* public static class User{
public int id = 0;
public String name = "";
}*/
//常量
final String Fs ="常在河边走,哪有不湿鞋";
//静态变量
static String Ss ="以静制动";
//次数
int count =0 ;
//King老师出差
public void king(int money){
//13号技师
Object tech13 = new Object();
//调用一次13号服务
tech13.hashCode();
int i;
money = money -100; //花费100
count++;
//if(count ==2000) return;
king(money);
}
public static void main(String[] args)throws Throwable {
JavaStack javaStack = new JavaStack();
try {
javaStack.king(10000);
}catch (Throwable e){
//输出异常时循环的次数
System.out.println("栈异常!调用方法(king)的次数():"+javaStack.count);
throw e;
}
}
}

虚拟机栈的执行过程演示.png

本地方法栈

定义

本地方法栈保存的是native方法的信息

当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单的动态链接并直接调用native方法(例如hashCode()方法)

虚拟机规范无强制规定,每个版本虚拟机自由实现

HotSpot直接把本地房发展和虚拟机栈合二为一

线程共享区域

方法区

类信息(class)

常量

静态变量

即时编译期编译后的代码

Java堆

对象实例(几乎所有的对象)

数组

堆的大小参数设置

-Xmx 堆区内存可被分配的最大上限

-Xms 堆区内存出事内存分配的大小

直接内存

不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域

如果使用了NIO,这块区域会被频繁使用,在java堆内可以用directByteBuffer对象直接引用并操作

这块内存不受java堆大小限制,但受本机总内存的限制,可以通过MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常

深入辨析堆和栈

功能

以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(int、short、long、byte、float、double、boolean、char)以及对象的引用变量(ref),其内存分配在栈上,变量出了作用域就会自动释放

而堆内存用来存储Java中的对象,无论是成员变量、局部变量,还是类变量,他们指向的对象都存储在堆内存中。

线程独享还是共享

栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即占内存可以理解成线程的私有内存。

堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。

空间大小

栈的内存要远远小于堆内存,栈的深度是有限制的,可能发生StackOverFlowError的问题

虚拟机中对象

对象的内存布局

对象的访问定位

虚拟机优化技术--逃逸分析

逃逸分析

逃逸分析是目前JVM中比较前沿的优化技术,它不是直接优化手段而是为其他优化手段提供依据的分析技术

逃逸分析的基本行为就是分析对象动态作用域。

栈上分配

99% 对象都在堆上分配

1% 对象可能在创建时经逃逸分析,在栈上分配

如果是栈上分配,就不需要垃圾回收了,会随着线程的结束而消亡

public class StackAlloc {
public static class User{
public int id = 0;
public String name = "";
}
public static void alloc() {
User u = new User(); //Object 在堆上分配的() ,有逃逸分析的技术 ,在栈中分配的
u.id = 5;
u.name = "King";
}
public static void main(String[] args) {
long b = System.currentTimeMillis(); //开始时间
for(int i=0;i<100000000;i++) {//一个方法运行1亿次()
alloc();
}
long e = System.currentTimeMillis(); //结束时间
System.out.println(e-b);//打印运行时间:毫秒
}
}

牵扯到的JVM参数

-XX:+DoEscapeAnalysis:启用逃逸分析(默认打开)-XX:-DoEscapeAnalysis(关闭)

XX:DoEscapeAnalysis

-XX:+EliminateAllocations:标量替换(默认打开)

-XX:+UseTLAB 本地线程分配缓冲(默认打开)

-XX:+PrintGC(打印垃圾回收过程)