非排序算法编程知识复习(Java,C++基础,链表等)

数据结构

hashmap和hashtable区别,具体实现

HashMap的工作原理是近年来常见的Java面试题。几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此特殊呢?是因为这道题考察的深度很深。这题经常出现在高级或中高级面试中。投资银行更喜欢问这个问题,甚至会要求你实现HashMap来考察你的编程能力。ConcurrentHashMap和其它同步集合的引入让这道题变得更加复杂。让我们开始探索的旅程吧!

“你用过HashMap吗?” “什么是HashMap?你为什么用到它?”

几乎每个人都会回答“是的”,然后回答HashMap的一些特性,譬如HashMap可以接受null键值和值,而Hashtable则不能;HashMap是非synchronized;HashMap很快;以及HashMap储存的是键值对等等。这显示出你已经用过HashMap,而且对它相当的熟悉。但是面试官来个急转直下,从此刻开始问出一些刁钻的问题,关于HashMap的更多基础的细节。面试官可能会问出下面的问题:

“你知道HashMap的工作原理吗?” “你知道HashMap的get()方法的工作原理吗?”

你也许会回答“我没有详查标准的Java API,你可以看看Java源代码或者Open JDK。”“我可以用Google找到答案。”

但一些面试者可能可以给出答案,“HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。”这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,作为Map.Entry。这一点有助于理解获取对象的逻辑。如果你没有意识到这一点,或者错误的认为仅仅只在bucket中存储值的话,你将不会回答如何从HashMap中获取对象的逻辑。这个答案相当的正确,也显示出面试者确实知道hashing以及HashMap的工作原理。但是这仅仅是故事的开始,当面试官加入一些Java程序员每天要碰到的实际场景的时候,错误的答案频现。下个问题可能是关于HashMap中的碰撞探测(collision detection)以及碰撞的解决方法:

“当两个对象的hashcode相同会发生什么?” 从这里开始,真正的困惑开始了,一些面试者会回答因为hashcode相同,所以两个对象是相等的,HashMap将会抛出异常,或者不会存储它们。然后面试官可能会提醒他们有equals()和hashCode()两个方法,并告诉他们两个对象就算hashcode相同,但是它们可能并不相等。一些面试者可能就此放弃,而另外一些还能继续挺进,他们回答“因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。”这个答案非常的合理,虽然有很多种处理碰撞的方法,这种方法是最简单的,也正是HashMap的处理方法。但故事还没有完结,面试官会继续问:

“如果两个键的hashcode相同,你如何获取值对象?” 面试者会回答:当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,然后获取值对象。面试官提醒他如果有两个值对象储存在同一个bucket,他给出答案:将会遍历链表直到找到值对象。面试官会问因为你并没有值对象去比较,你是如何确定确定找到值对象的?除非面试者直到HashMap在链表中存储的是键值对,否则他们不可能回答出这一题。

其中一些记得这个重要知识点的面试者会说,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。完美的答案!

许多情况下,面试者会在这个环节中出错,因为他们混淆了hashCode()和equals()方法。因为在此之前hashCode()屡屡出现,而equals()方法仅仅在获取值对象的时候才出现。一些优秀的开发者会指出使用不可变的、声明作final的对象,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生,提高效率。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择。

如果你认为到这里已经完结了,那么听到下面这个问题的时候,你会大吃一惊。“如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?”除非你真正知道HashMap的工作原理,否则你将回答不出这道题。默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

如果你能够回答这道问题,下面的问题来了:“你了解重新调整HashMap大小存在什么问题吗?”你可能回答不上来,这时面试官会提醒你当多线程的情况下,可能产生条件竞争(race condition)。

