文章目录

JVM启动流程



jvm.cfg JVM配置文件

JNIEnv提供了大量的和JVM交互的一些操作,比如findClass

PC寄存器

每个线程拥有一个PC寄存器

在线程创建时 创建

指向下一条指令的地址

执行本地方法时,PC的值为undefined

方法区

注意:

JDK6时,String等常量信息置于方法区

JDK7时,已经移动到了堆

保存装载的类信息

类型的常量池

字段,方法信息

方法字节码

通常和永久区(Perm)关联在一起

永久区(Perm)保存相对静止的数据,虽然类的数据在运行的过程当中可能会出现调整,比如说热加载,如果是热加载热替换的话,显然会有一个删除然后重新加载的过程,虽然这些信息可能会发生变动,但是应用层面的软件相比是比较稳定的,这样的稳定就是永久区,但是永久区并不是永远不会变的。

Java堆

跟方法区相比,方法区是jvm来维护的,而java堆所有代码中通过new出来的对象,基本上都是在java堆当中

  • 和程序开发密切相关
  • 应用系统对象都保存在Java堆中
  • 所有线程共享Java堆
  • 对分代GC来说,堆也是分代的
  • GC的主要工作区间:

    eden | s0 | s1 | tenured

    eden对象出生的地方,s0 s1使用复制算法,tenured老年代。

Java栈

堆是全局共享的,栈是线程私有的

  • 线程私有
  • 栈由一系列帧组成(因此Java栈也叫做帧栈)
  • 帧保存一个方法的局部变量、操作数栈、常量池指针
  • 每一次方法调用创建一个帧,并压栈

Java栈 – 局部变量表 包含参数和局部变量

public class StackDemo {

	public static int runStatic(int i,long l,float  f,Object o ,byte b){
		return 0;
	}
	public int runInstance(char c,short s,boolean b){
		return 0;
	}
}
//runStatic静态方法的栈示意
//因为一个槽位只能容纳32位的大小,即int,所以int占了一个槽位,long(64位)占了两个槽位
//对象是引用reference(32位)
-----------------------
0 int int i
-----------------------
1 long long l
-----------------------
3 float float f
-----------------------
4 reference Object o
-----------------------
5 int byte b
-----------------------
//runInstance方法的示意
-----------------------
0 reference this
-----------------------
1 int char c
-----------------------
2 int short s
-----------------------
3 int boolean b
-----------------------

注意: runInstance和runStatic的区别是runInstance比runStatic多了一个自身的this引用。

Java栈 – 函数调用组成帧栈

public static int runStatic(int i,long l,float  f,Object o ,byte b){
return runStatic(i,l,f,o,b);
}
-----------------------
0 int int i
-----------------------
1 long long l
-----------------------
3 float float f
-----------------------
4 reference Object o
-----------------------
5 int byte b
-----------------------
//上面为一个帧,省略:操作数栈、返回地址等。

-----------------------
0 int int i
-----------------------
1 long long l
-----------------------
3 float float f
-----------------------
4 reference Object o
-----------------------
5 int byte b
-----------------------

-----------------------
0 int int i
-----------------------
1 long long l
-----------------------
3 float float f
-----------------------
4 reference Object o
-----------------------
5 int byte b
-----------------------

每次方法调用就会有一个帧被压入栈,上面是一个递归调用,一直往上面压,一直到帧栈满了为止,发生溢出,方法调用结束了,帧又会自动从帧栈里面移除掉。

Java栈 – 操作数栈

Java没有寄存器,所有参数传递使用操作数栈

public static int add(int a,int b){
	int c=0;
	c=a+b;
	return c;
}
 0:   iconst_0 // 0压栈
 1:   istore_2 // 弹出int,存放于局部变量2
 2:   iload_0  // 把局部变量0压栈
 3:   iload_1 // 局部变量1压栈
 4:   iadd      //弹出2个变量,求和,结果压栈
 5:   istore_2 //弹出结果,放于局部变量2
 6:   iload_2  //局部变量2压栈
 7:   ireturn   //返回

Java栈 – 栈上分配

C++ 代码示例

class BcmBasicString{    ....}
堆上分配,每次需要清理空间
public void method(){
BcmBasicString* str=new BcmBasicString;    ....    delete str;
}

上面的代码new是放在了堆里面,每次用完之后需要delete,如果忘记的会就会发生内存泄漏,这种错误是很难发现的。

栈上分配,函数调用完成自动清理
//声明出来的写法
public void method(){
BcmBasicString str;
  ....
}

java代码

public class OnStackTest {
    public static void alloc(){
        byte[] b=new byte[2];
        b[0]=1;
    }
    public static void main(String[] args) {
        long b=System.currentTimeMillis();
        for(int i=0;i<100000000;i++){
            alloc();
        }
        long e=System.currentTimeMillis();
        System.out.println(e-b);
    }
-server -Xmx10m -Xms10m
-XX:+DoEscapeAnalysis -XX:+PrintGC

这种方式运行

输出结果 5

-server -Xmx10m -Xms10m
-XX:-DoEscapeAnalysis -XX:+PrintGC

这种方式运行输出如下结果

……
[GC 3550K->478K(10240K), 0.0000977 secs]
[GC 3550K->478K(10240K), 0.0001361 secs]
[GC 3550K->478K(10240K), 0.0000963 secs]
564

说明方式一是在栈上面分配内存,方式二是在堆上分配内存,jvm内部是有相关的优化的,当数据量不是很大的时候,通过栈的方式分配,能够减小gc的压力。

总结:

