2、类加载器有三种JVM:
Suan公司的HotSpotBEA的JRockitIMB的J9VM
类(引用类型)在栈里面,对象在堆里面,栈是引用地址。常用类lang包在rt.jar中,扩展的在jre>lib>ext1、类加载器流程
装载:查找和导入Class文件。
链接:
检查:检查载入的Class文件数据的正确性。包括了四种验证:
文件格式验证:是否以OxCAFEBABE开头,版本号是否合理。元数据验证:是否有弗雷,继承了final类?非抽象类实现了所有的抽象方法。字节码验证:运行检查,栈数据类型和操作码参数吻合,跳转指令到合理的位置。符号引用验证:常量池中描述类是否存在,访问的方法或字段是否存在有足够的权限。 准备:给类的静态变量分配存储空间。解析:将符号引用转成直接引用。初始化:对静态变量,静态代码块执行初始化工作。
2、加载器类型 根加载器(Bootstrap)扩展类加载器(ExtClassLoader)应用加载器(系统加载器)(AppClassLoader) 3、双亲委派机制
加载类顺序:应用加载器>扩展类加载器> 根加载器,从这个顺序开始加载类,以后加载的类为准,例:自定义了一个String类,这个自定义的String类是位于应用加载器中,而根加载器也有一个自带的String方法类,所以最后会执行根加载器的String,执行不到自定义的String。
3、沙箱安全机制Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制系统资源访问,那系统资源包括什么?CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
所有的Java程序运行都可以指定沙箱,可以定制安全策略。
在Java中将执行程序分别本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信任的,对于授权的本地代码,可以访问一切本地资源。而对于非授权的远程代码在早期的Java实现中,安全依赖于沙箱(Sandbox)机制。如下图JDK1.0安全模型。
但如此严格的安全机制也给程序的功能扩展带来了障碍,比如当用户希望远程代码访问本地系统的文件袋时候,就无法实现。因此在后续的Java1.1版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。如下图所示JDK1.1安全模型。
在Java1.2版本中,再次改进了安全机制,增加了代码签名。无论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。如所示的JDK1.2安全模型。
当前最新的安全机制实现,则引入了域(Domain)的概念,虚拟机会把所有代码加载到不同系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不用的受保护域(Protected Domain),对应不一样的权限(Permission)。存在与不用域中的类文件就具有了当前域的全部权限,如下图所示的最新的安全模型(JDK1.6)。
它防止恶意代码取干涉善意代码(双亲委派机制)。它守护了被信任的类库边界。它将代码归入了保护域,确定了代码可以进行哪些操作。
类装载器采用的机制是双亲委派模式。
从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
存取控制器(access controller):存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。
安全管理器(security manager):是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
安全软件包
(security package):java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:
安全提供者消息摘要数字签名加密鉴别 4、Native(本地方法栈) 凡是带native关键字的,说明java的作用范围达不到,回去调用底层C语言的库。带native的会进入本地方法栈。然后本地方法栈再到本地方法接口,JNIJNI作用:扩展Java的使用,融合不同的编程语言为Java所用它在内存区域中转盟开辟了一块标记区域:Native Method Stack,登记native方法。在最终执行的时候,加载本地方法库中的方法通过JNI。 5、程序计数器(Program Counter Register)
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向一条指令的地址,也就是将要执行的指令代码——,在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。
6、方法区(Method Area) 方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。重要!:静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关。 7、栈栈内存主管程序的运行,生命周期和线程同步。线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收问题,一旦线程结束,栈就结束。栈存储的:8大基本类型、对象引用、实例的方法栈的运行原理:栈帧,一个帧一个帧的压栈mian方法是最先入栈,在栈底,main()先执行,最后结束
1、栈、堆、方法区关系 引用的放在栈里面,具体的实例放在堆里面,方法区放方法(.class),方法区里面还有一些常量池等。
8、在内存中对象的实例化过程 当使用关键字new的时候会在内存中开辟一个空间存放对象,这个对象指向堆内存的一个空间。执行顺序:静态变量>静态代码块>变量>代码块>构造器
Java 中的数据类型有两种:
1、基本类型(primitive types): 共有8种,即:int、short、long、byte、char、float、double、boolean(注意并没有 String 的基本类型),这8中类型的定义是通过诸如:int a = 5;long b = 22L;的形式来定义的,称为自动变量。注意:自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在;
如:int a = 5; 这里的 a 是一个指向 int 类型的引用,指向 5 这个字面值,这些字面值的数据由于大小可知,生存期可知( 这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了 ),出于追求速度的原因,这些字面值就存在于栈区中;
另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义
int a = 3;
int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。
特别注意的是:这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与 b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。
————————————————
版权声明:本文为CSDN博主「车万白野兔」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_36360543/article/details/114234491
一个JVM只有一个堆内存,一个线程有一个栈,堆内存的大小是可以调节的,堆内存满了会报OOM(OutOfMemoryError)错误。
类加载器读取文件后,一般会把类,方法,常量,变量放到堆中,保存我们所有引用类型的真实对象。
堆内存细分为三个区域:
新生区养老区永久区(JDK8以后改为元空间)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RPFQpLaf-1644214895657)(堆内存-16441336551354.png)]
1、新生区(新生代)、老年区 大部分对象的创建和回收都在伊甸园区。先伊甸园区如果满了,则促发轻GC进行垃圾回收,这个时候可能有的数据还存在引用,没有被轻GC回收掉,则会进入幸存区。当幸存区经过十五次GC还未被清除掉的时候就会进入老年期。当新生区满了,老年区也满了则会触发重GC(Full GC)。 2、永久区(永久代)JDK1.6之前:叫做永久代,常量池是在方法区。JDK1.7:叫做永久代,但是慢慢退化了,出现了一个方向叫做去永久代,这个时候常量池在堆中。JDK1.8之后:无永久代,改成元空间,这个时候常量池在元空间这个区域常驻内存。用来存放JDK自身携带的Class对象,Interface元数据,存储的是Java运行时的一些环境信息,这个区域不存在垃圾回收!关闭VM虚拟机就会释放这个区域的内存。
3、堆内存调优
堆内存有最大容量,总容量。当全满的时候轻GC和重GC都回收不了的时候则会栈满报OOM错误。
手动改变堆内存大小(内存不够问题)。
VM option:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
减少代码对死循环或者无效引用(代码问题)。
4、定位分析排错堆内存满OOM的代码 1、安装内存快照工具idea安装插件JProfiler电脑下载软件JProfiler配置idea里面的JPofiler文件路径:File>Settings>Tools>JProfiler>JProfiler executable(这个文件在JProfilter安装目录下的bin>JProfiler.exe) 2、使用Dump 在VM options中输入命令:-XX:+HeapDumpOnOutOfMemoryError运行该类,运行该类后就会在该程序的目录下生成一个java_pidxxxx.hprof的文件。用JProfiler打开该文件,就可以分析了。 3、使用JProfilter进行分析dump的java_pidXXX.hprof文件 查看Classes使用情况查看Biggest Object查看Thread Dump,可以查看具体在哪一行 GC(垃圾回收机制)使用内存快照(dump)工具:MAT(Excelipse)、JProfilter(idea)
1、引用计数器 是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。使用引用计数技术可以实现自动资源管理的目的。 2、复制算法有标记清除法、标记压缩法、引用计数器、复制算法。
当轻GC清除伊甸区的时候,伊甸活着的会到幸存区to,然后之前幸存区from活着的也会到幸存区to,然后幸存区to变成幸存区from,幸存区from变成幸存区to。好处:没有内存的碎片。坏处:浪费了内存空间。使用场景:对象存活度较低的时候,比如新生区。因为如果都存存活的话,每次复制移动就会消耗很多资源。 3、标记清除算法谁是空的,谁是幸存区to
遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。
再次遍历堆中所有对象,将没有标记的对象全部清除掉。
优点:不需要额外的空间,时间换空间。
两次扫描,严重浪费时间,会产生内存碎片。
4、标记压缩算法 遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。再次遍历堆中所有对象,将存活的对象移动到前面。优点:不会产生碎片,方便对象分配,内存不会减半。缺点:两次扫描浪费时间,效率低。5、总结 内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)内存整齐度:复制算法=标记压缩算法>标记清除算法内存利用率:标记压缩算法=标记清除算法>复制算法 4、标记压缩算法 遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。再次遍历堆中所有对象,将存活的对象移动到前面。优点:不会产生碎片,方便对象分配,内存不会减半。缺点:两次扫描浪费时间,效率低。 5、总结 内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)内存整齐度:复制算法=标记压缩算法>标记清除算法内存利用率:标记压缩算法=标记清除算法>复制算法