Java语法(五)JVM 总结

1、____________________________________________________________

1,JVM底层结构

  1,JVM指令系统  

      Java指令也是由 操作码和操作数两部分组成。

      操作码为8位二进制数,使得JVM最多有256种指令,目前已使用了160多种操作码。 

  2,JVM寄存器 

      pc程序计数器//用于记录程序的执行

      optop操作数栈顶指针//下面3个都是用于记录指向Java栈区的指针

      frame当前执行环境指针 

      vars指向当前执行环境中第一个局部变量的指针 

  3,JVM栈结构 

      JVM为每一个方法创建一个栈框架,以保存该方法的状态信息。每个栈框架包括以下三类信息: 

      局部变量//vars寄存器指向该变量表中的第一个局部变量。

      执行环境//保存解释器对Java字节码进行解释过程中所需的信息

      操作数栈//存储运算所需操作数及运算的结果

  4,JVM碎片回收堆 

      Java类的实例所需的存储空间是在堆上分配的。解释器具体承担为类实例分配,回收空间的工作

  5,JVM存储区:常量缓冲池、方法区、堆、Java栈、程序计数器和本地方法栈

      常量缓冲池用于存储类名称、方法和字段名称以及串常量。

      方法区则用于存储Java方法的字节码。

      堆保存的是用new创建的对象和数组

      栈中主要保存的是基本类型的变量和对象的引用变量,

      具体实现方式在JVM规格中没有明确规定。

      这使得Java应用程序的存储布局必须在运行过程中确定,依赖于具体平台的实现方式。 

      JVM还存在缺陷,但JVM的思想是成功的。

2,JVM内存

  1,JVMTI--JVM Tool Interface,但JVMTI提供的能监听的event总觉得还太少

  2,代码验证:

      Runtime r=Runtime.getRuntime();

      r.maxMemory();//默认64M

      r.freeMemory();

      r.totalMemory();//默认-按需从OS动态获取

  3,参数说明

      java -Xmx3550m -Xms3550m -Xmn2g -Xss128k

      - Xmx3550m :设置JVM 最大可用内存为3550M。XMS(Extended Memory Specification)说明书-规格。扩展内存。默认64M

      -Xms3550m :设置JVM 初始内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM 重新分配内存。

      -Xmn2g :设置年代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小 。持久代一般固定大小为64m,持久代Permanent Generation space

      -Xss128k :设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

  4,参数-命令行设定  

      java -cp ClassPath -Xmx512m ClassName,//-cp等效于-classpath

  5,内存溢出类型 

      1 、 java.lang.OutOfMemoryError: PermGen space 

          存放Class和Meta信息,很多Class或第三方jar。大小超过了jvm默认的大小(4M)报错

          GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,

          它

      2、tomcat下的应用

          1,设置内存

          $TOMCAT_HOME$/CATALINA.BAT第一行,增加

          set JAVA_OPTS=%JAVA_OPTS% -Xms512m -Xmx900m -Duser.timezone=GMT+08

          2 、 java.lang.OutOfMemoryError: Java heap space 

      3,手动设置MaxPermSize大小 

          修改TOMCAT_HOME/bin/catalina.bat(Linux下为catalina.sh),在Java代码 

          “echo "Using CATALINA_BASE: $CATALINA_BASE"”上面加入以下行:    

          set JAVA_OPTS=%JAVA_OPTS% -server -XX:PermSize=128M -XX:MaxPermSize=512m   

        6,Sun JVM分代垃圾回收器把堆内存分成3块:采用分代的策略。

      1)年轻代(Young Gen):年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个Eden(伊甸园) Space和2个Suvivor(幸存者) Space(命名为A和B)

当对象在堆创建时,将进入年轻代的Eden Space。

垃圾回收器进行垃圾回收时,扫描Eden Space和A Suvivor Space,如果对象仍然存活,则复制到B Suvivor Space,如果B Suvivor Space已经满,则复制到 Old Gen

扫描A Suvivor Space时,如果对象已经经过了几次的扫描仍然存活,JVM认为其为一个Old对象,则将其移到Old Gen。

扫描完毕后,JVM将Eden Space和A Suvivor Space清空,然后交换A和B的角色(即下次垃圾回收时会扫描Eden Space和BSuvivor Space。

Young Gen垃圾回收时,采用将存活对象复制到到空的Suvivor Space的方式来确保不存在内存碎片,采用空间换时间的方式来加速内存垃圾回收。

      2)年老代(Tenured Gen):年老代主要存放JVM认为比较old的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁(譬如可能几个小时一次)。年老代主要采用压缩的方式来避免内存碎片(将存活对象移动到内存片的一边),当然,有些垃圾回收器(譬如CMS垃圾回收器)出于效率的原因,可能会不进行压缩。

      3)持久代(Perm Gen):持久代主要存放类定义、字节码和常量等很少会变更的信息,关于这块的垃圾回收策略可以参考我的另一篇BLOG《Tomcat Context reloadabled 与 OutOfMemory(PermSpace) 》。