当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。这个时候,你可以质问面试官,为什么这么奇怪,要在多线程的环境下使用HashMap呢?:)

  1. 为什么String, Interger这样的wrapper类适合作为键? String, Interger这样的wrapper类作为HashMap的键是再适合不过了,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个field声明成final就能保证hashCode是不变的,那么请这么做吧。因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。
  2. 我们可以使用自定义的对象作为键吗? 这是前一个问题的延伸。当然你可能使用任何对象作为键,只要它遵守了equals()和hashCode()方法的定义规则,并且当对象插入到Map中之后将不会再改变了。如果这个自定义对象时不可变的,那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了。
  3. 我们可以使用CocurrentHashMap来代替Hashtable吗?这是另外一个很热门的面试题,因为ConcurrentHashMap越来越多人用了。我们知道Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性。看看这篇博客查看Hashtable和ConcurrentHashMap的区别。

讲一下动态链表和静态链表的区别

静态链表和动态链表是线性表链式存储结构的两种不同的表示方式。

1、静态链表是用类似于数组方法实现的,是顺序的存储结构,在物理地址上是连续的,而且需要预先分配地址空间大小。所以静态链表的初始长度一般是固定的,在做插入和删除操作时不需要移动元素,仅需修改指针。

2、动态链表是用内存申请函数(malloc/new)动态申请内存的,所以在链表的长度上没有限制。动态链表因为是动态申请内存的,所以每个节点的物理地址不连续,要通过指针来顺序访问。

get和post区别

post和get都可以给服务器发送请求,在做接口测试的时候,我发现有些时候某些功能的接口文档中是用post请求发送的,

但是只要接口一致参数一致用post也能发送请求,并且获取到的返回也是正确的。

那么,在做接口测试的过程中一定要按照接口文档中的请求方式来吗?post和get又有什么区别呢?

1、post和get请求发送数据的方式不同

post请求是将数据合在一起一整个的发送过去

get请求则是将参数放在URL后面发送

这种特性就导致post请求能发送的数据量比get请求多

2、服务器能识别post和get请求

post和get请求在服务器眼中是有区别的,研发可以直接在代码中设置某些链接仅允许post类型的请求或get类型的请求。

例子如下:

---- login.action post 
if(post){ return success }
else { return ERROR }
---- findUserList.action get
if(post){ return ERROR }
else { return SUCCESS }

Java基本知识

Java反射机制

https://www.cnblogs.com/zhaoguhong/p/6937364.html

类加载机制

如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。

加载

加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。

验证

这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

准备

准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:

1

public static int v = 8080;

实际上变量v在准备阶段过后的初始值为0而不是8080,将v赋值为8080的putstatic指令是程序被编译后,存放于类构造器<client>方法之中,这里我们后面会解释。
但是注意如果声明为:

1

public static final int v = 8080;

在编译阶段会为v生成ConstantValue属性,在准备阶段虚拟机会根据ConstantValue属性将v赋值为8080。

解析

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是class文件中的:

  • CONSTANT_Class_info
  • CONSTANT_Field_info
  • CONSTANT_Method_info

等类型的常量。

下面我们解释一下符号引用和直接引用的概念:

  • 符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
  • 直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。

初始化

初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由JVM主导。到了初始阶段,才开始真正执行类中定义的Java程序代码。

初始化阶段是执行类构造器<client>方法的过程。<client>方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证<client>方法执行之前,父类的<client>方法已经执行完毕。p.s: 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成<client>()方法。

注意以下几种情况不会执行类初始化:

  • 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
  • 定义对象数组,不会触发该类的初始化。
  • 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
  • 通过类名获取Class对象,不会触发类的初始化。
  • 通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
  • 通过ClassLoader默认的loadClass方法,也不会触发初始化动作。

内存管理机制

JAVA中的内存分区

编程语言会将内存分为不同的区域,具有不同的特性。学C语言的时候,一般是说分成堆和栈两个区域。 
在方法中定义的基本类型,比如int, double这些,是存放在栈上的。方法的调用层次的组织也是用栈实现的。这里的栈和数据结构里的栈类似,因为方法的调用也是后进先出。方法的调用返回通过栈的伸缩可以很容易实现。所以这些栈上的变量会随着方法栈的收缩消亡。因为栈有大小限制,一般不放很大的内容,所以使用malloc函数申请大块内存,这时候这些内存是在堆上的。 
当然,C中的内分区也并不是那么简单。比如还有常量区等等。

