JVM运行机制

JVM启动流程
Java XXX装载配置根据配置寻找JVM.dll初始化JVM获得JNIEnv接口找到main方法并运行
 根据当前路径和系统版本寻找jvm.cfgJVM.dll为JVM主要实现JNIEnv为JVM接口,findClass等操作通过它实现 

JVM基本结构
PC寄存器:
——每一个线程拥有一个PC寄存器
——在线程创建时创建
——指向下一条指令的地址

——执行本地方法时,值为undefined

方法区:

——保存装载的类信息:类型的常量池、字段,方法信息、方法字节码(JDK6时,String等常量信息置于方法;JDK7时,已经移动到了堆)

——通常和永久区联系一起

Java堆:

——和程序开发密切相关

——应用系统对象都保存在Java堆中

——所有线程共享Java堆

——对分代GC来说,堆也是分代的

——GC的主要工作区间

Java栈:

——线程私有

——栈由一系列帧组成(Java栈也叫帧栈)

——帧保存一个方法的局部变量、操作数栈、常量池指针

——每一次方法调用创建一个帧,并压栈

例:Java栈-局部变量表(包含参数和局部变量)

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;
	}
}
0int    int i
1long    long l
3float    float f
4reference    Object o
5byte    byte b
0reference    this
1int    char c
2int    short s
3int    boolean b
例: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 i0    int    int i0    int    int i
1    long    long l1    long    long l1    long    long l
3    float    float f3    float    float f3    float    float f
4    reference    Object o4    reference    Object o4    reference    Object o
5    int    byte b5    int    byte b5    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栈-栈上分配

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
堆上分配参数:-server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC
-小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上
-直接分配在栈上,可以自动回收,减轻GC压力
-大对象或者逃逸对象无法栈上分配

例:栈、堆、方法区交互

public class AppMain {
	public static void main(String[] args) {
		Sample test1 = new Sample("test1");
		Sample test2 = new Sample("test2");
		test1.printName();
		test2.printName();
	}
}

class Sample {
	private String name;

	public Sample(String name) {
		this.name = name;
	}

	public void printName() {
		System.out.println(name);
	}
}

内存模型

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

——工作内存存放主存中变量值的拷贝


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

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

对于普通变量,一个线程中更新的值,不能马上反应在其他变量中。如果需要在其他线程中立即可见,需要使用 volatile 关键字。

public class VolatileStopThread extends Thread {
	private  volatile boolean stop = false;//无volatile则无法停止

	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 不能代替锁,一般认为volatile 比锁性能好(不绝对)。选择使用volatile的条件是:语义是否满足应用。

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

保证可见性的方法
-volatile
-synchronized (unlock之前,写变量值回主存)
-final(一旦初始化完成,其他线程就可见)

-有序性
在本线程内,操作都是有序的
在线程外观察,操作都是无序的。(指令重排 或 主内存同步延时)
-指令重排
线程内串行语义
写后读 a = 1;b = a; 写一个变量之后,再读这个位置。
写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。
读后写 a = b;b = 1; 读一个变量之后,再写这个变量。
以上语句不可重排
编译器不考虑多线程间的语义
可重排: a=1;b=2;

-指令重排的基本原则
程序顺序原则:一个线程内保证语义的串行性
volatile规则:volatile变量的写,先发生于读
锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
传递性:A先于B,B先于C 那么A必然先于C
线程的start方法先于它的每一个动作
线程的所有操作先于线程的终结(Thread.join())
线程的中断(interrupt())先于被中断线程的代码
对象的构造函数执行结束先于finalize()方法

编译和解释运行的概念

-解释运行
解释执行以解释方式运行字节码
解释执行的意思是:读一句执行一句
-编译运行(JIT)
将字节码编译成机器码
直接执行机器码
运行时编译

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

一些问题

1. 写一个程序,让程序在运行之后,最终抛出由于Perm区溢出引起的OOM,给出运行的jdk版本,程序源码,运行参数,以及系统溢出后的截图、程序所依赖的jar包说明,并说明你的基本思路。

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * 想要perm抛出Oom,首先要知道oom存放什么数据: 类型的常量池, 字段、方法信息 ,方法字节码
 * 由于Java想要动态创建字段、class信息需要引用到第三方Jar包。所以这个地方我利用无限创建常量池来使得抛出perm gen oom jvm
 * 运行参数:-server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC
 */
public class PermOOM {

	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		while (true) {
			list.add(UUID.randomUUID().toString().intern());
		}
	}
}

2.你能想到有什么办法,可以让一个程序的函数调用层次变的更深。比如,你在一个递归调用中,发生了stack的溢出,你可以做哪些方面的尝试,使系统尽量不溢出?阐述你的观点和原因。

首先了解到线程在调用每个方法的时候,都会创建相应的栈,在退出方法的时候移出栈桢,并且栈是私用的,也需要占用空间,所以让一个程序的函数调用层次变的更深,减少栈帧的空间很必要。或者增大线程的栈的大小。通过volatile增加调用层次深度。线程会对一个没有volatile的变量进行临时存储,这就导致线程栈占的空间增大,如果对一个变量增加volatile修饰,可以适当增加深度。

public class OverFlowTest {
	private volatile int i = 0;
	private volatile int b = 0;
	private volatile int c = 0;

	// private int i=0;
	// private int b=0;
	// private int c=0;

	public static void main(String[] args) {
		OverFlowTest o = new OverFlowTest();
		try {
			o.deepTest();
		} catch (Throwable e) {
			System.out.println("over flow deep:" + o.i);
			e.printStackTrace();
		}
	}

	private void deepTest() {
		++i;
		++b;
		++c;
		deepTest();
	}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值