  • 小对象(一般几十个bytes),在没有逃逸的情况下(逃逸,即分配出来之后除了我的线程要用,其他的线程也要用的时候,因为栈是线程私有的。),可以直接分配在栈上
  • 直接分配在栈上,可以自动回收,减轻GC压力(函数调用完成之后)
  • 大对象或者逃逸对象无法栈上分配

栈、堆、方法区交互

小例子



JVM通过栈调用main方法,局部变量test1存放着Sample实例的引用,用过实例的引用去堆中找到Sample实例,Sample实例的相关信息在方法区中存放着。

public   class  AppMain
 //运行时, jvm 把appmain的信息都放入方法区
 { public   static   void  main(String[] args)
//main 方法本身放入方法区。 {
 Sample test1 = new  Sample( " 测试1 " );
 //test1是引用,所以放到栈区里, Sample是自定义对象应该放到堆里面
 Sample test2 = new  Sample( " 测试2 " );
 test1.printName(); test2.printName(); }
public   class  Sample
 //运行时, jvm 把appmain的信息都放入方法区
 { private  name;
 //new Sample实例后, name 引用放入栈区里,  name 对象放入堆里
 public  Sample(String name)
 { this .name = name; }
 //print方法本身放入 方法区里。public   void  printName()
 { System.out.println(name); }
 }

JVM基本结构

内存模型

  • 每一个线程有一个工作内存和主存独立

    这里的主存指的是堆内存,共享内存
  • 工作内存存放主存中变量的值的拷贝



    当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作

每一个操作都是原子的,即执行期间不会被中断

对于普通变量,一个线程中更新的值,不能马上反应在其他变量中

如果需要在其他线程中立即可见,需要使用 volatile 关键字


可以看到从工作内存到主存中是需要一定的时间的,所以当一个线程中更新了值的时候,不能够在其他的线程中立即可见。

volatile

public class VolatileStopThread extends Thread{
	private volatile boolean stop = false;
	public void stopMe(){
		stop=true;
	}

	public void run(){
		int i=0;
		while(!stop){
			i++;
		}
	    System.out.println("Stop thread");
	}

	public static void main(String args[]) throws InterruptedException{
		VolatileStopThread t=new VolatileStopThread();
		t.start();
		Thread.sleep(1000);
		t.stopMe();
		Thread.sleep(1000);
	}
}

没有volatile -server 运行 无法停止

volatile 不能代替锁

一般认为volatile 比锁性能好(不绝对)

选择使用volatile的条件是:

语义是否满足应用

内存模型的几个重要概念

可见性

一个线程修改了变量,其他线程可以立即知道

保证可见性的方法

volatile

synchronized (unlock之前,写变量值回主存)

final(一旦初始化完成,其他线程就可见)

有序性

在本线程内,操作都是有序的

在线程外观察,操作都是无序的。(指令重排 或 主内存同步延时)

指令重排

线程内串行语义

写后读 a = 1;b = a; 写一个变量之后,再读这个位置。

写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。

读后写 a = b;b = 1; 读一个变量之后,再写这个变量。

以上语句不可重排

编译器不考虑多线程间的语义

可重排: a=1;b=2;

指令重排 – 破坏线程间的有序性

class OrderExample {
int a = 0;
boolean flag = false;

public void writer() {
    a = 1;
    flag = true;
}

public void reader() {
    if (flag) {
        int i =  a +1;
        ……
    }
}
}

线程A首先执行writer()方法

线程B线程接着执行reader()方法

线程B在int i=a+1 是不一定能看到a已经被赋值为1

因为在writer中,两句话顺序可能打乱

线程A
flag=true
a=1
线程B
flag=true(此时a=0)

指令重排 – 保证有序性的方法

class OrderExample {
int a = 0;
boolean flag = false;

public synchronized void writer() {
    a = 1;
    flag = true;
}

public synchronized void reader() {
    if (flag) {
        int i =  a +1;
        ……
    }
}
}

同步后,即使做了writer重排,因为互斥的缘故,reader 线程看writer线程也是顺序执行的。在线程A执行完之前,线程B是进不来的。

线程A
flag=true
a=1
线程B
flag=true(此时a=1)

指令重排的基本原则

程序顺序原则:一个线程内保证语义的串行性

volatile规则:volatile变量的写,先发生于读

锁规则:解锁(unlock)必然发生在随后的加锁(lock)前

传递性:A先于B,B先于C 那么A必然先于C

线程的start方法先于它的每一个动作

线程的所有操作先于线程的终结(Thread.join())

线程的中断(interrupt())先于被中断线程的代码

对象的构造函数执行结束先于finalize()方法

编译和解释运行的概念(字节码运行的两种方式)

解释运行

解释执行以解释方式运行字节码

解释执行的意思是:读一句执行一句

编译运行(JIT–just in time)

将字节码编译成机器码

直接执行机器码

运行时编译

编译后性能有数量级的提升

编译执行和解释执行性能应该差十倍以上。

编译和解释运行的概念

JVM内核-原理、诊断与优化学习笔记(二):JVM运行机制的更多相关文章