JAVA中则分为更多块。根据java® Virtual Machine Specification的说明,有这几个部分:

The pc Register 
Java Virtual Machine Stacks 
Heap 
Method Area 
Run-Time Constant Pool 
Native Method Stacks

以下内容参考Java Virtual Machine Specification

JAVA虚拟机栈

虚拟机栈类似C中的方法栈,用来管理方法的调用,每个被调用的方法在在其中表示为一个栈帧。栈帧中保存了本地变量表,返回地址等等。 
每个线程有自己独立的虚拟机栈。它的生命周期和线程同步,当线程创建的时候被创建,线程销毁的时候被销毁。 
java中的对象需要通过new关键字得到,所有的对象实例都存放在堆上。当然,引用和基本类型是存放在栈中的。

Object o = new Object();
  • 1

此时o这个引用是存放在栈中的,而真正的Object对象是存放在堆上的。 
栈的特点是访问快,而且栈帧的大小是在编译期就确定了的。

本地方法栈

因为java可以调用其他语言比如C语言的代码,当执行本地方法的时候,栈帧就存在于本地方法栈中而不是虚拟机栈中。此时程序计数区里的数据为undefined

堆是JAVA很重要的区域,因为所有的new出来的instance和array都在堆上。堆又可以分为几个部分。 
堆被所有线程共享。生命周期和JVM同步。

Method Area

It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.

属于堆的一部分。可能会有垃圾回收可能没有。jvm初始化的时候既初始化。 
用于存放已被虚拟机加载的类信息,常量,静态变量,即时编译期编译过后的代码等数据。 
在上面进行的垃圾回收主要是针对常量池的回收和对类型的卸载。

Run-Time Constant Pool

常量区存放一些编译期就确定的常量。 
其实是Method Area的一部分。

程序计数区

标示当前程序运行到的位置。JVM相当于一台抽象的计算机,感觉这个区域类似于真正的CPU中的PC寄存器。 
当程序当前运行到正常的java方法的时候,这里标识的是the address of the Java Virtual Machine instruction currently being executed,当当前运行到本地方法的时候,这里的值是undefined

垃圾回收机制

和C/C++比起来,java的一个优势是,有自动的垃圾回收机制(garbage collection)。在C/C++中,任何malloc出来的值都要在合适的时候free,任何new出来的值都要在合适的时候delete,不然就容易发生内存泄露。而在JAVA里面则不同,JVM有一个垃圾管理机制,也就是有一个垃圾处理的线程,在某些时候会被执行,把不用的东西从内存中清理掉,避免了内存泄露。一般情况下,java程序员不需要去手动地释放内存,不用的内存不管它就是了。

垃圾收集的几种方法

  • mark & swap
  • 引用计数
  • 分代收集
  • stop & copy

垃圾回收要解决的问题大概可以归为以下三个:

  • 判断什么是垃圾(需要被回收)? 
    一般来说,把没有引用指向的对象当做是可被回收的垃圾。因为没有对象指向它,也就无法对它进行操作,这个对象对于我们来说是没用的了,也就是所谓的垃圾。
  • 什么时候回收? 
    内存不足或者当前空闲的时候。一般来说,gc线程的优先级都不太高。
  • 如何进行回收? 
    有多种实现方案。

mark & sweep

先标记出哪些不是垃圾,回收的时候把没有被标记到的认为是垃圾,进行回收。 
标记的方法是,从一个rootSet出发,能被里面某个引用指向的对象就认为是可达的,也就是不是垃圾。可达性是可以传递的,被认为是可达的对象指向的对象也被认为是可达的。 
一般来说,rootset里的初始的引用可以是栈中的临时变量,static类型的常量,寄存器中内容等等,因为这些内容一定是可达的。

这种方法可能会导致后面内存中有很多碎片。 
CSAPP中讲到这种方法的问题还在于判断某些内存中的数据到底是一个引用还是单纯的数值。java具备能够分辨出引用还是不是引用的能力,所以java的GC叫做准确式GC。

