Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且,加载某个类的class文件时,java虚拟机采用的是双亲委派机制,即请求交由父类处理,它是一种任务委派模式。
(1)如果一个类加载器受到了类加载器请求,它并不会自己先加载,而是把这个请求委托给父类的加载器去执行;
(2)如果父类加载器还存在其父类 加载器,则会进一步向上委托,依次递归,请求最终将到达顶层的引导类加载器。
如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成加载任务,自家在其才会尝试自己去加载,这就是双亲委派机制。
父类加载器一层一层往下 分配任务,如果子类加载器能加载,则加载此类,如果将加载任务分配至系统类加载器也无法加载此类,则抛出异常。
双亲委派机制优势通过上面的例子,我们可以指导,双亲机制可以
- 避免类的重复加载
- 保护程序安全,防止核心API被随意篡改
- 自定义类:Java.lang.String(没用)
- 自定义类:java.lang.ShkStart(报错:组织创建java.lang开头的类)
Java安全模型的核心就是Java沙箱。
什么是沙箱沙箱是一个限制程序运行的环境,沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源的访问,通过这样的方式来保证对代码的有效隔离,防止对本地系统造成破坏。
那么系统资源包括什么?
CPU、内存、文件系统、网络、不同级别的沙箱对这些资源的访问限制也可以不一样。
在Java中将执行程序分为本地 代码和远程代码两种,本地代码默认为可信任的,而远程代码则认为是不受信的。对于授信的本地代码,可以访问一切本地资源;而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱机制。
JDK1.0但是如此严格的安全机制也给程序的 功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时,就无法实现。因此在后续的Java1.1版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。
在Java1.2版本中,再次改进了安全机制,增加了代码签名,不论本地代码还是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。
当前最新的安全机制实现,则引入了域(domain)的概念,虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域(Protected Domain)对应不一样的权限(Permission)。存在于不同域中的类文件就具有了当前域的全部权限。
沙箱的基本组件-
字节码校验器(bytecode verifier):确保java类文件遵循java语言规范,这样可以帮助java程序实现内存保护,但并不是所有的类文件都会经过字节码校验,比如核心类。
-
类装载器(class loader):其中类装载器在3个方面对java沙箱起作用
- 它防止恶意代码去干涉善意的代码; //双亲委派机制
- 它守护了被信任的类库边界;
- 它将代码归入保护域,确定了代码可以进行哪些 *** 作。
类装载器采用的机制是双亲委派模式。
- 从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
- 由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效;
- 存取控制器(access controller):存取控制器可以控制核心API对 *** 作系统的存取权限,而这个控制的策略设定,可以由用户指定。
- 安全管理器(security manager):是核心API和 *** 作系统之间的主要接口,实现权限控制,比存取控制器优先级高。
- 安全软件包(security package):java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:
- 安全提供者
- 消息摘要
- 数字签名
- 加密
- 鉴别
native关键字:凡是带了native关键字的,说明Java的作用范围达不到了,回去调用底层C语言的库。凡是带了native的就会进入本地方法栈,会调用本地方法接口(native method interface),即JNI。本地方法的作用就是调用本地方法库,为了扩展Java的使用,融合不同的编程语言为java所用,最初是C、C++;
Java诞生的时候,C、C++横行,要想立足,必须要有调用C、C++的程序。它在内存区域中专门开辟了一块标记区域:native method stack,用来登记native方法,在最终执行的时候,去加载本地方法库中的方法通过JNI。
例如:Java程序驱动打印机,管理系统,掌握即可,但是企业级应用较少。
调用其他接口:采取Socket,webService,Http。
PC寄存器程序计数器:Program Counter Register
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。
方法区Method Area方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存储在堆内存中,和方法无关(static,final,class,常量池)。
栈栈是一种数据结构
程序=数据结构+算法
程序=框架+业务逻辑
栈:先进后出(桶)
队列:先进先出 (FIFO)
为什么Main()方法先执行,后结束
栈:栈内存,主管程序的运行,生命周期和线程同步;线程结束,栈内存也就被释放。对于栈来说,不存在垃圾回收问题。一旦线程结束 ,栈就over了
栈:8大基本类型+对象引用+实例方法
栈运行原理:以栈帧
程序正在执行的方法一定在栈的顶部。
栈满了就会抛出StackOverError
堆Heap,一个JVM只有一个堆内存,而且堆内存的大小是可以调节的。
类加载器读取类文件后会把实例对象放到堆中(类,方法,常量,变量),保存我们所有引用类型的真实对象。
堆内存中划分为三个区域:新生代、老年代、永久代;新生区包括Eden区和幸存区;
GC垃圾回收:主要就在伊甸园区和老年代区
假设内存满了,OOM,堆内存不够!
在JDK1.8以后,永久区改为元空间;
新生区和老年区- 类诞生和成长的地方,甚至死亡;
- 分为伊甸园区和幸存者区(0区和1区),所有的对象都在Eden区 new出来的
- 伊甸园区满了就会触发一次轻GC,幸存区能够留下来的就进入养老区。
真理:经过研究,99%的对象都是临时对象!new
永久区这个区域常驻内存的,用来存放jdk自身携带的Class对象,interface元数据,存储的是java运行时的一些环境;这个区域不存在垃圾回收,关闭虚拟机就会释放这个区域的内存。
- jdk1.6之前:永久代,常量池是在方法区中
- jdk1.7:永久代,但是慢慢的退化了,去永久代,常量池在堆中;
- jdk1.8之后:没有永久代,叫做元空间,常量池在元空间。
元空间:逻辑上存在,物理上不存在;
在一个项目中,如果出现了OOM,那么该如何排除?
研究为什么出错?
- 能够看到代码第几行出错,内存快照分析工具,MAT、jprofiler
- Debug,一行行分析代码
MAT、jprofiler作用:
- 分析Dump内存文件,快速定位内存泄露;
- 获得堆中的数据
- 获得大的对象
- Sun公司 HotSpot Java HotSpot™ 64-Bit Server VM (build 25.0-b70, mixed mode)
- BEA JRockit
- IBM J9VM
GC分为两种类型:轻GC(普通GC)和重GC(全局GC)
GC题目:
- JVM的内存模型和分区—详细到每个区放什么?
- 堆里面的分区有哪些?Eden,from,to,老年区,说说它们的特点
- GC的算法有哪些?标记清除法,标记整理(压缩),复制算法,引用计数器,怎么用的
- 轻GC和重GC分别在什么时候发生?
递归遍历整个堆空间,找出无效对象进行清除,这个算法有两大缺点
- 遍历整个空间效率低下,时间长;
- GC过后会产生很多内存碎片。
将申请的内存空间分成两块,每次只用一块A,GC的时候找到活的对象复制到另外一块内存B中,清除内存A的空间。
- 解决了内存碎片的问题;
- 算法依然低效;
- 引出新的问题,内存利用率低下(每次只能用一半)
第一步遍历出存活的对象,第二步将标记好的对象复制到内存的一端,回收以外的空间。
- 解决了碎片问题;
- 解决了内存利用率低的问题;
- GC效率依然低下;
该算法不是 什么新算法而是根据场景,灵活运用以上三种算法而产生的算法。
- 少量对象存活:复制算法,只需要复制少量的对象就能完成GC;
- 大量对象存活:标记清除或者标记整理,对象存活率高,复制算法过于浪费内存空间,所以使用其他两种算法。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)