3,JVM加载类

  1,类装载器

      装载类时,总是先让自己的(递归)父类装载器装载

      Test.class.getClassLoader().getClass()

      sun.misc.Launcher$AppClassLoader

      Test.class.getClassLoader().getParent().getClass()

      sun.misc.Launcher$ExtClassLoader

      ExtClassLoader.getParent()为null

      BootstrapLoader是用C++语言写的,不存在java类实体

  2,JVM按以下顺序搜索并装载所有需要的类: 

      1, 系统/平台类: jdk1.6.0_02\jre\lib\rt.jar。

      2, 扩展类:$JAVA_HOME\jre\lib\ext\*.jar

      3, 用户类: 开发者定义的类,没有使用 java 扩展机制的第三方jar. 

  3,定位JRE: 

      1. 自己的目录下有没有JRE目录 

      2. 父目录下的JRE子目录 

      3. 查询环境变量$JAVA_HOME目录

  4,定位到类

      “系统类”,“扩展类”,前两种的路径由JRE指定;

      “用户类”的查找顺序

          1. -classpath(命令行模式下使用); 

          2. classpath(在环境变量中设计,当有 –classpath时该路径信息不起作用) 

          3. 缺省路径”.” 当前目录(当前两步都没有时,才会使用缺省路径) 

  5,类如何被装载-“动态装载”-为了节省内存,但牺牲了相应速度。

      隐式的类装载:A b = new A();最常见

      显式的类装载Class.forName("test.A");

      类的声明,并不去装载。这一点和编译类是不相同的。

  6,类装载器的隔离性

      两个位于不同分支的类装载器具有隔离性,它们装载同一个类,也会在内存中出现两个Class类的实例。

      因为被具有隔离性的类装载器装载的类不会共享内存空间,使得使用一个类装载器不可能完成的任务变得可以轻而易举,

2、____________________________________________________________

unicode字符,全都是char的。没有所谓的整数等,它都叫unicode字符了,网络上传输的全都是字符。整数,只能存在于内存中,但如何区分呢?

字符流数据无边界

pc,栈的 结构是什么,线性表还是链表,用什么实现的?c么?c上面的又是用什么实现。追溯到cpu指令。

????看完jvm等后对编程有什么启发

指令存在那里,for循环里面的?jvm里面的指令如何与操作系统同联系,中间需要本地方法不?(应该不需要)

java体系结构的四个组成部分:

        java语言

        java calss文件格式

        java API(类库,有很多抽象方法,也有很多底层的方法,比如访问系统资源I/O)

        jvm 抽象的计算机,有自己的指令和内存模型,C语言就没有

        java API和jvm一起组成java平台。

装载与执行

        jvm包含classloader,用来装载class文件,API中只有程序执行时需要的那些类才会被装载,字节码有执行引擎来执行

执行引擎(前三种为软件实现,第四种为硬件实现)

        1、一次性解释字节码:实现简单,效率低(这是“解释”技术的共性)。

        2、即时编译器(just-in-time compiler),第一次执行的字节码会被编译成本地机器代码(然后缓存,以后可以重用)。效率高,但是占用内存大。

        3、自适应优化器,结合1、2的优点,对频繁使用的代码编译成本地代码(10%-20%,但是它们使用的时间为80%-90%),其他仍为解释字节码。

        4、硬件芯片实现,内嵌在芯片里,用本地方法执行java字节码。

        三种理解:1、抽象规范,2、具体实现,3、正在运行的实例(线程)

        执行引擎的行为由指令集来定义。

        指令集:方法的字节码流,有指令序列构成,每个指令包含:一个单字节的指令,和后面跟随的0-n个操作数operand。

                ///////////////////////执行一条指令的任务之一是:决定下一条要执行的指令是什么,有三种方法:////////////////////////////

                        1、顺序执行。2、goto或return。3、异常。

        byteStream:03 3b 84 00 01 a7 ff f9                //都是16进制的

                iconst_0        //03        把int型常量0压入栈        常量0,是索引为0的常量,不是值为0,已经用javap验证。

                istore_0         //3b        把int型的值存入局部变量0中

                iinc 0,1        //84 00 01 

                goto 2        //a7 ff f9