引用计数

针对每个对象对象维护一个表示当前有多少个对象指向这个引用的值。实时更新这个值。也就是另某个引用指向它时引用加一,反之减一。值为0也就表示这个对象不可达。 
这种的弊端是可能会因为循环引用导致某些有应该被回收的对象的引用计数不为0,所以无法被回收。 
比如说A持有B的引用,B也持有A的引用,但是除此之外没有指向A和B的引用,程序根本就无法操作A和B,所以他们应该被回收的,但是并没有。

stop & copy

这种方法和前面不同的主要是清理的方式。 
把堆分成两部分,每个时刻其实只有一个部分起到充当堆内存的作用,进行garbage colloction的时候,把当前作为堆内存部分的不是垃圾的内容copy到另一个部分,然后再把这个部分作为堆来使用就行、

这种方法的缺点是,对内存的利用不够,有一部分是用不到的。而且copy的时候,之前的引用的要进行修正。

java中的实现

因为大部分对象的生命周期并不长久,而少部分的对象又可以活得很久,所以可以把堆分为不同的区域,分别存放存活时间短的和存活时间长的对象,分别执行不同的策略。 
把堆分为年轻代,老年代。年轻代上存放经历garbage collection次数较少的对象,老年代上存放经历过较多次garbage collection的对象。 
年轻代分为三个部分,分别是eden,toSurvival,fromSurvival。每个时刻eden加上toSurvival,fromSurvival中的一个区域作为用作分配的区域,另一个Survival区域则是用于发生gc的时候使用。

当发生垃圾回收的时候,根据上面的说法,年轻代中只有少数对象是还存活着的。那么这时候采用stop-copy算法就比较高效,因为只需要比较少的复制操作。年轻代上的garbage collectio叫做minor gc。

当需要在老年代上进行gc操作的时候,采取的是标记算法。老年代上的gc叫做major gc。一般比新生代上的gc费时得多。

rootset包括虚拟机栈中引用的对象,方法区中类静态属性引用的对象,本地区中常量引用的对象,本地方法栈中JNI引用的对象 
java使用了一组叫做OopMap的数据结构来存储rootser,但是执行每条指令都更新这个数据,那就太麻烦了。不在每次执行指令的时候更新,为了避免在枚举可达性的过程中可达性不同步,在某个特定的点更新,然后gc只会在等待所有线程都进行到这个点的时候执行。

finalize()

当gc决定要回收一个对象的内存的时候,就会去调用这个对象的finalize()函数(如果这个类重写了finalize函数并且没有被执行过),然后在下一次gc过程中再去真正回收这个对象。 
finalize函数被用来做一些清理的工作,java几乎所有对象都是通过new方法在堆上分配的,这些内存可以正确地被jvm回收,但是如果调用了本地方法,比如说用到了C里面的malloc,那么就需要在finilize函数中正确地释放这部分内存了。 
考虑下面的代码(摘自《深入理解java虚拟机》)

public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;
    public void isAlive(){
        System.out.println("yes,i am still alive!");
    }

    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Throwable {
        SAVE_HOOK = new FinalizeEscapeGC();
        //对象第一次成功拯救自己
        SAVE_HOOK = null;
        System.gc();
        // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null){
            SAVE_HOOK.isAlive();
        }else{
            System.out.println("no,i am dead!");
        }
        // 下面这段代码与上面的完全相同,但是这次自救却失败了
        SAVE_HOOK = null;
        System.gc();
        // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null){
            SAVE_HOOK.isAlive();
        }else{
            System.out.println("no,i am dead!");
        }
    }
}

这段代码验证了finalize函数只会被执行一次

