1.java自动管理堆(heap)和(栈),程序员不能直接的设置堆和栈。
2.操作系统的堆和栈:
堆(操作系统):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表。
栈(操作系统):由操作系统自动分配释放,存放函数的参数值,局部变量值等。操作方式与数据结构中的栈相类似。
1. 2.为什么jvm的内存是分布在操作系统的堆中呢??
因为操作系统的栈是操作系统管理的,它随时会被回收,所以如果jvm放在栈中,那java的一个null对象就很难确定会被谁回收了,那gc的存在就一点意义都莫有了,而要对栈做到自动释放也是jvm需要考虑的,所以放在堆中就最合适不过了。
上图表明:jvm虚拟机位于操作系统的堆中,并且,程序员写好的类加载到虚拟机执行的过程是:当一个classLoder启动的时候,classLoader的生存地点在jvm中的堆,然后它会去主机硬盘上将A.class装载到jvm的方法区,方法区中的这个字节文件会被虚拟机拿来new A字节码(),然后在堆内存生成了一个A字节码的对象,然后A字节码这个内存文件有两个引用一个指向A的class对象,一个指向加载自己的classLoader
5,java虚拟机的生命周期:声明周期起点是当一个java应用main函数启动时虚拟机也同时被启动,而只有当在虚拟机实例中的所有非守护进程都结束时,java虚拟机实例才结束生命。
6,java虚拟机与main方法的关系:main函数就是一个java应用的入口,main函数被执行时,java虚拟机就启动了。启动了几个main函数就启动了几个java应用,同时也启动了几个java的虚拟机。
7,java的虚拟机种有两种线程,一种叫叫守护线程,一种叫非守护线程(也叫普通线程),main函数就是个非守护线程,虚拟机的gc就是一个守护线程。java的虚拟机中,只要有任何非守护线程还没有结束,java虚拟机的实例都不会退出,所以即使main函数这个非守护线程退出,但是由于在main函数中启动的匿名线程也是非守护线程,它还没有结束,所以jvm没办法退出.
8,虚拟机的gc(垃圾回收机制)就是一个典型的守护线程。
9,实例理解“当所有的非守护线程全部结束,jvm声明周期才结束”:
public class MianAndThread{
public static void main( String args[]){
new Thread(new Runnable(){
@override
public void run(){
Thread.currendThread.sleep(5000s);
System.out.println("睡了5s后打印,这是出main之外的非守护线程,这个推出后这个引用结束,jvm声明周期结束。任务管理的java/javaw.exe进程结束"
}
}
System.out.println("mian线程直接打印,mian线程结束,电脑任务管理器的java/javaw.exe进程并没有结束。")
}
}
10,GC垃圾回收机制不是创建的变量为空是就被立刻回收,而是超出变量的作用域后就被自动回收。
11,程序在jvm原先的流程:
首先,当一个程序启动之前,它的class会被类装载器装入方法区 ,执行引擎读取方法区的字节码自适应解析,边解析就边运行(其中一种方式),然后pc寄存器指向了main函数所在位置,虚拟机开始为main函数在java栈中预留一个栈帧(每个方法都对应一个栈帧),然后开始跑main函数,main函数里的代码被执行引擎映射成本地操作系统里相应的实现,然后调用本地方法接口,本地方法运行的时候,操纵系统会为本地方法分配本地方法栈,用来储存一些临时变量,然后运行本地方法,调用操作系统APIi等等。
12,根据Java虚拟机规范的规定,如果方法区的内存空间不能满足内存分配需要时,将抛出OutOfMemoryError异常。
13,双亲委派机制:JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
例如:当jvm要加载Test.class的时候,
(1)首先会到自定义加载器中查找(其实是看运行时数据区的方法区有没有加载),看是否已经加载过,如果已经加载过,则返回字节码。
(2)如果自定义加载器没有加载过,则询问上一层加载器(即AppClassLoader)是否已经加载过Test.class。
(3)如果没有加载过,则询问上一层加载器(ExtClassLoader)是否已经加载过。
(4)如果没有加载过,则继续询问上一层加载(BoopStrap ClassLoader)是否已经加载过。
(5)如果BoopStrap ClassLoader依然没有加载过,则到自己指定类加载路径下("sun.boot.class.path")查看是否有Test.class字节码,有则返回,没有通
知下一层加载器ExtClassLoader到自己指定的类加载路径下(java.ext.dirs)查看。
(6)依次类推,最后到自定义类加载器指定的路径还没有找到Test.class字节码,则抛出异常ClassNotFoundException。
为什么要使用这种加载方式呢?
1,类加载器代码本身也是java类,因此类加载器本身也是要被加载的,因此显然必须有第一个类加载器不是Java类,这就是bootStrap,是使用c++写的其他这是java了。
2,虽说bootStrap、extclassLoader、appclassloader三个是父子类加载器关系,但是并没有使用继承,而是使用了组合关系。
3,优点,具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,可以比较笼统的说像jdk自带的几个jar包肯定是位于最顶级的,再就是我们引用的包,最后是我们自己写的,保证了java程序的稳定性。
20,JVM运行简易过程:
上图左半部分其实不是在JVM中,程序员在eclipse上写的是.java文件,经过编译成.class文件(比如maven工程需要maven install,打成jar报,jar包里面都是.calss文件);这些步骤都是在eclipse上进行的。然后类加载器(classloader)一直到解释器是属于JVM的
二 从内存的角度看Jvm
1 声明一个普通类person,类里面没有任何参数。
可以看到person对象有8个字节。
2 如何我们把Person类定义成内部类呢
这里就多了4个字节,这4个字节就是该对象持有外部类的引用。
3 如果我们把对象的声明写成静态的会发生什么?
person引用是静态的,所以这个person就属于类的一部分,而这个引用在有持有外部类的引用,如果说这段代码是在activity中写的,那么就相当于持有activity的引用,但是类的生命周期是整个jvm的,这会导致person的生命周期很长但是却持有了activity这种短生命周期的引用。就会导致内存泄漏。如何去解决呢?
4 将类的改为静态内部类。
从内存看引用的内存又恢复了8个字节,不会去持有外部类的引用了。
三 android虚拟机底层跟踪findclass
1 jni调用findclass开始
2 一直跟踪到具体实现到了class_linker.cc中
3 细节跟踪
// Find the class in the loaded classes table. mirror::Class* klass = LookupClass(descriptor, class_loader);
从谷歌工程师上的注解看到,这一步是从class类表中找到指定class
lookupclass()
从已加载的dex缓存中读取
defineClass()如何没有从lookupClass 方法缓存中读取到class信息就开始完整的去从磁盘中读取
第一步:引出了一个类的“模型”,就是kclass
第二步 模型容器准备好了就需要在里面装“内容”了,调用LoadClass去装载
将磁盘中dex文件中读出的内容装到klass里面。
将各种参数和方法读到对应集合中。
静态变量,对象变量,基本变量撞到field集合
对应的不同类型方法装到方法集合。ArtMethod(方法其实就是一堆art指定的集合)
findclass 调用流程图