方法

        java方法:由java编写,编译成字节码,存储于class文件中

        本地方法:用(C或C++或汇编)编译成与处理器相关的机器代码,保存于动态连接库中,格式是各平台专有的。

                调用本地方法时,jvm装载包含这个本地方法的动态库,本地方法连接java程序与底层操作系统。

                JNI(java native interface),如果希望保证平台无关性,只能通过调用java         API访问底层系统资源。

类装载器calss loader

        jvm会使用装载第一个类的装载器,装载它所引用的类。        

        JVM规范定义了两种类型的类装载器:启动类装载器(bootstrap)和用户自定义装载器(user-defined class loader)。 

        bootstrap是JVM自带的类装载器,用来装载核心类库,如java.lang.*等。如java.lang.Object是由bootstrap装载的。 

        Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。 System Class Loader是一个特殊的用户自定义类装载器,由JVM的实现者提供,在编程者不特别指定装载器的情况下默认装载用户类。系统类装载器可以通过ClassLoader.getSystemClassLoader() 方法得到。

        当运行一个程序的时候,JVM启动,运行bootstrapclassloader(与jvm一样用本地代码实现),该ClassLoader加载java核心API(即所有java.*开头的类,ExtClassLoader和AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API(例如所有javax.*开头的类和存放在JRE的ext目录下的类),最后AppClassLoader加载CLASSPATH目录下定义的Class,

        似乎JVM自身的ClassLoader已经足够了,为什么我们还需要创建自己的ClassLoader呢?   因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,如果编写你自己的ClassLoader,你可以做到:   1)在执行非置信代码之前,自动验证数字签名   2)动态地创建符合用户特定需要的定制化构建类   3)从特定的场所取得java class,例如数据库中   4) 等等   事实上当使用Applet的时候,就用到了特定的ClassLoader,因为这时需要从网络上加载java class,并且要检查相关的安全信息。   目前的应用服务器大都使用了ClassLoader技术

        ClassLoader Tree & Delegation Model

当你决定创建你自己的ClassLoader时,需要继承java.lang.ClassLoader或者它的子类。在实例化每个ClassLoader对象时,需要指定一个父对象;如果没有指定的话,系统自动指定ClassLoader.getSystemClassLoader()为父对象。 

在Java中,java class的卸载仅仅是一种对系统的优化,有助于减少应用对内存的占用。既然是一种优化方法,那么就完全是JVM自行决定如何实现,对Java开发人员来说是完全透明的。   在什么时候一个java class/interface会被卸载呢?Sun公司的原话是这么说的:"class or interfacemay be unloaded if and only if its class loader is unreachable. Classesloaded by the bootstrap loader may not be unloaded." 

通过线程上下文来加载第三方库jndi实现, 而不依赖于双亲委派.   大部分java app服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。 

        运行时包。

如何回收对象:比如new Person时,20行后的代码才会引用到,gc怎么回收。

沙箱模型:沙箱,包含儿童玩耍的装置,安全环境的意思,

        java沙箱基本组件

                1、类装载器结构--守护被信任类的边界。

                        自定义java.lang.MyTest编译无语法错误,运行报

                2、class文件检验器--calss结构检查(语法检查)、类型数据的语义检查

                3、内置于jvm及java语言的安全特性

                        类型安全的引用转换、GC、数组边界检查、空引用检查

                        结构化的内存访问(无指针算法):class文件无内存地址,jvm装载时再分配。

                        本地方法必须连接,动态链接库。

                4、安全管理器及JAVA API

第五章,jvm

jvm概念:

        1、抽象规范

        2、一个具体的实现:jvm内部如何存储数据,和具体的实现有关。

        3、一个运行中的jvm实例(一个java程序,对应一个jvm实例)                

        初始类中的main方法,作为初始线程的起点,非守护线程。

        每个jvm都有一个执行引擎,执行被装载类方法中的指令。

运行时数据区5个:方法区、堆、java栈、pc计数器、本地方法栈。

                jvm规范对此描述相当抽象,细节有实现者决定。

        jvm装载class文件,解析得到类型信息,将其放入到方法区,并生成Class对象(位于堆区,类型信息与Class对象什么关系,重复么?)

                运行时,把创建的对象存入堆中。方法区和堆,该jvm实例中所有线程共享

        pc计数器和java栈,为线程独享(私有,其他线程不可见)。大小规则(容易处理,但不灵活),不像堆内存,不会出现内存碎片。

                pc的值,指示下一条将被执行的指令。(指令存于pc中?)

                        cpu的pc是指令地址计数器,存放指令地址,那么指令有存在哪里,java指令只有一个字节,是否直接存指令本身

                java栈,存储该线程中java方法的调用状态(包括它的局部变量,入参,返回值,中间结果等)

                        java栈有栈帧(stack frame)组成,一个栈帧对应一个调用状态。调用一个方法,就压入一个栈帧。

                                栈帧包含两部分:局部变量,和操作数栈。局部变量等,属于运行时数据。

        本地方法的调用状态存于本地方法栈。