各种引用

  • 强引用 
    我们平时使用的类似Object o = new Object(),这里o就是一个强引用。前面提到的判断是否应该被回收的问题,被强引用引用的对象就被认为是可达的,就算发生OutOfMemonyError也不会回收它。
  • 软引用 
    如果有一些对象我们可能会用到,但是又不想因为它们发生OutOfMemonyError,那么就把指向它们的引用设为软引用。当内存不足要进行gc的时候,只被软引用引用的对象是可以被回收的。 
    比如SoftReference<Object> weakRef = SoftReference<Object>(new Object()) 
    在设计缓存的时候,会经常会有用到软应用的需求。
  • 弱引用 
    类似软引用,但是只被弱引用指向的对象更有可能被回收。 
    只被弱引用引用对象一旦被垃圾处理器线程发现就会被回收。

内存泄漏和溢出的区别

Java内存泄漏就是没有及时清理内存垃圾,导致系统无法再给你提供内存资源(内存资源耗尽);

而Java内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。

 

内存溢出类似数组越届,超出你能存储的数据的上限
内存泄漏,就是内存使用完毕后,不能释放回收重新使用

 

Java内存泄露与溢出的区别

内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。

Java内存泄漏就是没有及时清理内存垃圾,导致系统无法再给你提供内存资源(内存资源耗尽)。

看到上面的解释,可能有些朋友还是不太理解吧。没问题,看以下例子

1.Java内存泄露是说程序逻辑问题,造成申请的内存无法释放.这样的话无论多少内存,早晚都会被占用光的.
最简单的例子就是死循环了.由于程序判断错误导经常发生此事

2.Java内存泄漏是指在堆上分配的内存没有被释放,从而失去对其控制。这样会造成程序能使用的内存越来越少,导致系统运行速度减慢,严重情况会使程序当掉。

3.关于内存溢出有点出入。比如说你申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
举个现实中的例子:

比如有一个桶,装满了水.你丢个苹果进去。桶的水正常。如果你放个大石头。水就出溢出,内存溢出也就是这个原理。

区别:内存溢出,提供的内存不够;Java内存泄漏,无法再提供内存资源

可能大家会问内存泄露与溢出是考JAVA哪方面?考这个有什么用?

我个人觉的是考大家对JAVA是怎么管理内存这一块的知识?对下是对Java是如何管理内存的解释

Java是如何管理内存

为了判断Java中是否有内存泄露,我们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中,内存 的分配是由程序完成的,而内存的释放是由垃圾收集器(GarbageCollection,GC)完成的,程序员不需要通过调用函数来释放内存,但它只能 回收无用并且不再被其它对象引用的那些对象所占用的空间。

Java的内存垃圾回收机制是从程序的主要运行对象开始检查引用链,当遍历一遍后发现没有被引用的孤立对象就作为垃圾回收。GC为了能够正确释放对 象,必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放 对象的根本原则就是该对象不再被引用。

在Java中,这些无用的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。虽然,我们有几个函数可以访问GC,例如运行GC的函数 System.gc(),但是根据Java语言规范定义,该函数不保证JVM的垃圾收集器一定会执行。因为不同的JVM实现者可能使用不同的算法管理 GC。通常GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行 GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。

在JVM中如果98%的时间是用于GC且可用的Heap size 不足2%的时候将抛出此异常信息。

JVM初始分配的内存由-Xms指定,默认是物理内存的1/64; 

JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。 

JVM内存的最大值跟操作系统有很大的关系。32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了。

注意:如果Xms超过了Xmx值,或者堆最大值和非堆最大值的总和超过了物理内存或者操作系统的最大限制都会引起服务器启动不起来。 

该错误常见场合:

a) Web上传文件时。

b) 开启大型文件或从数据库中一次取了太多的数据。

C基本知识

public、protected、private的区别

1、public:public表明该数据成员、成员函数是对全部用户开放的。全部用户都能够直接进行调用,在程序的不论什么其他地方訪问。

2、private:private表示私有。私有的意思就是除了class自己之外。不论什么人都不能够直接使用,私有財产神圣不可侵犯嘛。即便是子女。朋友,都不能够使用。

和public相反,加上这个修饰的属性和方法,仅仅同意在自己本身这个类里訪问。程序的不论什么其他地方都不能訪问 

