上面画住的是线程私有的,每启动一个线程,它都会在我们jvm中拿出这三个来,被我们这个线程私有,当我们启动一个线程的时候,我们会为这个线程分配三个区域,java栈,本地方法栈,程序计数器
java虚拟机栈
1.每个线程运行时所需要的内存,称为虚拟机栈
2.每个栈由多个栈帧组成,
3.每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
下面就是反编译后的文件
Compiled from "TestJvaa.java"
public class com.luban.test.TestJvaa {
public com.luban.test.TestJvaa();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int testMath();
Code:
0: iconst_4 //将整形常量4放入操作数栈中
1: istore_1 //将操作数栈顶的类型值存到局部变量表的第n个槽中
2: iconst_2
3: istore_2
4: iload_1 //将局部变量表中的第一个槽,压入操作数栈顶(这里是复制,这里的复制其实是为了局部变量在这个栈振中能够共享)
5: iload_2 //将局部变量表中的第二个槽,压入到操作数栈顶
bipush:讲一个8位带符号整数压入栈 sipush:将一个16位带符号整数压入栈
6: iadd //每执行一个idd它都会弹出栈顶的两个操作数,然后进行相加,再把相加的结果再重新的压入到这个操作数栈中
7: iconst_4、
8: imul //把栈顶的两个数出栈然后相乘,得出的结果再入栈
9: istore_3
10: iload_3
11: ireturn 记录到出口记录,然后把结果返回到了main函数当中去了
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/luban/test/TestJvaa
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method testMath:()I
12: pop
13: return
}
程序计数器的作用就是记录jvm指令执行到哪条了,一位cpu不可能一直为你自己工作,它还要为其它线程工作,等它切回来再为你工作的时候,你得记录到你执行到哪条jvm指令了?
public static void main(String[] args) {
String s1 ="luban";
String s ="abc"+"bcd"; //这行代码会产生一个对象为什么呢,下面看下反编译后的字节码
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String luban
2: astore_1
3: ldc #3 // String abcbcd
5: astore_2
6: return
}一看就清楚了是几个对象了 目前高版本jvm会对这个字面值进行优化
String和StringBuilder的区别
public static void main(String[] args) {
String s1 ="luban";
String s2 ="abc"+"bcd";
s1=s2+"aaaaaa"; //加了这行代码之后再去反编译
}
}
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String luban
2: astore_1
3: ldc #3 // String abcbcd
5: astore_2
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
13: aload_2
14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: ldc #7 // String aaaaaa
19: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
25: astore_1
26: return
}
说白了String和StringBuilder在使用级别上是没什么区别,底层把它优化成了StringBuilde
public class TestHeadOverFlow {
public static void main(String[] args) throws InterruptedException {
List<byte[]> list = new ArrayList<byte[]>();
for(;;){
list.add(new byte[1024*1024*500]);
Thread.sleep(200);
}
} 这段代码可以导致heap溢出
public class TestHeadOverFlow {
public static void main(String[] args) throws InterruptedException {
for(;;){
byte[] b = new byte[1024*1024*500];
}
}
} 而这段代码却不能导致堆溢出, 为什么呢? 我们new出来的这个数组 b是它的引用 但是for循环下次再来的胡又重新给它赋值了
之前得那个数组就变成不可达了,所以就具备回收的条件了,jvm可能随时把它回收掉,那如果我把对象放到集合里面去,那不一样,集合里面是持有引用的,我以后可以随时通过集合找到你,所以不具备回收的条件,所以会存在heap溢出
-Xms 初始java堆大小 初始值是物理内存的 1/64
-Xmx 最大java堆大小 -X选项是非标准选项,如有改动,数不另行通知
-Xss 为jvm启动时候每个线程分配的栈stack内存大小,默认是1M ,栈并不是分配的越大越好,如果栈分配的越大,会导致线程数减少
public class TestStackOverFlow {
@Test
public void stackOverFlow(){
print(0);
}
public void print(int n){
System.out.println(n);
n++;
print(n);
}
}
-Xmn 年轻代 如果调大的话那个地方就长了
java直接访问离堆(0ff-heap)即jvm之外的内存,用的是NIO
我们的通过编译生成的.class文件,知道你的类名以后,通过classLoader首先把类加载到方法区里面,首先我們要知道,lei类加载它并不是实例化的过程,类加载时把类放到jvm的过程,而不是创建对象的过程,创建对象的过程涉及到栈区里面方法执行的过程,这是方法在执行过程用到那个类就去加载哪个类,
很多人不知道这个类是怎么被加载的即我圈的CC那个类,
当你运行java程序的时候,你在dos里运行时肯定要输入java com.changchang.cn.CC 肯定会指定这个类名的,类加载器首先就会把这个CC类加载进method area 里面,但是你要记得 在启动虚拟机的时候就把java核心类库(rt.jar)全部都加载进来了,
在执行main的时候,碰到没有被加载的类,就看下方法区有没有,如果没有就加载到方法区
类加载器的分类:Bootstrap类加载器,ExtClassLoader,AppClassLoader 每种加载器都设定好从哪里加载类
BootstrapClassLoader--------->JRE/lib/rt.jar
ExtClassLoader-------->JRE/lib/ext或者
AppClassLoader ------>CLASSPATH环境变量,由classpath或-cp选项定义
类加载器的工作原理有1.委托机制,加入你要加载Person.class这个类,首先加载这个类的请求由AppClassLoader 委托给它的父类ExtClassLoader,然后再委托给BootstrapClassLoader,BootstrapClassLoader先看看rt.jar里面有没有这个类,如果没有,就返回给ExtClassLoader 查看jre/lib/ext目录下有没有这个类,如果还没有就由AppClassLoader 从classpath中寻找, 记住
classpath定义的是类文件的加载目录,而PATH定义的是可执行程序如javac java等的执行路径
2.可见性机制,子类加载器可以看到父类加载器加载的类,而反之则不行,如果ABC.class已经被AppClassLoader 加载过了,再用
ExtClassLoader的话就会抛出java,lang.ClassNotFountException
3.单一性:父类加载器加载过的类不能被子类加载器加载第二次
由于方法区里面存放的是类类型(Class),运行时加载类是你用到哪个类先去method area区找如果没有,你就把他加载进来存进方法区,你想呀 我们从main方法进去的时候肯定会遇见没有加载过的类,jam发现这个类没有被加载,就开始查找某某类.class文件,从类文件中抽取类型信息并放在了方法区 ,这个类一旦被加载,它就常驻内存里面了,
方法区溢出是加载类太多导致的,堆溢出是加载对象太多导致的 栈溢出是方法太多导致的,如方法的死递归
类型信息:
对于每个加载的类型,jvm必须在方法区存储以下类型信息:
方法区是线程共享的,当两个线程同时要加载一个类型的时候,只有一个类会请求Classloader加载,另一个线程会等待。
1:这个类型的完整有效名
2.这个类型直接父类的完整有效名(除非这个类是interface或者是java.lang.Object,这两种情况都没有父类)
3.这个类型的修饰符(public,abstract,final)
4.即时编译后的代码等信息
除了这些基本信息外,jvm还要为每个类型保存一下信息:
类型的常量池:
field信息
方法信息
所有static变量 ,在这里提一点就是可以把全局变量放在静态代码块里面,
在这之前先讲一下Class这个类的概念
class Person{
}这里有个Person类,这个类的实例对象如何表示呢?下面这句话就是了
Person p = new Person(); p就是Person这个类的实例对象,
class Student{
}
Student student = new Student(); student就是Student这个类的实例对象,
由于java里面万事万物皆对象 这个Person,Student类 也是一个实例对象,它是大写Class类的实例对象 ,也就是说我们平常所见到的类都是Class的实例对象 那么改如何表示呢?有三种表示方法:我们就举上面的Person类吧
1:Class c1 = Person.class(即类名.class)
2: Class c2 = p.getClass(即对象.getClass) 这里的p是 大写Person类的实例对象
写到这里可能你们会有些混淆:我再重申一下 上面的 p是Person类的实例对象,c1 c2代表 Class类的实例对象,
但是这个c1实例对象说的是什么玩意呢,说的是Person这个类本身就是一个实例对象
官网给出的准确说法是: c1 c2是 Person类的类类型(class type) ,类的类类型怎么理解呢,你就这样理解,Person类本身就是Class类的一个实例对象 ,你要清楚一点Person这个类本身也有实例对象,就是p, 我们以后就可以这么叫了 p是Person类的实例对象,c1是Person类的类类型
3:c3=Class.forName("类的路径"); 这里c1==c2==c3的
我们完全可以通过类的类类型创建该类的实例--->通过c1,c2,c3创建Person类的实例对象
如果是Person类的类类型,创建出来的是Person对象,如果是Student类的类类型,创建出来的是Student对象,所以要进行强转
即:Person person=(Person)c1.newInstance(); 这里newInstance前提是要有要有无参构造的
上面讲到的Class.forName(); 不仅表示了类的类类型,还代表了动态加载类,大家要区分编译和运行,
编译时刻加载类是静态加载类,运行时刻加载类是动态加载类,这里说下静态加载类
我们在dos里面输入 javac Office.java 来进行编译,会报错,因为缺少Word类跟Excel类
我们如果建一个Word类,然后 进行编译后,再去编译javac.Office.java就会报只缺少Excel这个类的错误了,在这里我们已经建好了Word类,想用却因为没有Excel类的原因用不了,这显然不是我们想要的,我们要的是,我建一个Word类我就能用,大家想想,如果程序里有100个类,有一个用不了其余的99都用不了的话你将会有多么的崩溃呀,这就是静态加载类的弊端,new创建对象是静态加载类,在编译的时候就需要加载所有可能使用到的类,不管你用不用,
通过动态加载类我们可以解决此问题,Class.forName,就是动态加载类,就不会在编译时候报错了,它只会在你用到这个类的时候才会报没有类的错误,
java虚拟机栈就是java方法执行的内存模型,每调用一个方法,就会生成一个栈帧 用于存储方法的本地变量表,方法出口等信息
每次方法的调用都会对应栈帧的压栈跟出栈,如果请求的栈的深度过大,就会抛出stackOverflowError
热部署其实是也可以通过java的热加载来实现的
上面这个图的准备阶段做一下描述,假如说private static int num=80; 在这个准备阶段,它并不是把这个num初始化成为80 ,而是赋初始值,int类型的初始值为所以准备阶段是num的值是0,初始化阶段num的值才为80
什么时候初始化呢,5个初始化的时机,
需要先触发类初始化(加载-验证-准备自然需要在此之前):
1)1.使用 new 关键字实例化对象 2.读取或设置一个类的静态字段 3.调用一个类的静态方法。
2)使用 java.lang.reflect 包的方法对类进行反射调用时,若类没有进行初始化,则需要触发其初始化
3)当初始化一个类时,若发现其父类还没有进行初始化,则要先触发其父类的初始化。
4)当虚拟机启动时,用户需要制定一个要执行的主类(有 main 方法的那个类),虚拟机会先初始化这个类。
final 修饰的类会在编译期的时候把结果放在常量池,即使调用也不会触发初始化 ,final修饰的是个常量,它会把常量放在常量池中,调用常量它不会出发初始化的这个阶段