数据类型:

        基本类型的变量持有原始值(原始数据)

        引用类型的变量持有引用值(地址)        

        retrunAddress类型:jvm内部使用,用来实现finally字句

方法区类型信息:内存不连续,可伸缩

        这个类型及其直接超类的权限定名。class文件中用/代替.        如java/lang/Object

        这个类型是类类型还是接口类型(可见abstract和interface本质上不同)

        这个类型的访问修饰符:public、static、final等

        直接接口的(多个)的权限定名的有序列表

        field信息

                字段名、字段类型、修饰符

        method信息

                方法名、返回类型、参数个数及类型、修饰符。如果不是本地方法或抽象方法,还有以下信息:

                        方法的字节码bytecode

                        操作数栈和该方法栈帧中局部变区的大小

                        异常表

        这个类型的常量池

                直接常量(String、integer等常量),对其他类型、字段、方法的符号引用。因此在java动态链接中起核心作用。

                池中的数据如数组一样通过索引访问。

        除常量以外的所有类(静态)变量

                编译时常量:final声明以及,编译时已知的值初始化的类变量。

        一个到类ClassLoader的引用

                jvm必须跟踪ClassLoader,在动态链接期间,用此来装载它所引用、依赖的类

        一个到类Class的引用

                每一个被装载的类型(类、接口),jvm都会为其创建一个java.lang.Class实例。

                jvm还会以某种方式将Class实例与类信息关联起来。

                Class类使得运行程序可以访问方法区中保存的信息。

        方法表:

                为了提高访问速度,jvm会为每个装载的非抽象类,生成一个方法表,作为类信息的一部分,

                方法表是一个数组,元素是可能被调用用实例方法的直接引用。

堆:内存不连续,可伸缩

        jvm规范没有规定java对象如何在堆中表示

        1、把堆分为:句柄池、对象池。对象引用先指向句柄池,句柄池有2个条目:指向对象的引用,指向类信息的引用

        2、对象中包含指向类信息的指针。

        这样就可以实现动态绑定了。

        堆中对象数据都有一个对象锁(互斥对象,只有一个线程可以拥有),逻辑上还与等待集合(wait set)数据关联。

PC:应该连续

栈:内存不连续,可伸缩        

栈的元素--栈帧:大小确定,堆内存的对象大小不确定。

        1、局部变量区:包含方法的入参和局部变量

                1个字长为单位,从0开始计数的数组,字节码指令opcode通过索引(偏移量?)取数据

                如:runInstanceMethod(char c,int i),局部变量区按索引如下存储:

                        0 reference(实例对象的引用,对象不会被拷贝)

                        1 int (char c 存储时会转换成int)

                        2 int(int i)

        2、操作数栈:也是一个字长为单位,但是是栈,不是数组

        3、帧数据区:

                用来支持常量池解析(入口地址),正常方法返回,异常方法派发机制

        每个方法被执行的时候,都会同时创建一个帧(Frame)用于存储本地变量表、操作栈、动态链接、方法出入口等信息。每一个方法的调用至完成,就意味着一个帧在VM栈中的入栈至出栈的过程。本地变量表存放了编译期可知的各种标量类型(boolean、byte、char、short、int、float、long、double)、对象引用(不是对象本身,仅仅是一个引用指针)、方法返回地址等。其中long和double会占用2个本地变量空间(32bit),其余占用1个。

本地方法栈        

        字节码(java方法),

第六章:class文件

ClassFile {

            u4 magic;//前4个字符永远是0xCAFEBABE

            u2 minor_version;

            u2 major_version;

            u2 constant_pool_count;

            cp_info constant_pool[constant_pool_count-1];

            u2 access_flags;

            u2 this_class;

            u2 super_class;

            u2 interfaces_count;

            u2 interfaces[interfaces_count];

            u2 fields_count;

            field_info fields[fields_count];

            u2 methods_count;

            method_info methods[methods_count];

            u2 attributes_count;

            attribute_info attributes[attributes_count];

    }

线程

        同步,syncronized

        协作,wait,notifyall