3、protected:protected对于子女、朋友来说。就是public的,能够自由使用,没有不论什么限制。而对于其它的外部class,protected就变成private。受保护的,位于public和private中间,加上这个修饰的属性和方法,仅仅能在子类(extends)和同包下的程序訪问,别的的地方不能訪问。

4.default(默认):同一包中的类能够訪问。声明时没有加修饰符,觉得是friendly。

容器

标准容器类说明
一,顺序性容器
vector从后面快速的插入与删除,直接访问任何元素
deque从前面或后面快速的插入与删除,直接访问任何元素
list双链表,从任何地方快速插入与删除
二,关联容器
set快速查找,不允许重复值
multiset快速查找,允许重复值
map一对多映射,基于关键字快速查找,不允许重复值
multimap一对多映射,基于关键字快速查找,允许重复值
三,容器适配器
stack后进先出
queue先进先出
priority_queue最高优先级元素总是第一个出列

其他

怎样解决线程安全问题

一.什么时候会出现线程安全问题?

  在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的的资源:一个变量、一个对象、一个文件、一个数据库表等,而当多个线程同时访问同一个资源的时候,就会存在一个问题:

  由于每个线程执行的过程是不可控的,所以很可能导致最终的结果与实际上的愿望相违背或者直接导致程序出错。

  举个简单的例子:

  现在有两个线程分别从网络上读取数据,然后插入一张数据库表中,要求不能插入重复的数据。

  那么必然在插入数据的过程中存在两个操作:

  1)检查数据库中是否存在该条数据;

  2)如果存在,则不插入;如果不存在,则插入到数据库中。

  假如两个线程分别用thread-1和thread-2表示,某一时刻,thread-1和thread-2都读取到了数据X,那么可能会发生这种情况:

  thread-1去检查数据库中是否存在数据X,然后thread-2也接着去检查数据库中是否存在数据X。

  结果两个线程检查的结果都是数据库中不存在数据X,那么两个线程都分别将数据X插入数据库表当中。

  这个就是线程安全问题,即多个线程同时访问一个资源时,会导致程序运行结果并不是想看到的结果。

  这里面,这个资源被称为:临界资源(也有称为共享资源)。

  也就是说,当多个线程同时访问临界资源(一个对象,对象中的属性,一个文件,一个数据库等)时,就可能会产生线程安全问题。

  不过,当多个线程执行一个方法,方法内部的局部变量并不是临界资源,因为方法是在栈上执行的,而Java栈是线程私有的,因此不会产生线程安全问题。

二.如何解决线程安全问题?

基本上所有的并发模式在解决线程安全问题上,都采用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称同步互斥访问。

 通常来说,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。

  在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock。

  本文主要讲述synchronized的使用方法,Lock的使用方法在下一篇博文中讲述。

全局变量,临时变量,静态变量分别存在哪里

- 代码区,是编译器生成的一个exe区段,拥有可读和可执行属性。存放函数体的二进制代码。 
- 栈区,低地址(小于exe基地址),拥有可读写属性,exe中没有对应的区段,系统加载dll时自动生成,由于内存地址使用方式从大往小减,所以数量有限,尽量不要定义过大的数组变量。 const的局部变量也是放在栈里的,而不是放在常量区。由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 
- 堆区,就是malloc和new之类的内存所在区段,拥有可读写属性,exe中没有对应的区段,系统加载dll时自动生成,首先是利用栈区地址下面的区段,也是低地址,当用完了,会自动分配稍微高一点地址(大于exe基地址)。 malloc和new都在这里分配内存。一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 
- 全局数据区,是编译器生成的一个exe区段,拥有可读写属性,初始和未初始化的全局和静态变量都放在这里。全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后由系统释放。 
- 常量区,是编译器生成的一个exe区段,只有可读属性,比如char s = ” hello world” ,这时候” hello world” 就在常量区,由于没有可写属性,所以修改内容会出错,另外全局的const变量也放在常量区里。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值