  1. 深入JVM内核---原理,诊断与优化

    JVM的概念 JAM是Java Virtual Machine的简称.意为Java虚拟机 虚拟机 指通过软件模拟的具有完整硬件系统功能的,运行在一种完整隔离环境中的完整计算机系统 有哪些虚拟机 - V ...

  2. JVM内核-原理、诊断与优化学习笔记(七):性能监控工具

    文章目录 系统性能监控 系统性能监控- linux uptime top vmstat(虚拟内存统计) pidstat 系统性能监控 - windows 任务管理器 Perfmon Process E ...

  3. JVM内核-原理、诊断与优化学习笔记(六):类装载器

    文章目录 class装载验证流程 class装载验证流程 class装载验证流程 -加载 class装载验证流程 -链接 验证 链接 -> 验证 文件格式的验证 元数据验证(class文件简单语 ...

  4. JVM内核-原理、诊断与优化学习笔记(一):初识JVM

    文章目录 JVM的概念 JVM是Java Virtual Machine的简称.意为Java虚拟机 虚拟机 有哪些虚拟机 VMWare或者Visual Box都是使用软件模拟物理CPU的指令集 JVM ...

  5. go学习笔记二:运行使用命令行参数

    本文只作为博主的go语言学习笔记. 对命令行参数的解析,只是在运行时使用的,比如以下命令:go run gomain -conf conf.toml 没有办法再go build时使用. 一.运行时命令 ...

  6. JVM内核-原理、诊断与优化学习笔记(八):JAVA堆分析

    文章目录 内存溢出(OOM)的原因 在JVM中,有哪些内存区间? 堆溢出 永久区 Java栈溢出 直接内存溢出 小问题? MAT使用基础 柱状图显示 支配树 显示线程信息 显示堆总体信息,比如消耗最大 ...

  7. JVM内核-原理、诊断与优化学习笔记(四):GC算法与种类

    文章目录 GC的概念 GC算法 引用计数法 引用计数法的问题 标记清除 标记压缩 小问题 复制算法 复制算法的最大问题是:空间浪费 整合标记清理思想 -XX:+PrintGCDetails的输出 gc ...

  8. JVM内核-原理、诊断与优化学习笔记(三):常用JVM配置参数

    文章目录 Trace跟踪参数 -verbose:gc (打开gc的跟踪情况) -XX:+printGC(打开gc的log开关,如果在运行的过程中出现了gc,就会打印出相关的信息.) -XX:+Prin ...

  9. JVM内核-原理、诊断与优化学习笔记(十一):JVM字节码执行

    文章目录 javap javap 举个

随机推荐

  1. PHP ftp_fget() 函数

    定义和用法 ftp_fget() 函数从 FTP 服务器上下载一个文件并保存到本地一个已经打开的文件中. 如果成功,该函数返回 TRUE.如果失败,则返回 FALSE. 语法 ftp_fget(ftp ...

  2. 通用shellcode代码

    #include <stdio.h>#include <windows.h> int main(){ __asm { CLD //清空标志位DF push 0x1E380A6A ...

  3. Http头域字段详解

    HTTP(HyperTextTransferProtocol) 是超文本传输协议的缩写,它用于传送WWW方式的数据,关于HTTP协议的详细内容请参考RFC2616.HTTP协议采用了请求/响应模型.客 ...

  4. 堆、栈、方法区、静态代码块---Java

    java 堆.栈.方法区 堆区: 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息.(class的目的是得到操作指令) 2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基 ...

  5. bigdecimal解决小数间的加减乘除

    public class bigdecimal { public static BigDecimal div(double v1,double v2){ BigDecimal b1=new BigDe ...

  6. Appium 工作原理

    Appium - automation for mobile apps   一.Appium架构介绍 官网:www.appium.io 由SauceLab公司主持.并在Google的GATC2013会 ...

  7. Jeecg 3.8修改lhgDialog弹窗的样式

    位置:F:\jeecg-bpm-3.8\eecg-bpm-3.8-master\jeecg-bpm-3.8\src\main\java\org\jeecgframework\core\util pub ...

  8. jQuery 加载事件

    1. jquery加载事件实现 ① $(document).ready(function处理); ② $().ready(function处理); ③ $(function处理);  对第一种加载的封 ...

  9. 广度优先搜索(Breadth First Search)

    Date:2019-07-03 14:29:02 走完一层的所有房间,再走下一层,用队列实现 算法实现 /*--------------------------模版------------------ ...

  10. 【转】手机web页面制作时的注意事项

    一.手机页面的标准头规范 字符编码使用utf-8:指定页面手机内存缓存中的存储时间段 device-width:通知浏览器使用设备的宽度作为可视区的宽度 initial-scale - 初始的缩放比例 ...