Java基础简历5

String 字符串常量:适用于少量的字符串操作的情况
StringBuffer 字符串变量(线程安全)很多方法可以带有synchronized关键字保证线程安全:适用于多线程下在字符串缓冲区进行大量操作的情况。
StringBuilder 字符串变量(非线程安全):适用于单线程下在字符缓冲区进行大量操作的情况。

==比较地址,equal比较值

Java有八大基础类型 分为三类:

整型(byte,int,short,long,float,double),字符型(char),布尔型(boolean)

Java运行时数据区:

1程序计数器:当前线程所执行的字节码的行号指示器。取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要这个计数器来完成。如果执行native方法,这个计数器值为空。没有规定任何OutOfMemoryError情况

2Java虚拟机栈:线程私有的,生命周期与线程相同。每个方法在执行的同时都会创建一个栈帧:用于存储局部变量表。操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出Stack OverflowError。如果可以动态扩展,如果动态扩展无法申请到足够的内存,就会抛出OutOfMemoryError异常

3本地方法栈:Native方法服务(一个Native Method就是一个java调用非java代码的接口。)。跟虚拟机栈发挥作用相似。

4Java堆:是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。存放对象实例。所有的对象实例以及数组都要在堆上分配。

现代收集器都采用分代收集算法,所以Java堆中还可以细分为:新生代和老年代:再细致一点的有Eden空间、From Survivor空间、To Survivor空间。通过-Xmx和-Xms控制。如果堆中没有内存完成实例分配,并且也无法再扩展时,将会抛出OutOfMemoryError异常。

5方法区:线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常

扫描二维码关注公众号,回复: 3585303 查看本文章

6运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池。用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。也会抛出OutOFMemoryError异常

对象的创建:

1)当虚拟机遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个符号引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化过,如果没有,那必须先执行相应的类加载过程。

2)在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可以完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。假设Java堆中内存是绝对规整的,用过的在一边,空闲的在另一边,中间放一个指针。挪动指针----指针碰撞法。若不是规整的用“空闲列表法”。

除了考虑如何划分可以内存外,还有一个需要考虑的问题是对象创建在虚拟机中是非常频繁的行为,即使是仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的(可能正在给A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存)。解决方案:一对分配内存空间的动作进行同步处理--采用CAS配上失败重试的方式保证原子性;另一种是把内存分配的动作按照线程划分在不同的空间之中进行。即每个线程 在Java堆中预先分配一小块内存,称为本地线程分配缓冲。

3)内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值。

4)接下来虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等。这些信息存放在对象的对象头之中。根据虚拟机当前的运行状态的不同,如是否启用偏向锁等,对象头有不同的设置方式。

5)从虚拟机角度看,一下新的对象已经产生,但从Java程序的视角,对象创建才刚刚开始--init方法还没执行。执行new指令后会接着执行init方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

标记-清除算法:

1 首先标记出所需要回收的对象

2标记完成后统一回收所有被标记的对象

不足:一个是效率问题,标记和清除两个过程的效率都不高;二是空间问题,标记清除后会产生大量碎片

复制算法:将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块,然后再把 使用过的内存空间一次清理掉。IBM公司研究表明,新生代的对象98%都是“朝生夕死”的,不需要1:1的比例来划分。而是将内存划分为一块儿较大的Eden区和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor上,最后清理掉使用过的Eden和Survivor空间。HotSpot虚拟机默认的Eden和Survivor的大小比例是:8:1.也就是每次新生代中可用内存为整个新生代容量的90%,只有10%的内存会被浪费。我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里值老年代)进行分配担保。---如果另外一块Survivor空间没有足够空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代。

复制算法在对象存活率就要进行较多的复制,效率将会变低。老年代不使用这种方法。

标记-整理:与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

分代收集算法:当代虚拟机都采用“分代收集”算法,根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法。

对象优先在Eden分配

大对象(例 字符串,数组)直接进入老年代

长期存活的对象将进入老年代

Java内存模型:

类加载过程:Java类加载分为5个过程:加载,验证,准备,解析,初始化,使用,卸载

什么时候需要对类进行初始化?
1)使用new该类实例化对象的时候;
2)读取或设置类静态字段的时候(但被final修饰的字段,在编译器时就被放入常量池的静态字段除外static final);
3)调用类静态方法的时候;
4)使用反射Class.forName(“xxxx”)对类进行反射调用的时候,该类需要初始化;
5) 初始化一个类的时候,有父类,先初始化父类(注:1. 接口除外,父接口在调用的时候才会被初始化;2.子类引用父类静态字段,只会引发父类初始化);
6) 被标明为启动类的类(即包含main()方法的类)要初始化;
7)当使用JDK1.7的动态语言支持时,如果一个java.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

JVM提供了以下3种系统的类加载器:

  • 启动类加载器(Bootstrap ClassLoader):最顶层的类加载器,负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
  • 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
  • 应用程序类加载器(Application ClassLoader):也叫做系统类加载器,可以通过getSystemClassLoader()获取,负责加载用户路径(classpath)上的类库。如果没有自定义类加载器,一般这个就是默认的类加载器。

双亲委派模式:如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。

猜你喜欢

转载自blog.csdn.net/cclliii/article/details/82952982
今日推荐