保存到什么地方(另类说法)

        程序运行时,我们最好对数据保存到什么地方做到心中有数。特别要注意的是内存的分配。有六个地方都可以保存数据:

        (1) 寄存器。最快,位于CPU。寄存器是根据需要由编译器分配。我们对此没有直接的控制权。

        (2) 堆栈。可通过它的“堆栈指针”获得处理的直接支持。

        (3) 堆。

        (5) 常数存储。常数值通常直接置于程序代码内部。因为它们永远都不会改变

        (6) 非RAM存储。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。比如“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。

3、待整理____________________________________________________________

经典书不是看一两遍就可以的。要反反复复地体会理解。结合JMX

内存模型,class文件。?

一个对象占用多大的内存。?

计算机的基本构成是:运算器、控制器、存储器、输入和输出设备

JVM是一个内存中的虚拟机

编译环境

        .java文件--java编译器--.class文件

        字节码.class文件本地或网络

运行期环境(java平台)

        类装载器字节码的验证--装入.class文件和库文件--java解释器/即时编译器--

        运行数据区5个(除了本地方法栈)--执行引擎--native interface -- native libray

问:OutOfMemory错误分几种?

答:分两种,分别是“OutOfMemoryError:java heap size”和”OutOfMemoryError: PermGen space”,两种都是内存溢出,heap size是说申请不到新的内存了,这个很常见,检查应用或调整堆内存大小。

“PermGen space”是因为永久存储区满了,这个也很常见,一般在热发布的环境中出现,是因为每次发布应用系统都不重启,久而久之永久存储区中的死对象太多导致新对象无法申请内存,一般重新启动一下即可。

问:为什么会产生StackOverflowError?

答:因为一个线程把Stack内存全部耗尽了,一般是递归函数造成的。栈和线程、方法、基本类型值、对象符号引用有关

JIT编译器

        java开始只是解释型的,速度慢,为此后来sun在JVM上提供一个工具,把字节码编译成原生码,下次你来访问的时候直接访问原生码就成了

引用类型包括:类类型,接口类型和数组

栈是运行时的单位,而堆是存储的单位.栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。

栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等.

当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。

程序运行永远都是在栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题,传对象不显示(对象大小不规则)。线程拷贝的对象副本在线程工作内存中,共享对象在主内存。

对象,从某种意义上说,是由基本类型组成的。可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一颗树(即非叶子节点),基本类型则为树的叶子节点。

垃圾回收策略:会暂停程序执行

按照基本回收策略分

1、引用计数(Reference Counting):

比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。

此算法最致命的是无法处理循环引用的问题,即A,B相互引用。

2、标记-清除(Mark-Sweep)+复制(Copying)

3、标记-整理(Mark-Compact)

4、火车头算法。

按分区对待的方式分

增量收集(Incremental Collecting):实时垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因JDK5.0中的收集器没有使用这种算法的。

分代收集(Generational Collecting):基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。

按系统线程分:串行收集(单线程),并行收集。并发收集:相对于串行收集和并行收集而言,前面两个在进行垃圾回收工作时,需要暂停整个运行环境,而只有垃圾回收程序在运行,因此,系统在垃圾回收时会有明显的暂停,而且暂停时间会因为堆越大而越长。

垃圾回收从Java栈开始,获取哪些对象正在被使用,因为栈是真正进行程序执行地方。并发收集器

可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中、大规模应用。使用-XX:+UseConcMarkSweepGC打开

最大垃圾回收暂停:-XX:MaxGCPauseMillis

暂停问题:当堆空间持续增大时,垃圾回收的时间也将会相应的持续增大,对应应用暂停的时间也会相应的增大

内存碎片:并发垃圾回收算法,解决了暂停的问题,但复杂度高,回收效率低下。

String对象如何不变,他不改变参数,返回时new一个新的。

-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,操作系统对一个进程内的线程数是有限制的,不能无限生成,经验值在3000~5000左右

吞吐量最大化:GC时间占总运行时间最小化

年轻代大小选择

响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。

吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用

年轻代大小选择

响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。

吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。

年老代大小选择

响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。

JVM判断2个类相同的条件:

        1、类的全名是否相同,还要看加载此类的类加载器是否一样

所有Java代码都要引用java.lang.Object类,如果用自己的类加载器来完成的话,就会存在很多版本的java.lang.Object类。通过代理模式,对于Java核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。

不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。这种技术在许多框架中都被用到,后面会详细介绍。

        方法-对应线程-main是主线程。

SPI(Service Provider Interface,如JDBC,JNDI) 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题,由此产生了context class loader,是从 JDK 1.2 开始引入的

类加载器是 Java 语言的一个创新。它使得动态安装和更新软件组件成为可能(不重启程序,只需要从服务器上下载新的class文件即可)。

相关推荐